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}