001 /**
002 * Copyright 2007 Mike Kroutikov.
003 *
004 * This program is free software; you can redistribute it and/or modify
005 * it under the terms of the Lesser GNU General Public License as
006 * published by the Free Software Foundation; either version 3 of
007 * the License, or (at your option) any later version.
008 *
009 * This program is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
012 * Lesser GNU General Public License for more details.
013 *
014 * You should have received a copy of the Lesser GNU General Public License
015 * along with this program. If not, see <http://www.gnu.org/licenses/>.
016 */
017
018 package org.otfeed.protocol.request;
019
020 import org.otfeed.command.AggregationSpan;
021 import org.otfeed.command.BookDeleteTypeEnum;
022 import org.otfeed.command.ListSymbolEnum;
023 import org.otfeed.command.OptionTypeEnum;
024 import org.otfeed.command.ListSymbolsCommand.MatchStyleEnum;
025 import org.otfeed.event.DividendPropertyEnum;
026 import org.otfeed.event.InstrumentEnum;
027 import org.otfeed.event.OTError;
028 import org.otfeed.event.TradePropertyEnum;
029 import org.otfeed.event.TradeSideEnum;
030 import org.otfeed.protocol.CommandEnum;
031 import org.otfeed.protocol.ErrorEnum;
032 import org.otfeed.protocol.MessageEnum;
033 import org.otfeed.protocol.ProtocolException;
034 import org.otfeed.protocol.StatusEnum;
035
036 import java.util.Date;
037 import java.util.EnumSet;
038 import java.util.Set;
039 import java.nio.ByteBuffer;
040 import java.nio.charset.Charset;
041
042 /**
043 * Serialization and other utility functions.
044 */
045 public final class Util {
046
047 private static final Charset UTF8 = Charset.forName("utf-8");
048
049 private Util() { }
050
051 /**
052 * Writes a variable-length string to buffer.
053 * Internal format is a two-byte little-endian "short"
054 * specifying the length, followed by utf-8 encoded bytes.
055 */
056 public static final void writeString(ByteBuffer out, String val) {
057 ByteBuffer encoded = UTF8.encode(val);
058 out.putShort((short) encoded.limit());
059 out.put(encoded);
060 }
061
062 /**
063 * Reads variable-length string.
064 */
065 public static final String readString(ByteBuffer in) {
066 int len = in.getShort();
067
068 ByteBuffer encoded = in.duplicate();
069 in.position(in.position() + len);
070
071 encoded.limit(encoded.position() + len);
072
073 return UTF8.decode(encoded).toString().trim();
074 }
075
076
077 /**
078 * Writes a fixed-length string (padded by binary zeroes if needed).
079 */
080 public static final void writeString(ByteBuffer out, String val, int len) {
081 ByteBuffer encoded = UTF8.encode(val);
082
083 if(encoded.limit() >= len) {
084 encoded.limit(len);
085 out.put(encoded);
086 } else {
087 int padding = len - encoded.limit();
088
089 out.put(encoded);
090
091 while(padding-- > 0) {
092 out.put((byte) 0);
093 }
094 }
095 }
096
097 /**
098 * Reads a fixed-length string.
099 */
100 public static final String readString(ByteBuffer in, int len) {
101
102 ByteBuffer encoded = in.duplicate();
103 in.position(in.position() + len);
104
105 while(len > 0) {
106 if(encoded.get(encoded.position() + len - 1) == 0) {
107 len--;
108 } else {
109 break;
110 }
111 }
112 encoded.limit(encoded.position() + len);
113
114 return UTF8.decode(encoded).toString().trim();
115 }
116
117 public static final OTError readError(int reqID, ByteBuffer in) {
118
119 short errorCode = in.getShort();
120 String reason = Util.readString(in);
121
122 return new OTError(reqID, errorCode, reason);
123 }
124
125 public static final OTError newError(ErrorEnum code, String message) {
126 return new OTError(0, code.code, message);
127 }
128
129 public static final OTError newError(String message) {
130 return new OTError(0, ErrorEnum.E_OTFEED_INTERNAL.code, message);
131 }
132
133 public static final Date readDate(ByteBuffer in) {
134 int stamp = in.getInt();
135
136 if(stamp == 0) return null;
137
138 return new Date(1000L * stamp);
139 }
140
141 public static final void writeDate(ByteBuffer out, Date date) {
142 if(date == null) {
143 out.putInt(0);
144 } else {
145 int stamp = (int) (date.getTime() / 1000L);
146 out.putInt(stamp);
147 }
148 }
149
150 public static final boolean readBoolean(ByteBuffer in) {
151 int c;
152 // not sure whether this is supposed to be 'Y' or '1'
153 // .NET driver uses 'Y', Java and C++ use '1'.
154 // untill known, let me be very suspicious here
155 //
156 // well, testing showed that
157 // book events use Y/N, others use 1/0. Therefore have to
158 // support both types.
159 c = in.get();
160 if(c == 0) {
161 return false;
162 } else if(c == 1) {
163 return true;
164 } else if(c == 'N') {
165 return false;
166 } else if(c == 'Y') {
167 return true;
168 } else {
169 throw new ProtocolException("boolean format is incorrect: " + ((char) c), in);
170 }
171 }
172
173 public static final TradeSideEnum readTradeSideEnum(ByteBuffer in) {
174 char sideCode = (char) in.get();
175 if(sideCode == 'B') {
176 return TradeSideEnum.BUYER;
177 } else if(sideCode == 'S') {
178 return TradeSideEnum.SELLER;
179 } else {
180 throw new ProtocolException("unexpected side code: " + sideCode, in);
181 }
182 }
183
184 public static final OptionTypeEnum readOptionTypeEnum(ByteBuffer in) {
185 char code = (char) in.get();
186 if(code == 'A') {
187 return OptionTypeEnum.AMERICAN;
188 } else if(code == 'E') {
189 return OptionTypeEnum.EUROPEAN;
190 } else if(code == 'C') {
191 return OptionTypeEnum.CAPPED;
192 } else {
193 throw new ProtocolException("unexpected option type code: " + code, in);
194 }
195 }
196
197 public static final InstrumentEnum readInstrumentEnum(ByteBuffer in) {
198
199 int code = in.get();
200 InstrumentEnum i = InstrumentEnum.decoder.get(code);
201 if(i == null) {
202 throw new ProtocolException("unknown instrument code: " + code, in);
203 }
204
205 return i;
206 }
207
208 public static final BookDeleteTypeEnum readBookDeleteTypeEnum(ByteBuffer in) {
209 int code = in.get();
210
211 BookDeleteTypeEnum deleteType = BookDeleteTypeEnum.decoder.get(code);
212 if(deleteType == null) {
213 throw new ProtocolException("unknown delete type code: " + code, in);
214 }
215
216 return deleteType;
217 }
218
219 public static final StatusEnum readStatusEnum(ByteBuffer in) {
220 int code = in.get();
221
222 StatusEnum status = StatusEnum.decode(code);
223 if(status == null) {
224 throw new ProtocolException("unknown status code: " + code, in);
225 }
226 return status;
227 }
228
229 public static final MessageEnum readMessageEnum(ByteBuffer in) {
230 int code = in.get();
231
232 MessageEnum mess = MessageEnum.decoder.get(code);
233 if(mess == null) {
234 throw new ProtocolException("unknown message type code: " + code, in);
235 }
236 return mess;
237 }
238
239 public static final CommandEnum readCommandEnum(ByteBuffer in) {
240 int code = in.getInt();
241
242 CommandEnum command = CommandEnum.decoder.get(code);
243 if(command == null) {
244 throw new ProtocolException("unknown command code: " + code, in);
245 }
246 return command;
247 }
248
249 private static final Set<TradePropertyEnum> TRADE_PROPERTY_ENUM_ALL
250 = EnumSet.allOf(TradePropertyEnum.class);
251
252 public static final Set<TradePropertyEnum> readTradePropertySet(ByteBuffer in) {
253 int mask = in.get();
254 if(mask < 0) mask += 256; // unsigned byte
255
256 Set<TradePropertyEnum> set = EnumSet.noneOf(TradePropertyEnum.class);
257
258 for(TradePropertyEnum p : TRADE_PROPERTY_ENUM_ALL) {
259 if((mask & p.code) != 0) {
260 set.add(p);
261 mask ^= p.code;
262 }
263 }
264
265 if(mask != 0) {
266 throw new ProtocolException("extra bits in the trade properties mask: " + mask, in);
267 }
268
269 return set;
270 }
271
272 private final static Set<DividendPropertyEnum> DIVIDEND_PROPERTY_ENUM_ALL
273 = EnumSet.allOf(DividendPropertyEnum.class);
274
275 public static final Set<DividendPropertyEnum> readDividendProperrtyEnumSet(ByteBuffer in) {
276 int mask = in.getShort();
277
278 Set<DividendPropertyEnum> set = EnumSet.noneOf(DividendPropertyEnum.class);
279
280 for(DividendPropertyEnum p : DIVIDEND_PROPERTY_ENUM_ALL) {
281 if((mask & p.code) != 0) {
282 set.add(p);
283 mask ^= p.code;
284 }
285 }
286
287 //
288 // mpk: for "N/PG" server returns 0x3030, which leaves unexplained
289 // bits of 0x3000...
290 //
291 // if(mask != 0) {
292 // throw new ProtocolException("unhandled bits in the dividend property mask: 0x" + Integer.toHexString(mask), in);
293 // }
294
295 return set;
296 }
297
298 public static final void writeListSymbolMask(ByteBuffer out, Set<ListSymbolEnum> types, MatchStyleEnum matchStyle) {
299
300 int mask = 0;
301 for(ListSymbolEnum t : types) {
302 mask |= t.code;
303 }
304
305 switch(matchStyle) {
306 case PREFIX:
307 break;
308 case CONTAINS:
309 mask |= ListSymbolEnum.CONTAINS_FLAG;
310 break;
311 default:
312 throw new IllegalArgumentException("unexpected match type");
313 }
314
315 out.putInt(mask);
316 }
317
318 public static final void writeAggreagationSpan(ByteBuffer out, AggregationSpan span) {
319 out.put((byte) span.units.code);
320 out.put((byte) 0);
321 out.putShort((short) span.length);
322 }
323 }