001// ***************************************************************************************************************************
002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
003// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
005// * with the License.  You may obtain a copy of the License at                                                              *
006// *                                                                                                                         *
007// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
008// *                                                                                                                         *
009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
011// * specific language governing permissions and limitations under the License.                                              *
012// ***************************************************************************************************************************
013package org.apache.juneau.msgpack;
014
015import static org.apache.juneau.msgpack.DataType.*;
016
017import java.io.*;
018import java.math.*;
019import java.util.concurrent.atomic.*;
020
021/**
022 * Specialized output stream for serializing MessagePack streams.
023 *
024 * <h5 class='section'>Notes:</h5>
025 * <ul class='spaced-list'>
026 *    <li>
027 *       This class is not intended for external use.
028 * </ul>
029 */
030public final class MsgPackOutputStream extends OutputStream {
031
032   private final OutputStream os;
033
034   /**
035    * Constructor.
036    *
037    * @param os The output stream being wrapped.
038    */
039   protected MsgPackOutputStream(OutputStream os) {
040      this.os = os;
041   }
042
043   @Override /* OutputStream */
044   public void write(int b) throws IOException {
045      os.write(b);
046   }
047
048   /**
049    * Same as {@link #write(int)}.
050    */
051   final MsgPackOutputStream append(byte b) throws IOException {
052      os.write(b);
053      return this;
054   }
055
056   /**
057    * Same as {@link #write(byte[])}.
058    */
059   final MsgPackOutputStream append(byte[] b) throws IOException {
060      os.write(b);
061      return this;
062   }
063
064   /**
065    * Appends one byte to the stream.
066    */
067   final MsgPackOutputStream append1(int i) throws IOException {
068      os.write(i);
069      return this;
070   }
071
072   /**
073    * Appends two bytes to the stream.
074    */
075   final MsgPackOutputStream append2(int i) throws IOException {
076      return append1(i>>8).append1(i);
077   }
078
079   /**
080    * Appends four bytes to the stream.
081    */
082   final MsgPackOutputStream append4(int i) throws IOException {
083      return append1(i>>24).append1(i>>16).append1(i>>8).append1(i);
084   }
085
086   /**
087    * Appends eight bytes to the stream.
088    */
089   final MsgPackOutputStream append8(long l) throws IOException {
090      return append1((int)(l>>56)).append1((int)(l>>48)).append1((int)(l>>40)).append1((int)(l>>32)).append1((int)(l>>24)).append1((int)(l>>16)).append1((int)(l>>8)).append1((int)(l));
091   }
092
093   /**
094    * Appends a NULL flag to the stream.
095    */
096   final MsgPackOutputStream appendNull() throws IOException {
097      return append1(NIL);
098   }
099
100   /**
101    * Appends a boolean to the stream.
102    */
103   final MsgPackOutputStream appendBoolean(boolean b) throws IOException {
104      return append1(b ? TRUE : FALSE);
105   }
106
107   /**
108    * Appends an integer to the stream.
109    */
110   final MsgPackOutputStream appendInt(int i) throws IOException {
111      // POSFIXINT_L  = 0x00,  //   pos fixint     0xxxxxxx     0x00 - 0x7f
112      // POSFIXINT_U  = 0x7F,
113      // UINT8        = 0xCC,  //   uint 8         11001100     0xcc
114      // UINT16       = 0xCD,  //   uint 16        11001101     0xcd
115      // UINT32       = 0xCE,  //   uint 32        11001110     0xce
116      // UINT64       = 0xCF,  //   uint 64        11001111     0xcf
117      // INT8         = 0xD0,  //   int 8          11010000     0xd0
118      // INT16        = 0xD1,  //   int 16         11010001     0xd1
119      // INT32        = 0xD2,  //   int 32         11010010     0xd2
120      // INT64        = 0xD3,  //   int 64         11010011     0xd3
121      // NEGFIXINT_L  = 0xE0,  //   neg fixint     111xxxxx     0xe0 - 0xff
122      // NEGFIXINT_U  = 0xFF;
123      if (i >= 0) {
124         if (i < (1<<7))
125            return append1(i);
126         if (i < (1<<15))
127            return append1(INT16).append2(i);
128         return append1(INT32).append4(i);
129      }
130      if (i > -(1<<6))
131         return append((byte)(0xE0 | -i));
132      if (i > -(1<<7))
133         return append1(INT8).append1(i);
134      if (i > -(1<<15))
135         return append1(INT16).append2(i);
136      return append1(INT32).append4(i);
137   }
138
139   final long L2X31 = ((long)(1<<30))*2;
140
141   /**
142    * Appends a long to the stream.
143    */
144   final MsgPackOutputStream appendLong(long l) throws IOException {
145      if (l < L2X31 && l > -(L2X31))
146         return appendInt((int)l);
147      return append1(INT64).append8(l);
148   }
149
150   /**
151    * Appends a generic Number to the stream.
152    */
153   final MsgPackOutputStream appendNumber(Number n) throws IOException {
154      Class<?> c = n.getClass();
155      if (c == Integer.class || c == Short.class || c == Byte.class || c == AtomicInteger.class)
156         return appendInt(n.intValue());
157      if (c == Long.class || c == AtomicLong.class)
158         return appendLong(n.longValue());
159      if (c == Float.class)
160         return appendFloat(n.floatValue());
161      if (c == Double.class)
162         return appendDouble(n.doubleValue());
163      if (c == BigInteger.class)
164         return appendLong(n.longValue());
165      if (c == BigDecimal.class)
166         return appendDouble(n.doubleValue());
167      return appendInt(0);
168   }
169
170   /**
171    * Appends a float to the stream.
172    */
173   final MsgPackOutputStream appendFloat(float f) throws IOException {
174      // FLOAT32      = 0xCA,  //   float 32       11001010     0xca
175      return append1(FLOAT32).append4(Float.floatToIntBits(f));
176
177   }
178
179   /**
180    * Appends a double to the stream.
181    */
182   final MsgPackOutputStream appendDouble(double d) throws IOException {
183      // FLOAT64      = 0xCB,  //   float 64       11001011     0xcb
184      return append1(FLOAT64).append8(Double.doubleToLongBits(d));
185   }
186
187   /**
188    * Appends a string to the stream.
189    */
190   final MsgPackOutputStream appendString(CharSequence cs) throws IOException {
191
192      // fixstr stores a byte array whose length is up to 31 bytes:
193      // +--------+========+
194      // |101XXXXX|  data  |
195      // +--------+========+
196      //
197      // str 8 stores a byte array whose length is up to (2^8)-1 bytes:
198      // +--------+--------+========+
199      // |  0xd9  |YYYYYYYY|  data  |
200      // +--------+--------+========+
201      //
202      // str 16 stores a byte array whose length is up to (2^16)-1 bytes:
203      // +--------+--------+--------+========+
204      // |  0xda  |ZZZZZZZZ|ZZZZZZZZ|  data  |
205      // +--------+--------+--------+========+
206      //
207      // str 32 stores a byte array whose length is up to (2^32)-1 bytes:
208      // +--------+--------+--------+--------+--------+========+
209      // |  0xdb  |AAAAAAAA|AAAAAAAA|AAAAAAAA|AAAAAAAA|  data  |
210      // +--------+--------+--------+--------+--------+========+
211      // where
212      // * XXXXX is a 5-bit unsigned integer which represents N
213      // * YYYYYYYY is a 8-bit unsigned integer which represents N
214      // * ZZZZZZZZ_ZZZZZZZZ is a 16-bit big-endian unsigned integer which represents N
215      // * AAAAAAAA_AAAAAAAA_AAAAAAAA_AAAAAAAA is a 32-bit big-endian unsigned integer which represents N
216      // * N is the length of data
217
218      byte[] b = cs.toString().getBytes("UTF-8");
219      if (b.length < 32)
220         return append1(0xA0 + b.length).append(b);
221      if (b.length < (1<<8))
222         return append1(STR8).append1(b.length).append(b);
223      if (b.length < (1<<16))
224         return append1(STR16).append2(b.length).append(b);
225      return append1(STR32).append4(b.length).append(b);
226   }
227
228   /**
229    * Appends a binary field to the stream.
230    */
231   final MsgPackOutputStream appendBinary(byte[] b) throws IOException {
232      // bin 8 stores a byte array whose length is up to (2^8)-1 bytes:
233      // +--------+--------+========+
234      // |  0xc4  |XXXXXXXX|  data  |
235      // +--------+--------+========+
236      //
237      // bin 16 stores a byte array whose length is up to (2^16)-1 bytes:
238      // +--------+--------+--------+========+
239      // |  0xc5  |YYYYYYYY|YYYYYYYY|  data  |
240      // +--------+--------+--------+========+
241      //
242      // bin 32 stores a byte array whose length is up to (2^32)-1 bytes:
243      // +--------+--------+--------+--------+--------+========+
244      // |  0xc6  |ZZZZZZZZ|ZZZZZZZZ|ZZZZZZZZ|ZZZZZZZZ|  data  |
245      // +--------+--------+--------+--------+--------+========+
246      //
247      // where
248      // * XXXXXXXX is a 8-bit unsigned integer which represents N
249      // * YYYYYYYY_YYYYYYYY is a 16-bit big-endian unsigned integer which represents N
250      // * ZZZZZZZZ_ZZZZZZZZ_ZZZZZZZZ_ZZZZZZZZ is a 32-bit big-endian unsigned integer which represents N
251      // * N is the length of data
252
253      if (b.length < (1<<8))
254         return append1(BIN8).append1(b.length).append(b);
255      if (b.length < (1<<16))
256         return append1(BIN16).append2(b.length).append(b);
257      return append1(BIN32).append4(b.length).append(b);
258   }
259
260   /**
261    * Appends an array data type flag to the stream.
262    */
263   final MsgPackOutputStream startArray(int size) throws IOException {
264      // fixarray stores an array whose length is up to 15 elements:
265      // +--------+~~~~~~~~~~~~~~~~~+
266      // |1001XXXX|    N objects    |
267      // +--------+~~~~~~~~~~~~~~~~~+
268      //
269      // array 16 stores an array whose length is up to (2^16)-1 elements:
270      // +--------+--------+--------+~~~~~~~~~~~~~~~~~+
271      // |  0xdc  |YYYYYYYY|YYYYYYYY|    N objects    |
272      // +--------+--------+--------+~~~~~~~~~~~~~~~~~+
273      //
274      // array 32 stores an array whose length is up to (2^32)-1 elements:
275      // +--------+--------+--------+--------+--------+~~~~~~~~~~~~~~~~~+
276      // |  0xdd  |ZZZZZZZZ|ZZZZZZZZ|ZZZZZZZZ|ZZZZZZZZ|    N objects    |
277      // +--------+--------+--------+--------+--------+~~~~~~~~~~~~~~~~~+
278      //
279      // where
280      // * XXXX is a 4-bit unsigned integer which represents N
281      // * YYYYYYYY_YYYYYYYY is a 16-bit big-endian unsigned integer which represents N
282      // * ZZZZZZZZ_ZZZZZZZZ_ZZZZZZZZ_ZZZZZZZZ is a 32-bit big-endian unsigned integer which represents N
283      //     N is the size of a array
284
285      if (size < 16)
286         return append1(0x90 + size);
287      if (size < (1<<16))
288         return append1(ARRAY16).append2(size);
289      return append1(ARRAY32).append4(size);
290   }
291
292   /**
293    * Appends a map data type flag to the stream.
294    */
295   final MsgPackOutputStream startMap(int size) throws IOException {
296      // fixmap stores a map whose length is up to 15 elements
297      // +--------+~~~~~~~~~~~~~~~~~+
298      // |1000XXXX|   N*2 objects   |
299      // +--------+~~~~~~~~~~~~~~~~~+
300      //
301      // map 16 stores a map whose length is up to (2^16)-1 elements
302      // +--------+--------+--------+~~~~~~~~~~~~~~~~~+
303      // |  0xde  |YYYYYYYY|YYYYYYYY|   N*2 objects   |
304      // +--------+--------+--------+~~~~~~~~~~~~~~~~~+
305      //
306      // map 32 stores a map whose length is up to (2^32)-1 elements
307      // +--------+--------+--------+--------+--------+~~~~~~~~~~~~~~~~~+
308      // |  0xdf  |ZZZZZZZZ|ZZZZZZZZ|ZZZZZZZZ|ZZZZZZZZ|   N*2 objects   |
309      // +--------+--------+--------+--------+--------+~~~~~~~~~~~~~~~~~+
310      //
311      // where
312      // * XXXX is a 4-bit unsigned integer which represents N
313      // * YYYYYYYY_YYYYYYYY is a 16-bit big-endian unsigned integer which represents N
314      // * ZZZZZZZZ_ZZZZZZZZ_ZZZZZZZZ_ZZZZZZZZ is a 32-bit big-endian unsigned integer which represents N
315      // * N is the size of a map
316      // * odd elements in objects are keys of a map
317      // * the next element of a key is its associated value
318
319      if (size < 16)
320         return append1(0x80 + size);
321      if (size < (1<<16))
322         return append1(MAP16).append2(size);
323      return append1(MAP32).append4(size);
324   }
325}