diff --git a/src/main/java/com/serotonin/bacnet4j/type/primitive/CharacterString.java b/src/main/java/com/serotonin/bacnet4j/type/primitive/CharacterString.java index 1e21a113..6a8c3a95 100644 --- a/src/main/java/com/serotonin/bacnet4j/type/primitive/CharacterString.java +++ b/src/main/java/com/serotonin/bacnet4j/type/primitive/CharacterString.java @@ -28,185 +28,163 @@ */ package com.serotonin.bacnet4j.type.primitive; -import java.io.UnsupportedEncodingException; +import static com.serotonin.bacnet4j.type.primitive.encoding.StandardCharacterEncodings.ANSI_X3_4; +import static com.serotonin.bacnet4j.type.primitive.encoding.StandardCharacterEncodings.CODE_PAGE_LATIN_1; +import static com.serotonin.bacnet4j.type.primitive.encoding.StandardCharacterEncodings.IBM_MS_DBCS; +import static com.serotonin.bacnet4j.type.primitive.encoding.StandardCharacterEncodings.NO_CODE_PAGE; + +import java.util.List; +import java.util.Objects; +import java.util.ServiceLoader; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; import com.serotonin.bacnet4j.exception.BACnetErrorException; import com.serotonin.bacnet4j.exception.BACnetRuntimeException; import com.serotonin.bacnet4j.type.enumerated.ErrorClass; import com.serotonin.bacnet4j.type.enumerated.ErrorCode; +import com.serotonin.bacnet4j.type.primitive.encoding.CharacterEncoder; +import com.serotonin.bacnet4j.type.primitive.encoding.CharacterEncoding; import com.serotonin.bacnet4j.util.sero.ByteQueue; public class CharacterString extends Primitive { + public static final byte TYPE_ID = 7; - public static final int IBM_MS_DBCS_CODEPAGE = 850; - - public interface Encodings { - byte ANSI_X3_4 = 0; - byte IBM_MS_DBCS = 1; - byte JIS_C_6226 = 2; - byte ISO_10646_UCS_4 = 3; - byte ISO_10646_UCS_2 = 4; - byte ISO_8859_1 = 5; - } + + // load encoders before creating EMPTY + private static final List characterEncoders = loadEncoders(); public static final CharacterString EMPTY = new CharacterString(""); - private final byte encoding; + private final CharacterEncoding encoding; + private final CharacterEncoder encoder; private final String value; public CharacterString(final String value) { - encoding = Encodings.ANSI_X3_4; - this.value = value == null ? "" : value; + this(new CharacterEncoding(ANSI_X3_4), value); } /** * According to Oracle java documentation about Charset, the behavior of optional charsets may vary between java platform implementations. * This concerns ISO_10646_UCS_4 (UTF-32), IBM_MS_DBCS and JIS_C_6226. * @param encoding - * @param value + * @param value */ public CharacterString(final byte encoding, final String value) { + this(new CharacterEncoding(encoding, defaultCodingPage(encoding)), value); + } + + public CharacterString(final CharacterEncoding encoding, final String value) { + this.encoding = encoding; try { - validateEncoding(); + encoder = findEncoder(encoding); } catch (final BACnetErrorException e) { // This is an API constructor, so it doesn't need to throw checked exceptions. Convert to runtime. throw new BACnetRuntimeException(e); } - this.encoding = encoding; this.value = value == null ? "" : value; } - public byte getEncoding() { - return encoding; - } - - public String getValue() { - return value; - } - // // Reading and writing // public CharacterString(final ByteQueue queue) throws BACnetErrorException { final int length = (int) readTag(queue, TYPE_ID); - encoding = queue.pop(); - validateEncoding(); - int headerLength = 1; - if (encoding == Encodings.IBM_MS_DBCS) { - headerLength += 2; - //Decode the codePage - int codePage = queue.popU2B(); - //Currently only the codepage 850 is supported for IBM_MS_DBCS. - if (codePage != IBM_MS_DBCS_CODEPAGE) { - throw new BACnetErrorException(ErrorClass.property, ErrorCode.characterSetNotSupported, Byte.toString(encoding)); - } - } + encoding = createCharacterEncoding(queue); + encoder = findEncoder(encoding); + + int headerLength = calcHeaderLength(); final byte[] bytes = new byte[length - headerLength]; queue.pop(bytes); - value = decode(encoding, bytes); + value = encoder.decode(bytes); + } + + public CharacterEncoding getEncoding() { + return encoding; + } + + public String getValue() { + return value; } @Override public void writeImpl(final ByteQueue queue) { - queue.push(encoding); - queue.push(encode(encoding, value)); + queue.push(encoding.getEncoding()); + queue.push(encoder.encode(value)); } @Override protected long getLength() { - return encode(encoding, value).length + 1; + return encoder.encode(value).length + 1; } @Override public byte getTypeId() { return TYPE_ID; } - - private static byte[] encode(final byte encoding, final String value) { - try { - switch (encoding) { - case Encodings.ANSI_X3_4: - return value.getBytes("UTF-8"); - case Encodings.ISO_10646_UCS_2: - return value.getBytes("UTF-16"); - case Encodings.ISO_8859_1: - return value.getBytes("ISO-8859-1"); - case Encodings.ISO_10646_UCS_4: - return value.getBytes("UTF-32"); - case Encodings.IBM_MS_DBCS: - byte[] bytes = value.getBytes("IBM" + IBM_MS_DBCS_CODEPAGE); - //Add the codePage - byte[] result = new byte[2 + bytes.length]; - result[0] = (byte) (IBM_MS_DBCS_CODEPAGE >> 8); - result[1] = (byte) IBM_MS_DBCS_CODEPAGE; - System.arraycopy(bytes, 0, result, 2, bytes.length); - return result; - default: - return null; - } - } catch (final UnsupportedEncodingException e) { - // Should never happen, so convert to a runtime exception. - throw new RuntimeException(e); + + private int calcHeaderLength() { + int headerLength = 1; + if (encoding.getCodePage() != NO_CODE_PAGE) { + headerLength += 2; } + return headerLength; } - private static String decode(final byte encoding, final byte[] bytes) { - try { - switch (encoding) { - case Encodings.ANSI_X3_4: - return new String(bytes, "UTF-8"); - case Encodings.ISO_10646_UCS_2: - return new String(bytes, "UTF-16"); - case Encodings.ISO_8859_1: - return new String(bytes, "ISO-8859-1"); - case Encodings.ISO_10646_UCS_4: - return new String(bytes, "UTF-32"); - case Encodings.IBM_MS_DBCS: - return new String(bytes, "IBM" + IBM_MS_DBCS_CODEPAGE); - default: - return ""; - } - } catch (final UnsupportedEncodingException e) { - // Should never happen, so convert to a runtime exception. - throw new RuntimeException(e); + private CharacterEncoding createCharacterEncoding(ByteQueue queue) { + byte encodingValue = queue.pop(); + if (encodingValue != IBM_MS_DBCS) { + return new CharacterEncoding(encodingValue, NO_CODE_PAGE); } + //Decode the codePage + int codePage = queue.popU2B(); + return new CharacterEncoding(encodingValue, codePage); } - private void validateEncoding() throws BACnetErrorException { - if (encoding != Encodings.ANSI_X3_4 && encoding != Encodings.ISO_10646_UCS_2 - && encoding != Encodings.ISO_8859_1 && encoding != Encodings.ISO_10646_UCS_4 && encoding != Encodings.IBM_MS_DBCS) - throw new BACnetErrorException(ErrorClass.property, ErrorCode.characterSetNotSupported, - Byte.toString(encoding)); + private static CharacterEncoder findEncoder(CharacterEncoding encoding) throws BACnetErrorException { + return characterEncoders.stream() + .filter(encoder -> encoder.isEncodingSupported(encoding)) + .findFirst() + .orElseThrow(() -> new BACnetErrorException( + ErrorClass.property, + ErrorCode.characterSetNotSupported, + encoding.toString()) + ); } - @Override - public int hashCode() { - final int PRIME = 31; - int result = 1; - result = PRIME * result + (value == null ? 0 : value.hashCode()); - return result; + private static int defaultCodingPage(byte encoding) { + if (encoding == IBM_MS_DBCS) { + return CODE_PAGE_LATIN_1; + } + return NO_CODE_PAGE; + } + + private static List loadEncoders() { + ServiceLoader loader = ServiceLoader.load(CharacterEncoder.class); + return StreamSupport.stream(loader.spliterator(), false) + .collect(Collectors.toList()); } @Override - public boolean equals(final Object obj) { - if (this == obj) + public boolean equals(Object o) { + if (this == o) { return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - final CharacterString other = (CharacterString) obj; - if (encoding != other.encoding) - return false; - if (value == null) { - if (other.value != null) - return false; - } else if (!value.equals(other.value)) + } + if (o == null || getClass() != o.getClass()) { return false; - return true; + } + CharacterString that = (CharacterString) o; + return Objects.equals(encoding, that.encoding) && + Objects.equals(encoder, that.encoder) && + Objects.equals(value, that.value); + } + + @Override + public int hashCode() { + return Objects.hash(encoding, encoder, value); } @Override diff --git a/src/main/java/com/serotonin/bacnet4j/type/primitive/encoding/AbstractCharacterEncoder.java b/src/main/java/com/serotonin/bacnet4j/type/primitive/encoding/AbstractCharacterEncoder.java new file mode 100644 index 00000000..e9e3b745 --- /dev/null +++ b/src/main/java/com/serotonin/bacnet4j/type/primitive/encoding/AbstractCharacterEncoder.java @@ -0,0 +1,41 @@ +package com.serotonin.bacnet4j.type.primitive.encoding; + +import java.io.UnsupportedEncodingException; + +import com.serotonin.bacnet4j.exception.BACnetRuntimeException; + +public abstract class AbstractCharacterEncoder implements CharacterEncoder { + + private final CharacterEncoding characterEncoding; + private final String javaCharsetName; + + public AbstractCharacterEncoder(CharacterEncoding characterEncoding, String javaCharsetName) { + this.characterEncoding = characterEncoding; + this.javaCharsetName = javaCharsetName; + } + + @Override + public boolean isEncodingSupported(CharacterEncoding encoding) { + return characterEncoding.equals(encoding); + } + + @Override + public byte[] encode(String value) { + try { + return value.getBytes(javaCharsetName); + } catch (final UnsupportedEncodingException e) { + // Should never happen + throw new BACnetRuntimeException(e); + } + } + + @Override + public String decode(byte[] bytes) { + try { + return new String(bytes, javaCharsetName); + } catch (final UnsupportedEncodingException e) { + // Should never happen + throw new BACnetRuntimeException(e); + } + } +} diff --git a/src/main/java/com/serotonin/bacnet4j/type/primitive/encoding/AbstractDbcsCharacterEncoder.java b/src/main/java/com/serotonin/bacnet4j/type/primitive/encoding/AbstractDbcsCharacterEncoder.java new file mode 100644 index 00000000..fc175b98 --- /dev/null +++ b/src/main/java/com/serotonin/bacnet4j/type/primitive/encoding/AbstractDbcsCharacterEncoder.java @@ -0,0 +1,34 @@ +package com.serotonin.bacnet4j.type.primitive.encoding; + +import java.io.UnsupportedEncodingException; + +import com.serotonin.bacnet4j.exception.BACnetRuntimeException; + +public abstract class AbstractDbcsCharacterEncoder extends AbstractCharacterEncoder { + + private final CharacterEncoding characterEncoding; + private final String javaCharsetName; + + public AbstractDbcsCharacterEncoder(CharacterEncoding characterEncoding, String javaCharsetName) { + super(characterEncoding, javaCharsetName); + this.characterEncoding = characterEncoding; + this.javaCharsetName = javaCharsetName; + } + + @Override + public byte[] encode(String value) { + try { + byte[] bytes = value.getBytes(javaCharsetName); + //Add the codePage + byte[] result = new byte[2 + bytes.length]; + int codePage = characterEncoding.getCodePage(); + result[0] = (byte) (codePage >> 8); + result[1] = (byte) codePage; + System.arraycopy(bytes, 0, result, 2, bytes.length); + return result; + } catch (final UnsupportedEncodingException e) { + // Should never happen + throw new BACnetRuntimeException(e); + } + } +} diff --git a/src/main/java/com/serotonin/bacnet4j/type/primitive/encoding/AnsiCharacterEncoder.java b/src/main/java/com/serotonin/bacnet4j/type/primitive/encoding/AnsiCharacterEncoder.java new file mode 100644 index 00000000..5b11b8b8 --- /dev/null +++ b/src/main/java/com/serotonin/bacnet4j/type/primitive/encoding/AnsiCharacterEncoder.java @@ -0,0 +1,12 @@ +package com.serotonin.bacnet4j.type.primitive.encoding; + +import static com.serotonin.bacnet4j.type.primitive.encoding.StandardCharacterEncodings.ANSI_X3_4; + +public class AnsiCharacterEncoder extends AbstractCharacterEncoder { + + private static final String JAVA_CHARSET_NAME = "UTF-8"; + + public AnsiCharacterEncoder() { + super(new CharacterEncoding(ANSI_X3_4), JAVA_CHARSET_NAME); + } +} diff --git a/src/main/java/com/serotonin/bacnet4j/type/primitive/encoding/CharacterEncoder.java b/src/main/java/com/serotonin/bacnet4j/type/primitive/encoding/CharacterEncoder.java new file mode 100644 index 00000000..b8c1a574 --- /dev/null +++ b/src/main/java/com/serotonin/bacnet4j/type/primitive/encoding/CharacterEncoder.java @@ -0,0 +1,10 @@ +package com.serotonin.bacnet4j.type.primitive.encoding; + +public interface CharacterEncoder { + + boolean isEncodingSupported(CharacterEncoding encoding); + + byte[] encode(String value); + + String decode(byte[] bytes); +} diff --git a/src/main/java/com/serotonin/bacnet4j/type/primitive/encoding/CharacterEncoding.java b/src/main/java/com/serotonin/bacnet4j/type/primitive/encoding/CharacterEncoding.java new file mode 100644 index 00000000..29ae4609 --- /dev/null +++ b/src/main/java/com/serotonin/bacnet4j/type/primitive/encoding/CharacterEncoding.java @@ -0,0 +1,54 @@ +package com.serotonin.bacnet4j.type.primitive.encoding; + +import static com.serotonin.bacnet4j.type.primitive.encoding.StandardCharacterEncodings.NO_CODE_PAGE; + +import java.util.Objects; + +public class CharacterEncoding { + + private final byte encoding; + private final int codePage; + + public CharacterEncoding(byte encoding) { + this(encoding, NO_CODE_PAGE); + } + + public CharacterEncoding(byte encoding, int codePage) { + this.encoding = encoding; + this.codePage = codePage; + } + + public byte getEncoding() { + return encoding; + } + + public int getCodePage() { + return codePage; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + CharacterEncoding that = (CharacterEncoding) o; + return encoding == that.encoding && + codePage == that.codePage; + } + + @Override + public int hashCode() { + return Objects.hash(encoding, codePage); + } + + @Override + public String toString() { + return "CharacterEncoding{" + + "encoding=" + encoding + + ", codePage=" + codePage + + '}'; + } +} diff --git a/src/main/java/com/serotonin/bacnet4j/type/primitive/encoding/DbcsCp850CharacterEncoder.java b/src/main/java/com/serotonin/bacnet4j/type/primitive/encoding/DbcsCp850CharacterEncoder.java new file mode 100644 index 00000000..0659c407 --- /dev/null +++ b/src/main/java/com/serotonin/bacnet4j/type/primitive/encoding/DbcsCp850CharacterEncoder.java @@ -0,0 +1,14 @@ +package com.serotonin.bacnet4j.type.primitive.encoding; + +import static com.serotonin.bacnet4j.type.primitive.encoding.StandardCharacterEncodings.IBM_MS_DBCS; + +public class DbcsCp850CharacterEncoder extends AbstractDbcsCharacterEncoder { + + public static final int DOS_LATIN_1_CODEPAGE = 850; + private static final String JAVA_ENCODING = "IBM850"; + + public DbcsCp850CharacterEncoder() { + super(new CharacterEncoding(IBM_MS_DBCS, DOS_LATIN_1_CODEPAGE), JAVA_ENCODING); + } + +} diff --git a/src/main/java/com/serotonin/bacnet4j/type/primitive/encoding/DbcsCp932CharacterEncoder.java b/src/main/java/com/serotonin/bacnet4j/type/primitive/encoding/DbcsCp932CharacterEncoder.java new file mode 100644 index 00000000..32f7ce33 --- /dev/null +++ b/src/main/java/com/serotonin/bacnet4j/type/primitive/encoding/DbcsCp932CharacterEncoder.java @@ -0,0 +1,14 @@ +package com.serotonin.bacnet4j.type.primitive.encoding; + +import static com.serotonin.bacnet4j.type.primitive.encoding.StandardCharacterEncodings.IBM_MS_DBCS; + +public class DbcsCp932CharacterEncoder extends AbstractDbcsCharacterEncoder { + + public static final int JAPANESE_CODEPAGE = 932; + private static final String JAVA_CHARSET_NAME = "MS932"; + + public DbcsCp932CharacterEncoder() { + super(new CharacterEncoding(IBM_MS_DBCS, JAPANESE_CODEPAGE), JAVA_CHARSET_NAME); + } + +} diff --git a/src/main/java/com/serotonin/bacnet4j/type/primitive/encoding/Iso8859CharacterEncoder.java b/src/main/java/com/serotonin/bacnet4j/type/primitive/encoding/Iso8859CharacterEncoder.java new file mode 100644 index 00000000..423b0c85 --- /dev/null +++ b/src/main/java/com/serotonin/bacnet4j/type/primitive/encoding/Iso8859CharacterEncoder.java @@ -0,0 +1,12 @@ +package com.serotonin.bacnet4j.type.primitive.encoding; + +import static com.serotonin.bacnet4j.type.primitive.encoding.StandardCharacterEncodings.ISO_8859_1; + +public class Iso8859CharacterEncoder extends AbstractCharacterEncoder { + + private static final String JAVA_CHARSET_NAME = "ISO-8859-1"; + + public Iso8859CharacterEncoder() { + super(new CharacterEncoding(ISO_8859_1), JAVA_CHARSET_NAME); + } +} diff --git a/src/main/java/com/serotonin/bacnet4j/type/primitive/encoding/StandardCharacterEncodings.java b/src/main/java/com/serotonin/bacnet4j/type/primitive/encoding/StandardCharacterEncodings.java new file mode 100644 index 00000000..dfffeb1a --- /dev/null +++ b/src/main/java/com/serotonin/bacnet4j/type/primitive/encoding/StandardCharacterEncodings.java @@ -0,0 +1,18 @@ +package com.serotonin.bacnet4j.type.primitive.encoding; + +public class StandardCharacterEncodings { + + private StandardCharacterEncodings() { + } + + public static final byte ANSI_X3_4 = 0; + public static final byte IBM_MS_DBCS = 1; + public static final byte JIS_C_6226 = 2; + public static final byte ISO_10646_UCS_4 = 3; + public static final byte ISO_10646_UCS_2 = 4; + public static final byte ISO_8859_1 = 5; + + public static final int CODE_PAGE_LATIN_1 = 850; + public static final int NO_CODE_PAGE = -1; + +} diff --git a/src/main/java/com/serotonin/bacnet4j/type/primitive/encoding/Ucs2CharacterEncoder.java b/src/main/java/com/serotonin/bacnet4j/type/primitive/encoding/Ucs2CharacterEncoder.java new file mode 100644 index 00000000..35d522b2 --- /dev/null +++ b/src/main/java/com/serotonin/bacnet4j/type/primitive/encoding/Ucs2CharacterEncoder.java @@ -0,0 +1,12 @@ +package com.serotonin.bacnet4j.type.primitive.encoding; + +import static com.serotonin.bacnet4j.type.primitive.encoding.StandardCharacterEncodings.ISO_10646_UCS_2; + +public class Ucs2CharacterEncoder extends AbstractCharacterEncoder { + + private static final String JAVA_CHARSET_NAME = "UTF-16"; + + public Ucs2CharacterEncoder() { + super(new CharacterEncoding(ISO_10646_UCS_2), JAVA_CHARSET_NAME); + } +} diff --git a/src/main/java/com/serotonin/bacnet4j/type/primitive/encoding/Ucs4CharacterEncoder.java b/src/main/java/com/serotonin/bacnet4j/type/primitive/encoding/Ucs4CharacterEncoder.java new file mode 100644 index 00000000..df9d35b0 --- /dev/null +++ b/src/main/java/com/serotonin/bacnet4j/type/primitive/encoding/Ucs4CharacterEncoder.java @@ -0,0 +1,12 @@ +package com.serotonin.bacnet4j.type.primitive.encoding; + +import static com.serotonin.bacnet4j.type.primitive.encoding.StandardCharacterEncodings.ISO_10646_UCS_4; + +public class Ucs4CharacterEncoder extends AbstractCharacterEncoder { + + private static final String JAVA_CHARSET_NAME = "UTF-32"; + + public Ucs4CharacterEncoder() { + super(new CharacterEncoding(ISO_10646_UCS_4), JAVA_CHARSET_NAME); + } +} diff --git a/src/main/resources/META-INF/services/com.serotonin.bacnet4j.type.primitive.encoding.CharacterEncoder b/src/main/resources/META-INF/services/com.serotonin.bacnet4j.type.primitive.encoding.CharacterEncoder new file mode 100644 index 00000000..1f049dc2 --- /dev/null +++ b/src/main/resources/META-INF/services/com.serotonin.bacnet4j.type.primitive.encoding.CharacterEncoder @@ -0,0 +1,6 @@ +com.serotonin.bacnet4j.type.primitive.encoding.AnsiCharacterEncoder +com.serotonin.bacnet4j.type.primitive.encoding.DbcsCp850CharacterEncoder +com.serotonin.bacnet4j.type.primitive.encoding.DbcsCp932CharacterEncoder +com.serotonin.bacnet4j.type.primitive.encoding.Iso8859CharacterEncoder +com.serotonin.bacnet4j.type.primitive.encoding.Ucs2CharacterEncoder +com.serotonin.bacnet4j.type.primitive.encoding.Ucs4CharacterEncoder \ No newline at end of file diff --git a/src/test/java/com/serotonin/bacnet4j/AnnexFEncodingTest.java b/src/test/java/com/serotonin/bacnet4j/AnnexFEncodingTest.java index 169e87b8..f5ac8961 100644 --- a/src/test/java/com/serotonin/bacnet4j/AnnexFEncodingTest.java +++ b/src/test/java/com/serotonin/bacnet4j/AnnexFEncodingTest.java @@ -157,6 +157,7 @@ import com.serotonin.bacnet4j.type.primitive.Unsigned16; import com.serotonin.bacnet4j.type.primitive.Unsigned32; import com.serotonin.bacnet4j.type.primitive.UnsignedInteger; +import com.serotonin.bacnet4j.type.primitive.encoding.StandardCharacterEncodings; import com.serotonin.bacnet4j.util.sero.ByteQueue; public class AnnexFEncodingTest { @@ -172,7 +173,8 @@ public void before() { public void e1_1aTest() { final AcknowledgeAlarmRequest acknowledgeAlarmRequest = new AcknowledgeAlarmRequest(new UnsignedInteger(1), new ObjectIdentifier(ObjectType.analogInput, 2), EventState.highLimit, - new TimeStamp(new UnsignedInteger(16)), new CharacterString(CharacterString.Encodings.ANSI_X3_4, "MDL"), + new TimeStamp(new UnsignedInteger(16)), + new CharacterString(StandardCharacterEncodings.ANSI_X3_4, "MDL"), new TimeStamp( new DateTime(new Date(1992, Month.JUNE, 21, DayOfWeek.UNSPECIFIED), new Time(13, 3, 41, 9)))); @@ -426,7 +428,7 @@ public void e1_8bTest() { @Test public void e1_9aTest() { final LifeSafetyOperationRequest lifeSafetyOperationRequest = new LifeSafetyOperationRequest( - new UnsignedInteger(18), new CharacterString(CharacterString.Encodings.ANSI_X3_4, "MDL"), + new UnsignedInteger(18), new CharacterString(StandardCharacterEncodings.ANSI_X3_4, "MDL"), LifeSafetyOperation.reset, new ObjectIdentifier(ObjectType.lifeSafetyPoint, 1)); final ConfirmedRequest pdu = new ConfirmedRequest(false, false, false, MaxSegments.UNSPECIFIED, @@ -717,7 +719,7 @@ public void e3_2dTest() { public void e3_3aTest() { final List propertyValues = new ArrayList<>(); propertyValues.add(new PropertyValue(PropertyIdentifier.objectName, null, - new CharacterString(CharacterString.Encodings.ANSI_X3_4, "Trend 1"), null)); + new CharacterString(StandardCharacterEncodings.ANSI_X3_4, "Trend 1"), null)); propertyValues .add(new PropertyValue(PropertyIdentifier.fileAccessMethod, null, FileAccessMethod.recordAccess, null)); final ConfirmedRequestService service = new CreateObjectRequest(ObjectType.file, @@ -970,7 +972,7 @@ public void e3_13Test() { @Test public void e4_1aTest() { final ConfirmedRequestService service = new DeviceCommunicationControlRequest(new UnsignedInteger(5), - EnableDisable.disable, new CharacterString(CharacterString.Encodings.ANSI_X3_4, "#egbdf!")); + EnableDisable.disable, new CharacterString(StandardCharacterEncodings.ANSI_X3_4, "#egbdf!")); final APDU pdu = new ConfirmedRequest(false, false, false, MaxSegments.UNSPECIFIED, MaxApduLength.UP_TO_1024, (byte) 5, (byte) 0, 0, service); compare(pdu, "00040511090519012D080023656762646621"); @@ -1012,7 +1014,7 @@ public void e4_3Test() { @Test public void e4_4aTest() { final ConfirmedRequestService service = new ReinitializeDeviceRequest(ReinitializedStateOfDevice.warmstart, - new CharacterString(CharacterString.Encodings.ANSI_X3_4, "AbCdEfGh")); + new CharacterString(StandardCharacterEncodings.ANSI_X3_4, "AbCdEfGh")); final APDU pdu = new ConfirmedRequest(false, false, false, MaxSegments.UNSPECIFIED, MaxApduLength.UP_TO_128, (byte) 2, (byte) 0, 0, service); compare(pdu, "0001021409011D09004162436445664768"); @@ -1028,7 +1030,7 @@ public void e4_4bTest() { public void e4_5aTest() { final ConfirmedRequestService service = new ConfirmedTextMessageRequest( new ObjectIdentifier(ObjectType.device, 5), MessagePriority.normal, - new CharacterString(CharacterString.Encodings.ANSI_X3_4, "PM required for PUMP347")); + new CharacterString(StandardCharacterEncodings.ANSI_X3_4, "PM required for PUMP347")); final APDU pdu = new ConfirmedRequest(false, false, false, MaxSegments.UNSPECIFIED, MaxApduLength.UP_TO_128, (byte) 3, (byte) 0, 0, service); compare(pdu, "000103130C0200000529003D1800504D20726571756972656420666F722050554D50333437"); @@ -1044,7 +1046,7 @@ public void e4_5bTest() { public void e4_6Test() { final UnconfirmedRequestService service = new UnconfirmedTextMessageRequest( new ObjectIdentifier(ObjectType.device, 5), MessagePriority.normal, - new CharacterString(CharacterString.Encodings.ANSI_X3_4, "PM required for PUMP347")); + new CharacterString(StandardCharacterEncodings.ANSI_X3_4, "PM required for PUMP347")); final APDU pdu = new UnconfirmedRequest(service); compare(pdu, "10050C0200000529003D1800504D20726571756972656420666F722050554D50333437"); } @@ -1060,7 +1062,7 @@ public void e4_7Test() { @Test public void e4_8aTest() { final UnconfirmedRequestService service = new WhoHasRequest(null, - new CharacterString(CharacterString.Encodings.ANSI_X3_4, "OATemp")); + new CharacterString(StandardCharacterEncodings.ANSI_X3_4, "OATemp")); final APDU pdu = new UnconfirmedRequest(service); compare(pdu, "10073D07004F4154656D70"); } @@ -1069,7 +1071,7 @@ public void e4_8aTest() { public void e4_8bTest() { final UnconfirmedRequestService service = new IHaveRequest(new ObjectIdentifier(ObjectType.device, 8), new ObjectIdentifier(ObjectType.analogInput, 3), - new CharacterString(CharacterString.Encodings.ANSI_X3_4, "OATemp")); + new CharacterString(StandardCharacterEncodings.ANSI_X3_4, "OATemp")); final APDU pdu = new UnconfirmedRequest(service); compare(pdu, "1001C402000008C4000000037507004F4154656D70"); } @@ -1086,7 +1088,7 @@ public void e4_8cTest() { public void e4_8dTest() { final UnconfirmedRequestService service = new IHaveRequest(new ObjectIdentifier(ObjectType.device, 8), new ObjectIdentifier(ObjectType.analogInput, 3), - new CharacterString(CharacterString.Encodings.ANSI_X3_4, "OATemp")); + new CharacterString(StandardCharacterEncodings.ANSI_X3_4, "OATemp")); final APDU pdu = new UnconfirmedRequest(service); compare(pdu, "1001C402000008C4000000037507004F4154656D70"); } diff --git a/src/test/java/com/serotonin/bacnet4j/obj/BACnetObjectTest.java b/src/test/java/com/serotonin/bacnet4j/obj/BACnetObjectTest.java index 6db6f275..531a9d4a 100644 --- a/src/test/java/com/serotonin/bacnet4j/obj/BACnetObjectTest.java +++ b/src/test/java/com/serotonin/bacnet4j/obj/BACnetObjectTest.java @@ -30,6 +30,7 @@ import com.serotonin.bacnet4j.type.primitive.SignedInteger; import com.serotonin.bacnet4j.type.primitive.Unsigned32; import com.serotonin.bacnet4j.type.primitive.UnsignedInteger; +import com.serotonin.bacnet4j.type.primitive.encoding.StandardCharacterEncodings; import com.serotonin.bacnet4j.util.RequestUtils; public class BACnetObjectTest extends AbstractTest { @@ -44,7 +45,8 @@ public void afterInit() throws Exception { new NameValue("tag2", Null.instance))); d2.writePropertyInternal(PropertyIdentifier.forId(6789), new BACnetArray<>(new Real(0), new Real(1), new Real(2))); - d2.writePropertyInternal(PropertyIdentifier.protocolVersion, new CharacterString(CharacterString.Encodings.ANSI_X3_4, "hxzy-1.01")); + d2.writePropertyInternal(PropertyIdentifier.protocolVersion, + new CharacterString(StandardCharacterEncodings.ANSI_X3_4, "hxzy-1.01")); } @Test diff --git a/src/test/java/com/serotonin/bacnet4j/service/acknowledgement/InvalidPropertyValueTest.java b/src/test/java/com/serotonin/bacnet4j/service/acknowledgement/InvalidPropertyValueTest.java index 99531845..edb9c58f 100644 --- a/src/test/java/com/serotonin/bacnet4j/service/acknowledgement/InvalidPropertyValueTest.java +++ b/src/test/java/com/serotonin/bacnet4j/service/acknowledgement/InvalidPropertyValueTest.java @@ -5,11 +5,6 @@ import static org.junit.Assert.fail; -import java.util.ArrayList; -import java.util.List; - -import org.junit.Test; - import com.serotonin.bacnet4j.exception.BACnetException; import com.serotonin.bacnet4j.type.constructed.ReadAccessResult; import com.serotonin.bacnet4j.type.constructed.ReadAccessResult.Result; @@ -22,7 +17,11 @@ import com.serotonin.bacnet4j.type.primitive.CharacterString; import com.serotonin.bacnet4j.type.primitive.ObjectIdentifier; import com.serotonin.bacnet4j.type.primitive.UnsignedInteger; +import com.serotonin.bacnet4j.type.primitive.encoding.StandardCharacterEncodings; import com.serotonin.bacnet4j.util.sero.ByteQueue; +import java.util.ArrayList; +import java.util.List; +import org.junit.Test; /** * @@ -37,24 +36,25 @@ public class InvalidPropertyValueTest { @Test public void testParse() { - + List deviceResultList = new ArrayList<>(); - + //Error reading a property ErrorClassAndCode errorResponse = new ErrorClassAndCode(ErrorClass.property, ErrorCode.unknownProperty); UnsignedInteger propertyArrayIndex = new UnsignedInteger(0); deviceResultList.add(new Result(PropertyIdentifier.maxSegmentsAccepted, propertyArrayIndex, errorResponse)); - + //String protocol version which is Invalid as per the spec - deviceResultList.add(new Result(PropertyIdentifier.protocolVersion, new UnsignedInteger(0), new CharacterString(CharacterString.Encodings.ANSI_X3_4, "hxzy-1.01"))); - + deviceResultList.add(new Result(PropertyIdentifier.protocolVersion, new UnsignedInteger(0), new CharacterString( + StandardCharacterEncodings.ANSI_X3_4, "hxzy-1.01"))); + SequenceOf deviceResults = new SequenceOf<>(deviceResultList); - + List resultList = new ArrayList<>(); resultList.add(new ReadAccessResult(new ObjectIdentifier(ObjectType.device, 1003), deviceResults)); SequenceOf results = new SequenceOf<>(resultList); ReadPropertyMultipleAck mockAck = new ReadPropertyMultipleAck(results); - + ByteQueue queue = new ByteQueue(); mockAck.write(queue); diff --git a/src/test/java/com/serotonin/bacnet4j/type/EncodableTest.java b/src/test/java/com/serotonin/bacnet4j/type/EncodableTest.java index 5ab1063b..5ef37f47 100644 --- a/src/test/java/com/serotonin/bacnet4j/type/EncodableTest.java +++ b/src/test/java/com/serotonin/bacnet4j/type/EncodableTest.java @@ -2,6 +2,8 @@ import static org.junit.Assert.assertEquals; +import java.io.UnsupportedEncodingException; + import org.junit.Test; import com.serotonin.bacnet4j.enums.DayOfWeek; @@ -31,6 +33,9 @@ import com.serotonin.bacnet4j.type.primitive.Time; import com.serotonin.bacnet4j.type.primitive.Unsigned8; import com.serotonin.bacnet4j.type.primitive.UnsignedInteger; +import com.serotonin.bacnet4j.type.primitive.encoding.CharacterEncoding; +import com.serotonin.bacnet4j.type.primitive.encoding.DbcsCp932CharacterEncoder; +import com.serotonin.bacnet4j.type.primitive.encoding.StandardCharacterEncodings; import com.serotonin.bacnet4j.util.sero.ByteQueue; public class EncodableTest { @@ -120,13 +125,27 @@ public void _20_2_15() throws BACnetException { new CharacterString("This is a BACnet string!")); // TODO The spec say this starts with '75'. Should probably point this out. decodePrimitive("7D0A004672616EC3A7616973", 7, new CharacterString("Français")); - decodePrimitive("0A03A8", 0, new BitString(new boolean[] { true, false, true, false, true, })); + decodePrimitive("0A03A8", 0, new BitString(new boolean[]{true, false, true, false, true,})); decodePrimitive("9900", 9, new Enumerated(0)); decodePrimitive("9C5B011804", 9, new Date(1991, Month.JANUARY, 24, DayOfWeek.THURSDAY)); decodePrimitive("4C11232D11", 4, new Time(17, 35, 45, 17)); decodePrimitive("4C00C0000F", 4, new ObjectIdentifier(ObjectType.binaryInput, 15)); } + @Test + public void shouldDecodeJapaneseEncoding() throws BACnetException, UnsupportedEncodingException { + ByteQueue queue = new ByteQueue(); + String text = "JapaneseEncoding ヲ"; + CharacterString value = new CharacterString( + new CharacterEncoding(StandardCharacterEncodings.IBM_MS_DBCS, + DbcsCp932CharacterEncoder.JAPANESE_CODEPAGE), + text); + value.write(queue); + + CharacterString characterString = Encodable.read(queue, CharacterString.class); + assertEquals(text, characterString.getValue()); + } + private static void decodePrimitive(final String hex, final Primitive expected) throws BACnetException { // Decode the given hex and compare final ByteQueue queue = new ByteQueue(hex);