diff --git a/quickfixj-base/src/main/java/org/quickfixj/CharsetSupport.java b/quickfixj-base/src/main/java/org/quickfixj/CharsetSupport.java index d176df7474..dcc435aff7 100644 --- a/quickfixj-base/src/main/java/org/quickfixj/CharsetSupport.java +++ b/quickfixj-base/src/main/java/org/quickfixj/CharsetSupport.java @@ -64,6 +64,10 @@ public static void setCharset(String charset) throws UnsupportedEncodingExceptio CharsetSupport.isStringEquivalent = isStringEquivalent(charsetInstance); } + public static void setDefaultCharset() throws UnsupportedEncodingException { + setCharset(getDefaultCharset()); + } + public static String getCharset() { return charset; } diff --git a/quickfixj-core/src/main/java/quickfix/CachedFileStore.java b/quickfixj-core/src/main/java/quickfix/CachedFileStore.java index 6ea5d01706..494b3cef1e 100644 --- a/quickfixj-core/src/main/java/quickfix/CachedFileStore.java +++ b/quickfixj-core/src/main/java/quickfix/CachedFileStore.java @@ -28,6 +28,7 @@ import java.io.BufferedOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; +import java.io.EOFException; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; @@ -86,8 +87,6 @@ public class CachedFileStore implements MessageStore { private FileOutputStream headerFileOutputStream; - private final String charsetEncoding = CharsetSupport.getCharset(); - CachedFileStore(String path, SessionID sessionID, boolean syncWrites) throws IOException { this.syncWrites = syncWrites; @@ -308,16 +307,15 @@ public boolean get(int sequence, String message) { throw new UnsupportedOperationException("not supported"); } - private String read(long offset, long size) throws IOException { - final byte[] data = new byte[(int) size]; - - messageFileReader.seek(offset); - if (messageFileReader.read(data) != size) { - throw new IOException("Truncated input while reading message: " - + new String(data, charsetEncoding)); + private String read(long offset, int size) throws IOException { + try { + final byte[] data = new byte[size]; + messageFileReader.seek(offset); + messageFileReader.readFully(data); + return new String(data, CharsetSupport.getCharset()); + } catch (EOFException eofe) { // can't read fully + throw new IOException("Truncated input while reading message: offset=" + offset + ", expected size=" + size, eofe); } - - return new String(data, charsetEncoding); } private Collection getMessage(long startSequence, long endSequence) throws IOException { @@ -326,7 +324,7 @@ private Collection getMessage(long startSequence, long endSequence) thro final List offsetAndSizes = messageIndex.get(startSequence, endSequence); for (final long[] offsetAndSize : offsetAndSizes) { if (offsetAndSize != null) { - final String message = read(offsetAndSize[0], offsetAndSize[1]); + final String message = read(offsetAndSize[0], (int) offsetAndSize[1]); messages.add(message); } } @@ -341,7 +339,8 @@ private Collection getMessage(long startSequence, long endSequence) thro */ public boolean set(int sequence, String message) throws IOException { final long offset = messageFileWriter.getFilePointer(); - final int size = message.length(); + final byte[] messageBytes = message.getBytes(CharsetSupport.getCharset()); + final int size = messageBytes.length; messageIndex.put((long) sequence, new long[] { offset, size }); headerDataOutputStream.writeInt(sequence); headerDataOutputStream.writeLong(offset); @@ -350,7 +349,7 @@ public boolean set(int sequence, String message) throws IOException { if (syncWrites) { headerFileOutputStream.getFD().sync(); } - messageFileWriter.write(message.getBytes(CharsetSupport.getCharset())); + messageFileWriter.write(messageBytes); return true; } diff --git a/quickfixj-core/src/main/java/quickfix/FileStore.java b/quickfixj-core/src/main/java/quickfix/FileStore.java index 542347a0e0..0b4cf6b6e6 100644 --- a/quickfixj-core/src/main/java/quickfix/FileStore.java +++ b/quickfixj-core/src/main/java/quickfix/FileStore.java @@ -63,7 +63,6 @@ public class FileStore implements MessageStore, Closeable { private final String sessionFileName; private final boolean syncWrites; private final int maxCachedMsgs; - private final String charsetEncoding = CharsetSupport.getCharset(); private RandomAccessFile messageFileReader; private RandomAccessFile messageFileWriter; private DataOutputStream headerDataOutputStream; @@ -350,7 +349,7 @@ private String getMessage(long offset, int size, int i) throws IOException { final byte[] data = new byte[size]; messageFileReader.seek(offset); messageFileReader.readFully(data); - return new String(data, charsetEncoding); + return new String(data, CharsetSupport.getCharset()); } catch (EOFException eofe) { // can't read fully throw new IOException("Truncated input while reading message: messageIndex=" + i + ", offset=" + offset + ", expected size=" + size, eofe); @@ -363,7 +362,8 @@ private String getMessage(long offset, int size, int i) throws IOException { @Override public boolean set(int sequence, String message) throws IOException { final long offset = messageFileWriter.getFilePointer(); - final int size = message.length(); + final byte[] messageBytes = message.getBytes(CharsetSupport.getCharset()); + final int size = messageBytes.length; if (messageIndex != null) { updateMessageIndex(sequence, offset, size); } @@ -374,7 +374,7 @@ public boolean set(int sequence, String message) throws IOException { if (syncWrites) { headerFileOutputStream.getFD().sync(); } - messageFileWriter.write(message.getBytes(CharsetSupport.getCharset())); + messageFileWriter.write(messageBytes); return true; } diff --git a/quickfixj-core/src/test/java/quickfix/AbstractMessageStoreTest.java b/quickfixj-core/src/test/java/quickfix/AbstractMessageStoreTest.java index 06d93ba4d3..40632bc471 100644 --- a/quickfixj-core/src/test/java/quickfix/AbstractMessageStoreTest.java +++ b/quickfixj-core/src/test/java/quickfix/AbstractMessageStoreTest.java @@ -20,11 +20,14 @@ package quickfix; import junit.framework.TestCase; +import org.quickfixj.CharsetSupport; import java.io.IOException; import java.util.ArrayList; +import java.util.List; public abstract class AbstractMessageStoreTest extends TestCase { + private SessionID sessionID; private MessageStore store; @@ -167,6 +170,44 @@ public void testRefreshMessageStore() throws Exception { } } + public void testSetAndGetMessageWithAsciiCharacters() throws IOException { + MessageStore underTest = getStore(); + + if (underTest instanceof SleepycatStore) { + return; + } + + underTest.set(1, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVXYZ0123456789"); + + List messages = new ArrayList<>(); + underTest.get(1, 1, messages); + + assertEquals(1, messages.size()); + assertEquals("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVXYZ0123456789", messages.get(0)); + } + + public void testSetAndGetMessageWithUnicodeCharacters() throws IOException { + MessageStore underTest = getStore(); + + if (underTest instanceof SleepycatStore) { + return; + } + + CharsetSupport.setCharset("UTF-8"); + + try { + underTest.set(1, "a \u00A9 \u2603 \uD834\uDF06"); + + List messages = new ArrayList<>(); + underTest.get(1, 1, messages); + + assertEquals(1, messages.size()); + assertEquals("a \u00A9 \u2603 \uD834\uDF06", messages.get(0)); + } finally { + CharsetSupport.setDefaultCharset(); + } + } + protected void closeMessageStore(MessageStore store) throws IOException { // does nothing, by default } diff --git a/quickfixj-core/src/test/java/quickfix/FieldTest.java b/quickfixj-core/src/test/java/quickfix/FieldTest.java index 6035fc563b..879ade68fb 100644 --- a/quickfixj-core/src/test/java/quickfix/FieldTest.java +++ b/quickfixj-core/src/test/java/quickfix/FieldTest.java @@ -19,11 +19,13 @@ package quickfix; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - +import java.io.UnsupportedEncodingException; +import java.math.BigDecimal; +import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.LocalTime; import java.util.Arrays; +import java.util.Date; import org.junit.Test; import org.quickfixj.CharsetSupport; @@ -39,6 +41,10 @@ import quickfix.fix50.MarketDataIncrementalRefresh; import quickfix.fix50.NewOrderSingle; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + public class FieldTest { @Test @@ -52,6 +58,185 @@ public void testMessageSetGetString() { assertEquals(side1, side2); } + private void testFieldCalcuations(String value, int checksum, int length) { + Field field = new Field<>(12, value); + field.setObject(value); + assertEquals("12=" + value, field.toString()); + assertEquals(checksum, field.getChecksum()); + assertEquals(length, field.getLength()); + + value = value.substring(0, value.length() - 1) + (char)(value.charAt(value.length() - 1) + 1); + checksum = (checksum + 1) & 0xFF; + field.setObject(value); + assertEquals("12=" + value, field.toString()); + assertEquals(checksum, field.getChecksum()); + assertEquals(length, field.getLength()); + + field.setTag(13); + checksum = (checksum + 1) & 0xFF; + assertEquals("13=" + value, field.toString()); + assertEquals(checksum, field.getChecksum()); + assertEquals(length, field.getLength()); + } + + @Test + public void testFieldCalculationsWithDefaultCharset() { + testFieldCalcuations("VALUE", 30, 9); + } + + @Test + public void testFieldCalculationsWithUTF8Charset() throws UnsupportedEncodingException { + CharsetSupport.setCharset("UTF-8"); + try { + testFieldCalcuations("\u6D4B\u9A8C\u6570\u636E", 50, 16); + } finally { + CharsetSupport.setDefaultCharset(); + } + } + + @Test + public void testDateField() { + DateField field = new DateField(11); + Date date = new Date(); + field.setValue(date); + assertEquals(11, field.getTag()); + assertEquals(date, field.getValue()); + field = new DateField(11, date); + assertEquals(11, field.getTag()); + assertEquals(date, field.getValue()); + } + + @Test + public void testUtcDateOnlyField() { + UtcDateOnlyField field = new UtcDateOnlyField(11); + LocalDate date = LocalDate.now(); + field.setValue(date); + assertEquals(11, field.getTag()); + assertEquals(date, field.getValue()); + field = new UtcDateOnlyField(11, date); + assertEquals(11, field.getTag()); + assertEquals(date, field.getValue()); + } + + @Test + public void testUtcTimeOnlyField() { + UtcTimeOnlyField field = new UtcTimeOnlyField(11); + LocalTime date = LocalTime.now(); + field.setValue(date); + assertEquals(11, field.getTag()); + assertEquals(date, field.getValue()); + field = new UtcTimeOnlyField(11, date); + assertEquals(11, field.getTag()); + assertEquals(date, field.getValue()); + } + + @Test + public void testUtcTimeStampField() { + UtcTimeStampField field = new UtcTimeStampField(11); + LocalDateTime date = LocalDateTime.now(); + field.setValue(date); + assertEquals(11, field.getTag()); + assertEquals(date, field.getValue()); + field = new UtcTimeStampField(11, date); + assertEquals(11, field.getTag()); + assertEquals(date, field.getValue()); + } + + @Test + public void testBooleanField() { + BooleanField field = new BooleanField(11); + field.setValue(true); + assertEquals(11, field.getTag()); + assertTrue(field.getValue()); + field.setValue(Boolean.FALSE); + assertEquals(11, field.getTag()); + assertFalse(field.getValue()); + field = new BooleanField(22, true); + assertEquals(22, field.getTag()); + assertTrue(field.getValue()); + field = new BooleanField(33, Boolean.TRUE); + assertEquals(33, field.getTag()); + assertTrue(field.getValue()); + } + + @Test + public void testDoubleField() { + DoubleField field = new DoubleField(11); + field.setValue(12.3); + assertEquals(11, field.getTag()); + assertEquals(12.3, field.getValue(), 0); + field.setValue(new Double(23.4)); + assertEquals(11, field.getTag()); + assertEquals(23.4, field.getValue(), 0); + field = new DoubleField(22, 34.5); + assertEquals(22, field.getTag()); + assertEquals(34.5, field.getValue(), 0); + field = new DoubleField(33, new Double(45.6)); + assertEquals(33, field.getTag()); + assertEquals(45.6, field.getValue(), 0); + } + + @Test(expected = NumberFormatException.class) + public void testDoubleFieldException() { + DoubleField field = new DoubleField(11, Double.NaN); + } + + @Test + public void testDecimalField() { + DecimalField field = new DecimalField(11); + field.setValue(12.3); + assertEquals(11, field.getTag()); + assertEquals(BigDecimal.valueOf(12.3), field.getValue()); + field.setValue(23.4); + assertEquals(11, field.getTag()); + assertEquals(BigDecimal.valueOf(23.4), field.getValue()); + field = new DecimalField(22, 34.5); + assertEquals(22, field.getTag()); + assertEquals(BigDecimal.valueOf(34.5), field.getValue()); + field = new DecimalField(33, 45.6); + assertEquals(33, field.getTag()); + assertEquals(BigDecimal.valueOf(45.6), field.getValue()); + } + + @Test(expected = NumberFormatException.class) + public void testDecimalFieldException() { + DecimalField field = new DecimalField(11, Double.POSITIVE_INFINITY); + } + + @Test + public void testCharField() { + CharField field = new CharField(11); + field.setValue('x'); + assertEquals(11, field.getTag()); + assertEquals('x', field.getValue()); + field.setValue(Character.valueOf('X')); + assertEquals(11, field.getTag()); + assertEquals('X', field.getValue()); + field = new CharField(22, 'a'); + assertEquals(22, field.getTag()); + assertEquals('a', field.getValue()); + field = new CharField(33, Character.valueOf('A')); + assertEquals(33, field.getTag()); + assertEquals('A', field.getValue()); + } + + @Test + public void testIntField() { + IntField field = new IntField(11); + field.setValue(12); + assertEquals(11, field.getTag()); + assertEquals(12, field.getValue()); + field.setValue(Integer.valueOf(23)); + assertEquals(11, field.getTag()); + assertEquals(23, field.getValue()); + field = new IntField(22, 23); + assertEquals(22, field.getTag()); + assertEquals(23, field.getValue()); + field = new IntField(33, Integer.valueOf(44)); + assertEquals(33, field.getTag()); + assertEquals(44, field.getValue()); + } + @Test public void testBytesField() { byte[] data = "rawdata".getBytes(); diff --git a/quickfixj-core/src/test/java/quickfix/FileStoreTest.java b/quickfixj-core/src/test/java/quickfix/FileStoreTest.java index 8612368964..08b09c25dd 100644 --- a/quickfixj-core/src/test/java/quickfix/FileStoreTest.java +++ b/quickfixj-core/src/test/java/quickfix/FileStoreTest.java @@ -19,6 +19,8 @@ package quickfix; +import org.quickfixj.CharsetSupport; + import java.io.IOException; import java.util.ArrayList; import java.util.Date; @@ -26,8 +28,9 @@ public class FileStoreTest extends AbstractMessageStoreTest { - protected void tearDown() throws Exception { + public void tearDown() throws Exception { super.tearDown(); + CharsetSupport.setDefaultCharset(); FileStore fileStore = (FileStore) getStore(); try { fileStore.closeAndDeleteFiles(); @@ -36,6 +39,7 @@ protected void tearDown() throws Exception { } } + @Override protected MessageStoreFactory getMessageStoreFactory() throws ConfigError, FieldConvertError { SessionSettings settings = new SessionSettings(getConfigurationFileName()); // Initialize the session settings from the defaults @@ -44,6 +48,7 @@ protected MessageStoreFactory getMessageStoreFactory() throws ConfigError, Field return new FileStoreFactory(settings); } + @Override protected Class getMessageStoreClass() { return FileStore.class; } diff --git a/quickfixj-core/src/test/java/quickfix/MessageTest.java b/quickfixj-core/src/test/java/quickfix/MessageTest.java index 0d3613cae3..194f4d39bb 100644 --- a/quickfixj-core/src/test/java/quickfix/MessageTest.java +++ b/quickfixj-core/src/test/java/quickfix/MessageTest.java @@ -189,7 +189,7 @@ private void doTestMessageWithEncodedField(String charset, String text) throws E final Message msg = new Message(order.toString(), DataDictionaryTest.getDictionary()); assertEquals(charset + " encoded field", text, msg.getString(EncodedText.FIELD)); } finally { - CharsetSupport.setCharset(CharsetSupport.getDefaultCharset()); + CharsetSupport.setDefaultCharset(); } } diff --git a/quickfixj-core/src/test/java/quickfix/SleepycatStoreTest.java b/quickfixj-core/src/test/java/quickfix/SleepycatStoreTest.java index ec84e6a9b3..fd3bf23808 100644 --- a/quickfixj-core/src/test/java/quickfix/SleepycatStoreTest.java +++ b/quickfixj-core/src/test/java/quickfix/SleepycatStoreTest.java @@ -23,7 +23,8 @@ import java.io.IOException; public class SleepycatStoreTest extends AbstractMessageStoreTest { - protected MessageStoreFactory getMessageStoreFactory() throws ConfigError, FieldConvertError { + + protected MessageStoreFactory getMessageStoreFactory() throws ConfigError { SessionSettings settings = new SessionSettings(getConfigurationFileName()); File tmpfile; try { @@ -62,5 +63,4 @@ public void testCloseAndOpen() throws Exception { assertEquals(123, store.getNextSenderMsgSeqNum()); assertEquals(321, store.getNextTargetMsgSeqNum()); } - } diff --git a/quickfixj-core/src/test/java/quickfix/mina/message/FIXMessageDecoderTest.java b/quickfixj-core/src/test/java/quickfix/mina/message/FIXMessageDecoderTest.java index aa1f856bcc..8d56eb1259 100644 --- a/quickfixj-core/src/test/java/quickfix/mina/message/FIXMessageDecoderTest.java +++ b/quickfixj-core/src/test/java/quickfix/mina/message/FIXMessageDecoderTest.java @@ -55,6 +55,7 @@ public class FIXMessageDecoderTest { @Before public void setUp() throws Exception { + CharsetSupport.setDefaultCharset(); decoder = new FIXMessageDecoder(); buffer = IoBuffer.allocate(8192); decoderOutput = new ProtocolDecoderOutputForTest(); @@ -62,17 +63,13 @@ public void setUp() throws Exception { @After public void tearDown() throws Exception { + CharsetSupport.setDefaultCharset(); buffer.clear(); } - @Test + @Test(expected = UnsupportedEncodingException.class) public void testInvalidStringCharset() throws Exception { - try { - decoder = new FIXMessageDecoder("BOGUS"); - fail("no exception thrown"); - } catch (UnsupportedEncodingException e) { - // expected - } + decoder = new FIXMessageDecoder("BOGUS"); } @Test @@ -104,8 +101,6 @@ public void testWesternEuropeanDecoding() throws Exception { doWesternEuropeanDecodingTest(); } catch (InvalidMessage e) { // expected - } finally { - CharsetSupport.setCharset(CharsetSupport.getDefaultCharset()); } } diff --git a/quickfixj-core/src/test/java/quickfix/mina/message/FIXMessageEncoderTest.java b/quickfixj-core/src/test/java/quickfix/mina/message/FIXMessageEncoderTest.java index 204ef60b51..b36573dbba 100644 --- a/quickfixj-core/src/test/java/quickfix/mina/message/FIXMessageEncoderTest.java +++ b/quickfixj-core/src/test/java/quickfix/mina/message/FIXMessageEncoderTest.java @@ -19,12 +19,13 @@ package quickfix.mina.message; -import java.io.UnsupportedEncodingException; +import org.apache.mina.filter.codec.ProtocolCodecException; -import junit.framework.ComparisonFailure; -import junit.framework.TestCase; +import org.junit.After; +import org.junit.Before; +import org.junit.ComparisonFailure; +import org.junit.Test; -import org.apache.mina.filter.codec.ProtocolCodecException; import org.quickfixj.CharsetSupport; import quickfix.Message; @@ -34,13 +35,25 @@ import quickfix.fix40.Logon; import quickfix.fix44.News; -public class FIXMessageEncoderTest extends TestCase { +import java.io.UnsupportedEncodingException; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +public class FIXMessageEncoderTest { + @Before + public void setUp() throws UnsupportedEncodingException { + CharsetSupport.setDefaultCharset(); + } + + @After public void tearDown() throws UnsupportedEncodingException { - // reset charset after every test - CharsetSupport.setCharset(CharsetSupport.getDefaultCharset()); + CharsetSupport.setDefaultCharset(); } + @Test public void testEncoding() throws Exception { FIXMessageEncoder encoder = new FIXMessageEncoder(); Message message = new Logon(); @@ -51,6 +64,7 @@ public void testEncoding() throws Exception { assertTrue(protocolEncoderOutputForTest.buffer.limit() > 0); } + @Test public void testWesternEuropeanEncoding() throws Exception { String headline = "\u00E4bcf\u00F6d\u00E7\u00E9"; @@ -67,6 +81,7 @@ public void testWesternEuropeanEncoding() throws Exception { } } + @Test public void testChineseEncoding() throws Exception { String headline = "\u6D4B\u9A8C\u6570\u636E"; @@ -96,16 +111,13 @@ private void doEncodingTest(String headline) throws ProtocolCodecException, Unsu assertEquals("wrong encoding", new String(bytes, CharsetSupport.getCharset()), news.toString()); } + @Test(expected = ProtocolCodecException.class) public void testEncodingBadType() throws Exception { FIXMessageEncoder encoder = new FIXMessageEncoder(); - try { - encoder.encode(null, new Object(), new ProtocolEncoderOutputForTest()); - fail("expected exception"); - } catch (ProtocolCodecException e) { - // expected - } + encoder.encode(null, new Object(), new ProtocolEncoderOutputForTest()); } + @Test public void testEncodingStringEnglish() throws Exception { FIXMessageEncoder encoder = new FIXMessageEncoder(); ProtocolEncoderOutputForTest protocolEncoderOutputForTest = new ProtocolEncoderOutputForTest(); @@ -113,6 +125,7 @@ public void testEncodingStringEnglish() throws Exception { assertEquals(4, protocolEncoderOutputForTest.buffer.limit()); } + @Test public void testEncodingStringChinese() throws Exception { CharsetSupport.setCharset("UTF-8"); FIXMessageEncoder encoder = new FIXMessageEncoder(); @@ -120,5 +133,4 @@ public void testEncodingStringChinese() throws Exception { encoder.encode(null, "\u6D4B\u9A8C\u6570\u636E", protocolEncoderOutputForTest); assertEquals(12, protocolEncoderOutputForTest.buffer.limit()); } - }