001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.msgpack;
018
019import static org.apache.juneau.commons.utils.IoUtils.*;
020import static org.apache.juneau.msgpack.DataType.*;
021
022import java.io.*;
023import java.math.*;
024import java.util.concurrent.atomic.*;
025
026import org.apache.juneau.serializer.*;
027
028/**
029 * Specialized output stream for serializing MessagePack streams.
030 *
031 * <h5 class='section'>Notes:</h5><ul>
032 *    <li class='note'>
033 *       This class is not intended for external use.
034 * </ul>
035 *
036 * <h5 class='section'>See Also:</h5><ul>
037 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/MessagePackBasics">MessagePack Basics</a>
038
039 * </ul>
040 */
041@SuppressWarnings("resource")
042public class MsgPackOutputStream extends OutputStream {
043
044   private final OutputStream os;
045
046   final long L2X31 = ((long)(1 << 30)) * 2;
047
048   /**
049    * Constructor.
050    *
051    * @param os The output stream being wrapped.
052    */
053   protected MsgPackOutputStream(OutputStream os) {
054      this.os = os;
055   }
056
057   @Override /* Overridden from OutputStream */
058   public void write(int b) {
059      try {
060         os.write(b);
061      } catch (IOException e) {
062         throw new SerializeException(e);
063      }
064   }
065
066   private static int getUtf8ByteLength(CharSequence cs) {
067      var count = 0;
068      for (int i = 0, len = cs.length(); i < len; i++) {
069         var ch = cs.charAt(i);
070         if (ch <= 0x7F) {
071            count++;
072         } else if (ch <= 0x7FF) {
073            count += 2;
074         } else if (Character.isHighSurrogate(ch)) {
075            count += 4;
076            ++i;
077         } else {
078            count += 3;
079         }
080      }
081      return count;
082   }
083
084   private int writeUtf8To(CharSequence in, OutputStream out) {
085      var count = 0;
086      for (int i = 0, len = in.length(); i < len; i++) {
087         var c = (in.charAt(i) & 0xFFFF);
088         if (c <= 0x7F) {
089            write((byte)(c & 0xFF));
090            count++;
091         } else if (c <= 0x7FF) {
092            write((byte)(0xC0 + ((c >> 6) & 0x1F)));
093            write((byte)(0x80 + (c & 0x3F)));
094            count += 2;
095         } else if (c >= 0xD800 && c <= 0xDFFF) {
096            int jchar2 = in.charAt(++i) & 0xFFFF;
097            int n = (c << 10) + jchar2 + 0xFCA02400;
098            write((byte)(0xF0 + ((n >> 18) & 0x07)));
099            write((byte)(0x80 + ((n >> 12) & 0x3F)));
100            write((byte)(0x80 + ((n >> 6) & 0x3F)));
101            write((byte)(0x80 + (n & 0x3F)));
102            count += 4;
103         } else {
104            write((byte)(0xE0 + ((c >> 12) & 0x0F)));
105            write((byte)(0x80 + ((c >> 6) & 0x3F)));
106            write((byte)(0x80 + (c & 0x3F)));
107            count += 3;
108         }
109      }
110      return count;
111   }
112
113   /**
114    * Same as {@link #write(int)}.
115    */
116   MsgPackOutputStream append(byte b) {
117      try {
118         os.write(b);
119      } catch (IOException e) {
120         throw new SerializeException(e);
121      }
122      return this;
123   }
124
125   /**
126    * Same as {@link #write(byte[])}.
127    */
128   MsgPackOutputStream append(byte[] b) {
129      try {
130         os.write(b);
131      } catch (IOException e) {
132         throw new SerializeException(e);
133      }
134      return this;
135   }
136
137   /**
138    * Appends one byte to the stream.
139    */
140   MsgPackOutputStream append1(int i) {
141      try {
142         os.write(i);
143      } catch (IOException e) {
144         throw new SerializeException(e);
145      }
146      return this;
147   }
148
149   /**
150    * Appends two bytes to the stream.
151    */
152   MsgPackOutputStream append2(int i) {
153      return append1(i >> 8).append1(i);
154   }
155
156   /**
157    * Appends four bytes to the stream.
158    */
159   MsgPackOutputStream append4(int i) {
160      return append1(i >> 24).append1(i >> 16).append1(i >> 8).append1(i);
161   }
162
163   /**
164    * Appends eight bytes to the stream.
165    */
166   MsgPackOutputStream append8(long l) {
167      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));
168   }
169
170   /**
171    * Appends a binary field to the stream.
172    */
173   MsgPackOutputStream appendBinary(byte[] b) {
174      // bin 8 stores a byte array whose length is up to (2^8)-1 bytes:
175      // +--------+--------+========+
176      // |  0xc4  |XXXXXXXX|  data  |
177      // +--------+--------+========+
178      //
179      // bin 16 stores a byte array whose length is up to (2^16)-1 bytes:
180      // +--------+--------+--------+========+
181      // |  0xc5  |YYYYYYYY|YYYYYYYY|  data  |
182      // +--------+--------+--------+========+
183      //
184      // bin 32 stores a byte array whose length is up to (2^32)-1 bytes:
185      // +--------+--------+--------+--------+--------+========+
186      // |  0xc6  |ZZZZZZZZ|ZZZZZZZZ|ZZZZZZZZ|ZZZZZZZZ|  data  |
187      // +--------+--------+--------+--------+--------+========+
188      //
189      // where
190      // * XXXXXXXX is a 8-bit unsigned integer which represents N
191      // * YYYYYYYY_YYYYYYYY is a 16-bit big-endian unsigned integer which represents N
192      // * ZZZZZZZZ_ZZZZZZZZ_ZZZZZZZZ_ZZZZZZZZ is a 32-bit big-endian unsigned integer which represents N
193      // * N is the length of data
194
195      if (b.length < (1 << 8))
196         return append1(BIN8).append1(b.length).append(b);
197      if (b.length < (1 << 16))
198         return append1(BIN16).append2(b.length).append(b);
199      return append1(BIN32).append4(b.length).append(b);
200   }
201
202   /**
203    * Appends a binary field to the stream.
204    */
205   MsgPackOutputStream appendBinary(InputStream is) {
206
207      var baos = new ByteArrayOutputStream();
208      pipe(is, baos, x -> {
209         throw new SerializeException(x);
210      });
211
212      var b = baos.toByteArray();
213
214      // bin 8 stores a byte array whose length is up to (2^8)-1 bytes:
215      // +--------+--------+========+
216      // | 0xc4   |XXXXXXXX|  data  |
217      // +--------+--------+========+
218      //
219      // bin 16 stores a byte array whose length is up to (2^16)-1 bytes:
220      // +--------+--------+--------+========+
221      // | 0xc5   |YYYYYYYY|YYYYYYYY|  data  |
222      // +--------+--------+--------+========+
223      //
224      // bin 32 stores a byte array whose length is up to (2^32)-1 bytes:
225      // +--------+--------+--------+--------+--------+========+
226      // | 0xc6   |ZZZZZZZZ|ZZZZZZZZ|ZZZZZZZZ|ZZZZZZZZ|  data  |
227      // +--------+--------+--------+--------+--------+========+
228      //
229      // where
230      // * XXXXXXXX is a 8-bit unsigned integer which represents N
231      // * YYYYYYYY_YYYYYYYY is a 16-bit big-endian unsigned integer which represents N
232      // * ZZZZZZZZ_ZZZZZZZZ_ZZZZZZZZ_ZZZZZZZZ is a 32-bit big-endian unsigned integer which represents N
233      // * N is the length of data
234
235      if (b.length < (1 << 8))
236         return append1(BIN8).append1(b.length).append(b);
237      if (b.length < (1 << 16))
238         return append1(BIN16).append2(b.length).append(b);
239      return append1(BIN32).append4(b.length).append(b);
240   }
241
242   /**
243    * Appends a boolean to the stream.
244    */
245   MsgPackOutputStream appendBoolean(boolean b) {
246      return append1(b ? TRUE : FALSE);
247   }
248
249   /**
250    * Appends a double to the stream.
251    */
252   MsgPackOutputStream appendDouble(double d) {
253      // FLOAT64      = 0xCB,  //   float 64       11001011     0xcb
254      return append1(FLOAT64).append8(Double.doubleToLongBits(d));
255   }
256
257   /**
258    * Appends a float to the stream.
259    */
260   MsgPackOutputStream appendFloat(float f) {
261      // FLOAT32      = 0xCA,  //   float 32       11001010     0xca
262      return append1(FLOAT32).append4(Float.floatToIntBits(f));
263
264   }
265
266   /**
267    * Appends an integer to the stream.
268    */
269   MsgPackOutputStream appendInt(int i) {
270      // POSFIXINT_L  = 0x00,  //   pos fixint     0xxxxxxx     0x00 - 0x7f
271      // POSFIXINT_U  = 0x7F,
272      // UINT8        = 0xCC,  //   uint 8         11001100     0xcc
273      // UINT16       = 0xCD,  //   uint 16        11001101     0xcd
274      // UINT32       = 0xCE,  //   uint 32        11001110     0xce
275      // UINT64       = 0xCF,  //   uint 64        11001111     0xcf
276      // INT8         = 0xD0,  //   int 8          11010000     0xd0
277      // INT16        = 0xD1,  //   int 16         11010001     0xd1
278      // INT32        = 0xD2,  //   int 32         11010010     0xd2
279      // INT64        = 0xD3,  //   int 64         11010011     0xd3
280      // NEGFIXINT_L  = 0xE0,  //   neg fixint     111xxxxx     0xe0 - 0xff
281      // NEGFIXINT_U  = 0xFF;
282      if (i >= 0) {
283         if (i < (1 << 7))
284            return append1(i);
285         if (i < (1 << 15))
286            return append1(INT16).append2(i);
287         return append1(INT32).append4(i);
288      }
289      if (i > -(1 << 6))
290         return append((byte)(0xE0 | -i));
291      if (i > -(1 << 7))
292         return append1(INT8).append1(i);
293      if (i > -(1 << 15))
294         return append1(INT16).append2(i);
295      return append1(INT32).append4(i);
296   }
297
298   /**
299    * Appends a long to the stream.
300    */
301   MsgPackOutputStream appendLong(long l) {
302      if (l < L2X31 && l > -(L2X31))
303         return appendInt((int)l);
304      return append1(INT64).append8(l);
305   }
306
307   /**
308    * Appends a NULL flag to the stream.
309    */
310   MsgPackOutputStream appendNull() {
311      return append1(NIL);
312   }
313
314   /**
315    * Appends a generic Number to the stream.
316    */
317   MsgPackOutputStream appendNumber(Number n) {
318      var c = n.getClass();
319      if (c == Integer.class || c == Short.class || c == Byte.class || c == AtomicInteger.class)
320         return appendInt(n.intValue());
321      if (c == Long.class || c == AtomicLong.class)
322         return appendLong(n.longValue());
323      if (c == Float.class)
324         return appendFloat(n.floatValue());
325      if (c == Double.class)
326         return appendDouble(n.doubleValue());
327      if (c == BigInteger.class)
328         return appendLong(n.longValue());
329      if (c == BigDecimal.class)
330         return appendDouble(n.doubleValue());
331      return appendInt(0);
332   }
333
334   /**
335    * Appends a string to the stream.
336    */
337   MsgPackOutputStream appendString(CharSequence cs) {
338
339      // fixstr stores a byte array whose length is up to 31 bytes:
340      // +--------+========+
341      // |101XXXXX|  data  |
342      // +--------+========+
343      //
344      // str 8 stores a byte array whose length is up to (2^8)-1 bytes:
345      // +--------+--------+========+
346      // |  0xd9  |YYYYYYYY|  data  |
347      // +--------+--------+========+
348      //
349      // str 16 stores a byte array whose length is up to (2^16)-1 bytes:
350      // +--------+--------+--------+========+
351      // |  0xda  |ZZZZZZZZ|ZZZZZZZZ|  data  |
352      // +--------+--------+--------+========+
353      //
354      // str 32 stores a byte array whose length is up to (2^32)-1 bytes:
355      // +--------+--------+--------+--------+--------+========+
356      // |  0xdb  |AAAAAAAA|AAAAAAAA|AAAAAAAA|AAAAAAAA|  data  |
357      // +--------+--------+--------+--------+--------+========+
358      // where
359      // * XXXXX is a 5-bit unsigned integer which represents N
360      // * YYYYYYYY is a 8-bit unsigned integer which represents N
361      // * ZZZZZZZZ_ZZZZZZZZ is a 16-bit big-endian unsigned integer which represents N
362      // * AAAAAAAA_AAAAAAAA_AAAAAAAA_AAAAAAAA is a 32-bit big-endian unsigned integer which represents N
363      // * N is the length of data
364
365      int length = getUtf8ByteLength(cs);
366      if (length < 32)
367         append1(0xA0 + length);
368      else if (length < (1 << 8))
369         append1(STR8).append1(length);
370      else if (length < (1 << 16))
371         append1(STR16).append2(length);
372      else
373         append1(STR32).append4(length);
374
375      int length2 = writeUtf8To(cs, os);
376
377      if (length != length2)
378         throw new SerializeException("Unexpected length.  Expected={0}, Actual={1}", length, length2);
379
380      return this;
381   }
382
383   /**
384    * Appends an array data type flag to the stream.
385    */
386   MsgPackOutputStream startArray(int size) {
387      // fixarray stores an array whose length is up to 15 elements:
388      // +--------+~~~~~~~~~~~~~~~~~+
389      // |1001XXXX|    N objects    |
390      // +--------+~~~~~~~~~~~~~~~~~+
391      //
392      // array 16 stores an array whose length is up to (2^16)-1 elements:
393      // +--------+--------+--------+~~~~~~~~~~~~~~~~~+
394      // |  0xdc  |YYYYYYYY|YYYYYYYY|    N objects    |
395      // +--------+--------+--------+~~~~~~~~~~~~~~~~~+
396      //
397      // array 32 stores an array whose length is up to (2^32)-1 elements:
398      // +--------+--------+--------+--------+--------+~~~~~~~~~~~~~~~~~+
399      // |  0xdd  |ZZZZZZZZ|ZZZZZZZZ|ZZZZZZZZ|ZZZZZZZZ|    N objects    |
400      // +--------+--------+--------+--------+--------+~~~~~~~~~~~~~~~~~+
401      //
402      // where
403      // * XXXX is a 4-bit unsigned integer which represents N
404      // * YYYYYYYY_YYYYYYYY is a 16-bit big-endian unsigned integer which represents N
405      // * ZZZZZZZZ_ZZZZZZZZ_ZZZZZZZZ_ZZZZZZZZ is a 32-bit big-endian unsigned integer which represents N
406      //     N is the size of a array
407
408      if (size < 16)
409         return append1(0x90 + size);
410      if (size < (1 << 16))
411         return append1(ARRAY16).append2(size);
412      return append1(ARRAY32).append4(size);
413   }
414
415   /**
416    * Appends a map data type flag to the stream.
417    */
418   MsgPackOutputStream startMap(int size) {
419      // fixmap stores a map whose length is up to 15 elements
420      // +--------+~~~~~~~~~~~~~~~~~+
421      // |1000XXXX|   N*2 objects   |
422      // +--------+~~~~~~~~~~~~~~~~~+
423      //
424      // map 16 stores a map whose length is up to (2^16)-1 elements
425      // +--------+--------+--------+~~~~~~~~~~~~~~~~~+
426      // |  0xde  |YYYYYYYY|YYYYYYYY|   N*2 objects   |
427      // +--------+--------+--------+~~~~~~~~~~~~~~~~~+
428      //
429      // map 32 stores a map whose length is up to (2^32)-1 elements
430      // +--------+--------+--------+--------+--------+~~~~~~~~~~~~~~~~~+
431      // |  0xdf  |ZZZZZZZZ|ZZZZZZZZ|ZZZZZZZZ|ZZZZZZZZ|   N*2 objects   |
432      // +--------+--------+--------+--------+--------+~~~~~~~~~~~~~~~~~+
433      //
434      // where
435      // * XXXX is a 4-bit unsigned integer which represents N
436      // * YYYYYYYY_YYYYYYYY is a 16-bit big-endian unsigned integer which represents N
437      // * ZZZZZZZZ_ZZZZZZZZ_ZZZZZZZZ_ZZZZZZZZ is a 32-bit big-endian unsigned integer which represents N
438      // * N is the size of a map
439      // * odd elements in objects are keys of a map
440      // * the next element of a key is its associated value
441
442      if (size < 16)
443         return append1(0x80 + size);
444      if (size < (1 << 16))
445         return append1(MAP16).append2(size);
446      return append1(MAP32).append4(size);
447   }
448}