/* * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.f1x.v1; import org.f1x.api.message.AppendableValue; import org.f1x.api.message.MessageBuilder; import org.f1x.api.message.fields.MsgType; import org.f1x.api.message.types.ByteEnum; import org.f1x.api.message.types.IntEnum; import org.f1x.api.message.types.StringEnum; import org.f1x.util.ByteArrayReference; import org.f1x.util.format.*; /** * Simple implementation of MessageBuilder that collects all fields in fixed size byte array. */ public final class ByteBufferMessageBuilder implements MessageBuilder, AppendableValue { private static final byte BYTE_Y = (byte) 'Y'; private static final byte BYTE_N = (byte) 'N'; private static final byte SOH = 1; // field separator public static final String NULL = "null"; private final TimestampFormatter gmtTimestampFormat = TimestampFormatter.createUTCTimestampFormatter(); private final TimestampFormatter localTimestampFormat = TimestampFormatter.createLocalTimestampFormatter(); private final DoubleFormatter doubleFormatter; private CharSequence msgType; private final byte [] buffer; private int offset; public ByteBufferMessageBuilder (int maxLength, int doubleFormatterPrecision) { buffer = new byte[maxLength]; doubleFormatter = new DoubleFormatter(doubleFormatterPrecision); } public ByteBufferMessageBuilder (byte[] buff, int doubleFormatterPrecision) { buffer = buff; doubleFormatter = new DoubleFormatter(doubleFormatterPrecision); } @Override public void clear() { offset = 0; } @Override public void setMessageType(MsgType msgType) { this.msgType = msgType.getCode(); } @Override public void setMessageType(CharSequence msgType) { checkValue(35, msgType); this.msgType = msgType; } @Override public CharSequence getMessageType() { return msgType; } @Override public void add(int tagNo, final CharSequence value) { checkValue(tagNo, value); offset = IntFormatter.format(tagNo, buffer, offset); buffer[offset++] = '='; offset = CharSequenceFormatter.format(value, buffer, offset); buffer[offset++] = SOH; } @Override public void add(int tagNo, CharSequence value, int start, int end) { checkValue(tagNo, value, start, end); offset = IntFormatter.format(tagNo, buffer, offset); buffer[offset++] = '='; offset = CharSequenceFormatter.format(value, start, end, buffer, offset); buffer[offset++] = SOH; } @Override public void add(int tagNo, long value) { offset = LongFormatter.format(tagNo, buffer, offset); buffer[offset++] = '='; offset = LongFormatter.format(value, buffer, offset); buffer[offset++] = SOH; } @Override public void add(int tagNo, int value) { offset = IntFormatter.format(tagNo, buffer, offset); buffer[offset++] = '='; offset = IntFormatter.format(value, buffer, offset); buffer[offset++] = SOH; } @Override public void add(int tagNo, double value) { offset = IntFormatter.format(tagNo, buffer, offset); buffer[offset++] = '='; offset = doubleFormatter.format(value, buffer, offset); buffer[offset++] = SOH; } @Override public void add(int tagNo, double value, int precision) { offset = IntFormatter.format(tagNo, buffer, offset); buffer[offset++] = '='; offset = doubleFormatter.format(value, precision, buffer, offset); buffer[offset++] = SOH; } @Override public void add(int tagNo, double value, int precision, boolean roundUp) { offset = IntFormatter.format(tagNo, buffer, offset); buffer[offset++] = '='; offset = doubleFormatter.format(value, precision, roundUp, DoubleFormatter.MAX_WIDTH, buffer, offset); buffer[offset++] = SOH; } @Override public void add(int tagNo, byte value) { offset = IntFormatter.format(tagNo, buffer, offset); buffer[offset++] = '='; buffer[offset++] = value; buffer[offset++] = SOH; } @Override public void add(int tagNo, boolean value) { offset = IntFormatter.format(tagNo, buffer, offset); buffer[offset++] = '='; buffer[offset++] = (value) ? BYTE_Y : BYTE_N; buffer[offset++] = SOH; } @Override public void addUTCTimestamp(int tagNo, long timestamp) { offset = IntFormatter.format(tagNo, buffer, offset); buffer[offset++] = '='; offset = gmtTimestampFormat.formatDateTime(timestamp, buffer, offset); buffer[offset++] = SOH; } @Override public void addUTCDateOnly(int tagNo, long timestamp) { offset = IntFormatter.format(tagNo, buffer, offset); buffer[offset++] = '='; offset = gmtTimestampFormat.formatDateOnly(timestamp, buffer, offset); buffer[offset++] = SOH; } @Override public void addLocalMktDate(int tagNo, long timestamp) { offset = IntFormatter.format(tagNo, buffer, offset); buffer[offset++] = '='; offset = localTimestampFormat.formatDateOnly(timestamp, buffer, offset); buffer[offset++] = SOH; } @Override public void addLocalMktDate2(int tagNo, int yyyymmdd) { offset = IntFormatter.format(tagNo, buffer, offset); buffer[offset++] = '='; offset = IntFormatter.format4digits(yyyymmdd / 10000, buffer, offset); // year int mmdd = yyyymmdd % 10000; offset = IntFormatter.format2digits(mmdd / 100, buffer, offset); // month offset = IntFormatter.format2digits(mmdd % 100, buffer, offset); // day buffer[offset++] = SOH; } @Override public void addUTCTimeOnly(int tagNo, long timestamp) { offset = IntFormatter.format(tagNo, buffer, offset); buffer[offset++] = '='; offset = TimeOfDayFormatter.format(timestamp, buffer, offset); buffer[offset++] = SOH; } @Override public void addRaw(int tagNo, byte[] sourceBuffer, int sourceOffset, int sourceLength) { checkValue(tagNo, sourceBuffer, sourceOffset, sourceLength); offset = IntFormatter.format(tagNo, buffer, offset); buffer[offset++] = '='; System.arraycopy(sourceBuffer, sourceOffset, buffer, offset, sourceLength); offset += sourceLength; buffer[offset++] = SOH; } @Override public void addRaw(int tagNo, ByteArrayReference bytes) { checkValue(bytes); offset = IntFormatter.format(tagNo, buffer, offset); buffer[offset++] = '='; offset += bytes.copyTo(buffer, offset); buffer[offset++] = SOH; } @Override public AppendableValue add(int tagNo) { offset = IntFormatter.format(tagNo, buffer, offset); buffer[offset++] = '='; return this; } @Override public void add(int tag, ByteEnum value) { add (tag, value.getCode()); } @Override public void add(int tag, IntEnum value) { add (tag, value.getCode()); } @Override public void add(int tag, StringEnum value) { byte [] valueAsBytes = value.getBytes(); addRaw (tag, valueAsBytes, 0, valueAsBytes.length); } @Override public int output(byte[] buffer, int offset) { if (this.offset > buffer.length - offset) throw new IndexOutOfBoundsException("Output FIX message exceeds maximum size"); System.arraycopy(this.buffer, 0, buffer, offset, this.offset); return offset+this.offset; } @Override public int getLength() { return offset; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // AppendableValue @Override public AppendableValue append(CharSequence csq) { if (csq != null) offset = CharSequenceFormatter.format(csq, buffer, offset); else append(NULL); // complies with Appendable interface return this; } @Override public AppendableValue append(CharSequence csq, int start, int end) { if (csq != null) offset = CharSequenceFormatter.format(csq, start, end, buffer, offset); else append(NULL); // complies with Appendable interface return this; } @Override public AppendableValue append(char c) { if ((c & 0xFFFFFF00) != 0) throw new IllegalArgumentException("ASCII only"); buffer[offset++] = (byte) c; return this; } @Override public AppendableValue append(byte b) { buffer[offset++] = b; return this; } @Override public AppendableValue append(int value) { offset = IntFormatter.format(value, buffer, offset); return this; } @Override public AppendableValue append(long value) { offset = LongFormatter.format(value, buffer, offset); return this; } @Override public AppendableValue append(double value) { offset = doubleFormatter.format(value, buffer, offset); return this; } @Override public void end() { buffer[offset++] = SOH; } private static void checkValue(int tagNo, CharSequence value) { if (value == null) throw new NullPointerException("value is null for tag " + tagNo); if (value.length() == 0) throw new IllegalArgumentException("empty value for tag " + tagNo); } private static void checkValue(int tagNo, CharSequence value, int start, int end) { if (value == null) throw new NullPointerException("value is null for tag " + tagNo); int valueLength = value.length(); if (start < 0 || valueLength <= start) throw new IllegalArgumentException("invalid start index: " + start); if (end < 1 || valueLength < end) throw new IllegalArgumentException("invalid end index: " + end); int length = end - start; if (length < 1) throw new IllegalArgumentException("bad length for tag " + tagNo); } private static void checkValue(int tagNo, byte[] buffer, int offset, int length) { if (buffer == null) throw new NullPointerException("value buffer is null for tag " + tagNo); if (offset < 0 || buffer.length <= offset) throw new IllegalArgumentException("invalid offset: " + offset); if (length < 1) throw new IllegalArgumentException("invalid length: " + length); if (offset + length > buffer.length) throw new IllegalArgumentException("offset + length > length of buffer"); } private static void checkValue(ByteArrayReference value) { if (value == null) throw new NullPointerException("value is null"); if (value.length() == 0) throw new IllegalArgumentException("empty value"); } }