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    }