• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

wuwen5 / hessian / 17328794241

29 Aug 2025 03:55PM UTC coverage: 68.88% (+0.1%) from 68.748%
17328794241

push

github

web-flow
fix: Fix the issue of parent class fields covering subclasses (#39)

1835 of 2863 branches covered (64.09%)

Branch coverage included in aggregate %.

4212 of 5916 relevant lines covered (71.2%)

3.1 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

82.07
hessian2-codec/src/main/java/io/github/wuwen5/hessian/io/HessianEncoder.java
1
/*
2
 * Copyright (c) 2001-2008 Caucho Technology, Inc.  All rights reserved.
3
 *
4
 * The Apache Software License, Version 1.1
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions
8
 * are met:
9
 *
10
 * 1. Redistributions of source code must retain the above copyright
11
 *    notice, this list of conditions and the following disclaimer.
12
 *
13
 * 2. Redistributions in binary form must reproduce the above copyright
14
 *    notice, this list of conditions and the following disclaimer in
15
 *    the documentation and/or other materials provided with the
16
 *    distribution.
17
 *
18
 * 3. The end-user documentation included with the redistribution, if
19
 *    any, must include the following acknowlegement:
20
 *       "This product includes software developed by the
21
 *        Caucho Technology (http://www.caucho.com/)."
22
 *    Alternately, this acknowlegement may appear in the software itself,
23
 *    if and wherever such third-party acknowlegements normally appear.
24
 *
25
 * 4. The names "Burlap", "Resin", and "Caucho" must not be used to
26
 *    endorse or promote products derived from this software without prior
27
 *    written permission. For written permission, please contact
28
 *    info@caucho.com.
29
 *
30
 * 5. Products derived from this software may not be called "Resin"
31
 *    nor may "Resin" appear in their names without prior written
32
 *    permission of Caucho Technology.
33
 *
34
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
35
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
36
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
37
 * DISCLAIMED.  IN NO EVENT SHALL CAUCHO TECHNOLOGY OR ITS CONTRIBUTORS
38
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
39
 * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
40
 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
41
 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
42
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
43
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
44
 * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
45
 *
46
 * @author Scott Ferguson
47
 */
48

49
package io.github.wuwen5.hessian.io;
50

51
import io.github.wuwen5.hessian.util.IdentityIntMap;
52
import java.io.IOException;
53
import java.io.InputStream;
54
import java.io.OutputStream;
55
import java.util.HashMap;
56

57
/**
58
 * Output stream for Hessian 2 requests.
59
 *
60
 * <p>Since HessianOutput does not depend on any classes other than
61
 * in the JDK, it can be extracted independently into a smaller package.
62
 *
63
 * <p>HessianOutput is unbuffered, so any client needs to provide
64
 * its own buffering.
65
 *
66
 * <pre>
67
 * OutputStream os = ...; // from http connection
68
 * Hessian2Output out = new Hessian2Output(os);
69
 * String value;
70
 *
71
 * out.startCall("hello", 1); // start hello call
72
 * out.writeString("arg1");   // write a string argument
73
 * out.completeCall();        // complete the call
74
 * </pre>
75
 */
76
public class HessianEncoder extends AbstractHessianEncoder implements Hessian2Constants {
77

78
    /**
79
     * should match Resin buffer size for perf
80
     */
81
    public static final int SIZE = 8 * 1024;
82

83
    /**
84
     * the output stream/
85
     */
86
    protected OutputStream os;
87

88
    /**
89
     * map of references
90
     */
91
    private final IdentityIntMap refs = new IdentityIntMap(256);
12✔
92

93
    private int refCount = 0;
6✔
94

95
    private boolean isCloseStreamOnClose;
96

97
    /**
98
     * map of classes
99
     */
100
    private final IdentityIntMap classRefs = new IdentityIntMap(256);
12✔
101

102
    /**
103
     * map of types
104
     */
105
    private HashMap<String, Integer> typeRefs;
106

107
    protected final byte[] buffer = new byte[SIZE];
8✔
108
    protected int offset;
109

110
    private boolean isPacket;
111

112
    private boolean isUnshared;
113

114
    /**
115
     * Creates a new Hessian output stream, initialized with an
116
     * underlying output stream.
117
     */
118
    public HessianEncoder() {}
3✔
119

120
    /**
121
     * Creates a new Hessian output stream, initialized with an
122
     * underlying output stream.
123
     *
124
     * @param os the underlying output stream.
125
     */
126
    public HessianEncoder(OutputStream os) {
2✔
127
        init(os);
3✔
128
    }
1✔
129

130
    @Override
131
    public void init(OutputStream os) {
132
        reset();
2✔
133

134
        this.os = os;
3✔
135
    }
1✔
136

137
    public void initPacket(OutputStream os) {
138
        resetReferences();
2✔
139

140
        this.os = os;
3✔
141
    }
1✔
142

143
    public void setCloseStreamOnClose(boolean isClose) {
144
        isCloseStreamOnClose = isClose;
3✔
145
    }
1✔
146

147
    public boolean isCloseStreamOnClose() {
148
        return isCloseStreamOnClose;
3✔
149
    }
150

151
    /**
152
     * Sets hessian to be "unshared", meaning it will not detect
153
     * duplicate or circular references.
154
     */
155
    @Override
156
    public boolean setUnshared(boolean isUnshared) {
157
        boolean oldIsUnshared = this.isUnshared;
3✔
158

159
        this.isUnshared = isUnshared;
3✔
160

161
        return oldIsUnshared;
2✔
162
    }
163

164
    public void writeVersion() throws IOException {
165
        flushIfFull();
2✔
166

167
        buffer[offset++] = (byte) 'H';
11✔
168
        buffer[offset++] = (byte) 2;
11✔
169
        buffer[offset++] = (byte) 0;
11✔
170
    }
1✔
171

172
    /**
173
     * Writes any object to the output stream.
174
     */
175
    @Override
176
    public void writeObject(Object object) throws IOException {
177
        if (object == null) {
2✔
178
            writeNull();
2✔
179
            return;
1✔
180
        }
181

182
        Serializer serializer = findSerializerFactory().getObjectSerializer(object.getClass());
6✔
183

184
        serializer.writeObject(object, this);
4✔
185
    }
1✔
186

187
    /**
188
     * Writes the list header to the stream.  List writers will call
189
     * <code>writeListBegin</code> followed by the list contents and then
190
     * call <code>writeListEnd</code>.
191
     * <p>
192
     *     <pre>
193
     * <code>
194
     * list ::= V type value* Z
195
     *      ::= v type int value*
196
     * </code>
197
     * </pre>
198
     *
199
     * @return true for variable lists, false for fixed lists
200
     */
201
    @Override
202
    public boolean writeListBegin(int length, String type) throws IOException {
203
        flushIfFull();
2✔
204

205
        if (length < 0) {
2✔
206
            if (type != null) {
2✔
207
                buffer[offset++] = (byte) BC_LIST_VARIABLE;
11✔
208
                writeType(type);
4✔
209
            } else {
210
                buffer[offset++] = (byte) BC_LIST_VARIABLE_UNTYPED;
11✔
211
            }
212

213
            return true;
2✔
214
        } else if (length <= LIST_DIRECT_MAX) {
3✔
215
            if (type != null) {
2✔
216
                buffer[offset++] = (byte) (BC_LIST_DIRECT + length);
14✔
217
                writeType(type);
4✔
218
            } else {
219
                buffer[offset++] = (byte) (BC_LIST_DIRECT_UNTYPED + length);
14✔
220
            }
221

222
            return false;
2✔
223
        } else {
224
            if (type != null) {
2!
225
                buffer[offset++] = (byte) BC_LIST_FIXED;
11✔
226
                writeType(type);
4✔
227
            } else {
228
                buffer[offset++] = (byte) BC_LIST_FIXED_UNTYPED;
×
229
            }
230

231
            writeInt(length);
3✔
232

233
            return false;
2✔
234
        }
235
    }
236

237
    /**
238
     * Writes the tail of the list to the stream for a variable-length list.
239
     */
240
    @Override
241
    public void writeListEnd() throws IOException {
242
        flushIfFull();
2✔
243

244
        buffer[offset++] = (byte) BC_END;
11✔
245
    }
1✔
246

247
    /**
248
     * Writes the map header to the stream.  Map writers will call
249
     * <code>writeMapBegin</code> followed by the map contents and then
250
     * call <code>writeMapEnd</code>.
251
     *<pre>
252
     * <code>
253
     * map ::= M type (value value)* Z
254
     *     ::= H (value value)* Z
255
     * </code>
256
     * </pre>
257
     */
258
    @Override
259
    public void writeMapBegin(String type) throws IOException {
260
        if (SIZE < offset + 32) {
6!
261
            flushBuffer();
×
262
        }
263

264
        if (type != null) {
2✔
265
            buffer[offset++] = BC_MAP;
11✔
266

267
            writeType(type);
4✔
268
        } else {
269
            buffer[offset++] = BC_MAP_UNTYPED;
11✔
270
        }
271
    }
1✔
272

273
    /**
274
     * Writes the tail of the map to the stream.
275
     */
276
    @Override
277
    public void writeMapEnd() throws IOException {
278
        if (SIZE < offset + 32) {
6!
279
            flushBuffer();
×
280
        }
281

282
        buffer[offset++] = (byte) BC_END;
11✔
283
    }
1✔
284

285
    /**
286
     * Writes the object definition
287
     *<pre>
288
     * <code>
289
     * C &lt;string&gt; &lt;int&gt; &lt;string&gt;*
290
     * </code>
291
     * </pre>
292
     * @return the reference number for the class, or -1 if the class is not
293
     */
294
    @Override
295
    public int writeObjectBegin(String type) throws IOException {
296
        int newRef = classRefs.size();
4✔
297
        int ref = classRefs.put(type, newRef, false);
7✔
298

299
        if (SIZE < offset + 32) {
6!
300
            flushBuffer();
×
301
        }
302
        if (newRef != ref) {
3✔
303

304
            if (ref <= OBJECT_DIRECT_MAX) {
3!
305
                buffer[offset++] = (byte) (BC_OBJECT_DIRECT + ref);
15✔
306
            } else {
307
                buffer[offset++] = (byte) 'O';
×
308
                writeInt(ref);
×
309
            }
310

311
            return ref;
2✔
312
        } else {
313

314
            buffer[offset++] = (byte) 'C';
11✔
315

316
            writeString(type);
3✔
317

318
            return -1;
2✔
319
        }
320
    }
321

322
    /**
323
     * Writes the tail of the class definition to the stream.
324
     */
325
    @Override
326
    public void writeClassFieldLength(int len) throws IOException {
327
        writeInt(len);
3✔
328
    }
1✔
329

330
    /**
331
     * Writes the tail of the object definition to the stream.
332
     */
333
    @Override
334
    public void writeObjectEnd() {}
×
335

336
    /**
337
     * <pre>
338
     * <code>
339
     * type ::= string
340
     *      ::= int
341
     * </code>
342
     * </pre>
343
     */
344
    private void writeType(String type) throws IOException {
345
        flushIfFull();
2✔
346

347
        int len = type.length();
3✔
348
        if (len == 0) {
2!
349
            throw new IllegalArgumentException("empty type is not allowed");
×
350
        }
351

352
        if (typeRefs == null) {
3✔
353
            typeRefs = new HashMap<>();
5✔
354
        }
355

356
        Integer typeRefV = typeRefs.get(type);
6✔
357

358
        if (typeRefV != null) {
2✔
359
            int typeRef = typeRefV;
3✔
360

361
            writeInt(typeRef);
3✔
362
        } else {
1✔
363
            typeRefs.put(type, Integer.valueOf(typeRefs.size()));
9✔
364

365
            writeString(type);
3✔
366
        }
367
    }
1✔
368

369
    /**
370
     * Writes a boolean value to the stream.  The boolean will be written
371
     * with the following syntax:
372
     *<pre>
373
     * <code>
374
     * T
375
     * F
376
     * </code>
377
     * </pre>
378
     *
379
     * @param value the boolean value to write.
380
     */
381
    @Override
382
    public void writeBoolean(boolean value) throws IOException {
383
        if (SIZE < offset + 16) {
6!
384
            flushBuffer();
×
385
        }
386

387
        if (value) {
2✔
388
            buffer[offset++] = (byte) 'T';
12✔
389
        } else {
390
            buffer[offset++] = (byte) 'F';
11✔
391
        }
392
    }
1✔
393

394
    /**
395
     * Writes an integer value to the stream.  The integer will be written
396
     * with the following syntax:
397
     *<pre>
398
     * <code>
399
     * I b32 b24 b16 b8
400
     * </code>
401
     * </pre>
402
     *
403
     * @param value the integer value to write.
404
     */
405
    @Override
406
    public void writeInt(int value) throws IOException {
407
        int i = this.offset;
3✔
408

409
        if (SIZE <= i + 16) {
5!
410
            flushBuffer();
×
411
            i = this.offset;
×
412
        }
413

414
        if (INT_DIRECT_MIN <= value && value <= INT_DIRECT_MAX) {
6✔
415
            buffer[i++] = (byte) (value + BC_INT_ZERO);
10✔
416
        } else if (INT_BYTE_MIN <= value && value <= INT_BYTE_MAX) {
6✔
417
            buffer[i++] = (byte) (BC_INT_BYTE_ZERO + (value >> 8));
11✔
418
            buffer[i++] = (byte) (value);
8✔
419
        } else if (INT_SHORT_MIN <= value && value <= INT_SHORT_MAX) {
6!
420
            buffer[i++] = (byte) (BC_INT_SHORT_ZERO + (value >> 16));
11✔
421
            buffer[i++] = (byte) (value >> 8);
9✔
422
            buffer[i++] = (byte) (value);
8✔
423
        } else {
424
            buffer[i++] = (byte) ('I');
6✔
425
            buffer[i++] = (byte) (value >> 24);
9✔
426
            buffer[i++] = (byte) (value >> 16);
9✔
427
            buffer[i++] = (byte) (value >> 8);
9✔
428
            buffer[i++] = (byte) (value);
7✔
429
        }
430

431
        this.offset = i;
3✔
432
    }
1✔
433

434
    /**
435
     * Writes a long value to the stream.  The long will be written
436
     * with the following syntax:
437
     *<pre>
438
     * <code>
439
     * L b64 b56 b48 b40 b32 b24 b16 b8
440
     * </code>
441
     * </pre>
442
     *
443
     * @param value the long value to write.
444
     */
445
    @Override
446
    public void writeLong(long value) throws IOException {
447
        int i = this.offset;
3✔
448

449
        if (SIZE <= i + 16) {
5!
450
            flushBuffer();
×
451
            i = this.offset;
×
452
        }
453

454
        if (LONG_DIRECT_MIN <= value && value <= LONG_DIRECT_MAX) {
8✔
455
            buffer[i++] = (byte) (value + BC_LONG_ZERO);
11✔
456
        } else if (LONG_BYTE_MIN <= value && value <= LONG_BYTE_MAX) {
8✔
457
            buffer[i++] = (byte) (BC_LONG_BYTE_ZERO + (value >> 8));
12✔
458
            buffer[i++] = (byte) (value);
9✔
459
        } else if (LONG_SHORT_MIN <= value && value <= LONG_SHORT_MAX) {
8✔
460
            buffer[i++] = (byte) (BC_LONG_SHORT_ZERO + (value >> 16));
12✔
461
            buffer[i++] = (byte) (value >> 8);
10✔
462
            buffer[i++] = (byte) (value);
9✔
463
        } else if (-0x80000000L <= value && value <= 0x7fffffffL) {
8✔
464
            buffer[i] = (byte) BC_LONG_INT;
5✔
465
            buffer[i + 1] = (byte) (value >> 24);
11✔
466
            buffer[i + 2] = (byte) (value >> 16);
11✔
467
            buffer[i + 3] = (byte) (value >> 8);
11✔
468
            buffer[i + 4] = (byte) (value);
9✔
469

470
            i += 5;
2✔
471
        } else {
472
            buffer[i] = (byte) 'L';
5✔
473
            buffer[i + 1] = (byte) (value >> 56);
11✔
474
            buffer[i + 2] = (byte) (value >> 48);
11✔
475
            buffer[i + 3] = (byte) (value >> 40);
11✔
476
            buffer[i + 4] = (byte) (value >> 32);
11✔
477
            buffer[i + 5] = (byte) (value >> 24);
11✔
478
            buffer[i + 6] = (byte) (value >> 16);
11✔
479
            buffer[i + 7] = (byte) (value >> 8);
11✔
480
            buffer[i + 8] = (byte) (value);
9✔
481

482
            i += 9;
1✔
483
        }
484

485
        this.offset = i;
3✔
486
    }
1✔
487

488
    /**
489
     * Writes a double value to the stream.  The double will be written
490
     * with the following syntax:
491
     * <p>
492
     *     <pre>
493
     * <code>
494
     * D b64 b56 b48 b40 b32 b24 b16 b8
495
     * </code>
496
     * </pre>
497
     *
498
     * @param value the double value to write.
499
     */
500
    @Override
501
    public void writeDouble(double value) throws IOException {
502
        int i = this.offset;
3✔
503

504
        if (SIZE <= i + 16) {
5!
505
            flushBuffer();
×
506
            i = this.offset;
×
507
        }
508

509
        int intValue = (int) value;
3✔
510

511
        if (intValue == value) {
5✔
512
            if (intValue == 0) {
2✔
513
                buffer[i++] = (byte) BC_DOUBLE_ZERO;
6✔
514

515
                this.offset = i;
3✔
516

517
                return;
1✔
518
            } else if (intValue == 1) {
3✔
519
                buffer[i++] = (byte) BC_DOUBLE_ONE;
6✔
520

521
                this.offset = i;
3✔
522

523
                return;
1✔
524
            } else if (-0x80 <= intValue && intValue < 0x80) {
6✔
525
                buffer[i++] = (byte) BC_DOUBLE_BYTE;
6✔
526
                buffer[i++] = (byte) intValue;
7✔
527

528
                this.offset = i;
3✔
529

530
                return;
1✔
531
            } else if (-0x8000 <= intValue && intValue < 0x8000) {
6!
532
                buffer[i] = (byte) BC_DOUBLE_SHORT;
5✔
533
                buffer[i + 1] = (byte) (intValue >> 8);
10✔
534
                buffer[i + 2] = (byte) intValue;
8✔
535

536
                this.offset = i + 3;
5✔
537

538
                return;
1✔
539
            }
540
        }
541

542
        int mills = (int) (value * 1000);
5✔
543

544
        if (0.001 * mills == value) {
7✔
545
            buffer[i] = (byte) (BC_DOUBLE_MILL);
5✔
546
            buffer[i + 1] = (byte) (mills >> 24);
10✔
547
            buffer[i + 2] = (byte) (mills >> 16);
10✔
548
            buffer[i + 3] = (byte) (mills >> 8);
10✔
549
            buffer[i + 4] = (byte) (mills);
8✔
550

551
            this.offset = i + 5;
5✔
552

553
            return;
1✔
554
        }
555

556
        long bits = Double.doubleToLongBits(value);
3✔
557

558
        buffer[i] = (byte) 'D';
5✔
559
        buffer[i + 1] = (byte) (bits >> 56);
11✔
560
        buffer[i + 2] = (byte) (bits >> 48);
11✔
561
        buffer[i + 3] = (byte) (bits >> 40);
11✔
562
        buffer[i + 4] = (byte) (bits >> 32);
11✔
563
        buffer[i + 5] = (byte) (bits >> 24);
11✔
564
        buffer[i + 6] = (byte) (bits >> 16);
11✔
565
        buffer[i + 7] = (byte) (bits >> 8);
11✔
566
        buffer[i + 8] = (byte) (bits);
9✔
567

568
        this.offset = i + 9;
5✔
569
    }
1✔
570

571
    /**
572
     * Writes a date to the stream.
573
     *<pre>
574
     * <code>
575
     * date ::= d   b7 b6 b5 b4 b3 b2 b1 b0
576
     *      ::= x65 b3 b2 b1 b0
577
     * </code>
578
     * </pre>
579
     *
580
     * @param time the date in milliseconds from the epoch in UTC
581
     */
582
    @Override
583
    public void writeUTCDate(long time) throws IOException {
584
        if (SIZE < offset + 32) {
6!
585
            flushBuffer();
×
586
        }
587

588
        int i = this.offset;
3✔
589

590
        if (time % 60000L == 0) {
6✔
591
            // compact date ::= x65 b3 b2 b1 b0
592

593
            long minutes = time / 60000L;
4✔
594

595
            if ((minutes >> 31) == 0 || (minutes >> 31) == -1) {
6!
596
                buffer[i++] = (byte) BC_DATE_MINUTE;
6✔
597
                buffer[i++] = ((byte) (minutes >> 24));
10✔
598
                buffer[i++] = ((byte) (minutes >> 16));
10✔
599
                buffer[i++] = ((byte) (minutes >> 8));
10✔
600
                buffer[i++] = ((byte) (minutes >> 0));
10✔
601

602
                this.offset = i;
3✔
603
                return;
1✔
604
            }
605
        }
606

607
        buffer[i++] = (byte) BC_DATE;
6✔
608
        buffer[i++] = ((byte) (time >> 56));
10✔
609
        buffer[i++] = ((byte) (time >> 48));
10✔
610
        buffer[i++] = ((byte) (time >> 40));
10✔
611
        buffer[i++] = ((byte) (time >> 32));
10✔
612
        buffer[i++] = ((byte) (time >> 24));
10✔
613
        buffer[i++] = ((byte) (time >> 16));
10✔
614
        buffer[i++] = ((byte) (time >> 8));
10✔
615
        buffer[i++] = ((byte) (time));
8✔
616

617
        this.offset = i;
3✔
618
    }
1✔
619

620
    /**
621
     * Writes a null value to the stream.
622
     * The null will be written with the following syntax
623
     * <pre>
624
     * <code>
625
     * N
626
     * </code>
627
     * </pre>
628
     */
629
    @Override
630
    public void writeNull() throws IOException {
631
        int i = this.offset;
3✔
632

633
        if (SIZE <= i + 16) {
5!
634
            flushBuffer();
×
635
            i = this.offset;
×
636
        }
637

638
        buffer[i++] = BC_NULL;
6✔
639

640
        this.offset = i;
3✔
641
    }
1✔
642

643
    /**
644
     * Writes a string value to the stream using UTF-8 encoding.
645
     * The string will be written with the following syntax:
646
     * <pre>
647
     * <code>
648
     * S b16 b8 string-value
649
     * </code>
650
     * If the value is null, it will be written as
651
     * <code>
652
     * N
653
     * </code>
654
     * </pre>
655
     *
656
     * @param value the string value to write.
657
     */
658
    @Override
659
    public void writeString(String value) throws IOException {
660
        int i = this.offset;
3✔
661

662
        if (SIZE <= i + 16) {
5!
663
            flushBuffer();
×
664
            i = this.offset;
×
665
        }
666

667
        if (value == null) {
2✔
668
            buffer[i++] = (byte) BC_NULL;
6✔
669

670
            this.offset = i;
4✔
671
        } else {
672
            int length = value.length();
3✔
673
            int strOffset = 0;
2✔
674

675
            while (length > 0x8000) {
3✔
676
                int sublen = 0x8000;
2✔
677

678
                i = this.offset;
3✔
679

680
                if (SIZE <= i + 16) {
5!
681
                    flushBuffer();
×
682
                    i = this.offset;
×
683
                }
684

685
                // chunk can't end in high surrogate
686
                char tail = value.charAt(strOffset + sublen - 1);
8✔
687

688
                if (0xd800 <= tail && tail <= 0xdbff) {
3!
689
                    sublen--;
×
690
                }
691

692
                buffer[i] = (byte) BC_STRING_CHUNK;
5✔
693
                buffer[i + 1] = (byte) (sublen >> 8);
10✔
694
                buffer[i + 2] = (byte) (sublen);
8✔
695

696
                this.offset = i + 3;
5✔
697

698
                printString(value, strOffset, sublen);
5✔
699

700
                length -= sublen;
4✔
701
                strOffset += sublen;
4✔
702
            }
1✔
703

704
            i = this.offset;
3✔
705

706
            if (SIZE <= i + 16) {
5!
707
                flushBuffer();
×
708
                i = this.offset;
×
709
            }
710

711
            if (length <= STRING_DIRECT_MAX) {
3✔
712
                buffer[i++] = (byte) (BC_STRING_DIRECT + length);
10✔
713
            } else if (length <= STRING_SHORT_MAX) {
3✔
714
                buffer[i++] = (byte) (BC_STRING_SHORT + (length >> 8));
11✔
715
                buffer[i++] = (byte) (length);
8✔
716
            } else {
717
                buffer[i++] = (byte) ('S');
6✔
718
                buffer[i++] = (byte) (length >> 8);
9✔
719
                buffer[i++] = (byte) (length);
7✔
720
            }
721

722
            this.offset = i;
3✔
723

724
            printString(value, strOffset, length);
5✔
725
        }
726
    }
1✔
727

728
    /**
729
     * Writes a string value to the stream using UTF-8 encoding.
730
     * The string will be written with the following syntax:
731
     * <pre>
732
     * <code>
733
     * S b16 b8 string-value
734
     * </code>
735
     * If the value is null, it will be written as
736
     * <code>
737
     * N
738
     * </code>
739
     * </pre>
740
     *
741
     */
742
    @Override
743
    public void writeString(char[] buffer, int offset, int length) throws IOException {
744
        if (buffer == null) {
2✔
745
            if (SIZE < this.offset + 16) {
6!
746
                flushBuffer();
×
747
            }
748

749
            this.buffer[this.offset++] = (byte) (BC_NULL);
12✔
750
        } else {
751
            while (length > 0x8000) {
3✔
752
                int sublen = 0x8000;
2✔
753

754
                if (SIZE < this.offset + 16) {
6!
755
                    flushBuffer();
×
756
                }
757

758
                // chunk can't end in high surrogate
759
                char tail = buffer[offset + sublen - 1];
8✔
760

761
                if (0xd800 <= tail && tail <= 0xdbff) {
3!
762
                    sublen--;
×
763
                }
764

765
                this.buffer[this.offset++] = (byte) BC_STRING_CHUNK;
11✔
766
                this.buffer[this.offset++] = (byte) (sublen >> 8);
14✔
767
                this.buffer[this.offset++] = (byte) (sublen);
12✔
768

769
                printString(buffer, offset, sublen);
5✔
770

771
                length -= sublen;
4✔
772
                offset += sublen;
4✔
773
            }
1✔
774

775
            if (SIZE < this.offset + 16) {
6!
776
                flushBuffer();
×
777
            }
778

779
            if (length <= STRING_DIRECT_MAX) {
3✔
780
                this.buffer[this.offset++] = (byte) (BC_STRING_DIRECT + length);
15✔
781
            } else if (length <= STRING_SHORT_MAX) {
3✔
782
                this.buffer[this.offset++] = (byte) (BC_STRING_SHORT + (length >> 8));
16✔
783
                this.buffer[this.offset++] = (byte) length;
13✔
784
            } else {
785
                this.buffer[this.offset++] = (byte) ('S');
11✔
786
                this.buffer[this.offset++] = (byte) (length >> 8);
14✔
787
                this.buffer[this.offset++] = (byte) (length);
12✔
788
            }
789

790
            printString(buffer, offset, length);
5✔
791
        }
792
    }
1✔
793

794
    /**
795
     * Writes a byte array to the stream.
796
     * The array will be written with the following syntax:
797
     * <pre>
798
     * <code>
799
     * B b16 b18 bytes
800
     * </code>
801
     * If the value is null, it will be written as
802
     * <code>
803
     * N
804
     * </code>
805
     * </pre>
806
     *
807
     */
808
    @Override
809
    public void writeBytes(byte[] buffer) throws IOException {
810
        if (buffer == null) {
2✔
811
            if (SIZE < offset + 16) {
6!
812
                flushBuffer();
×
813
            }
814

815
            this.buffer[offset++] = BC_NULL;
12✔
816
        } else {
817
            writeBytes(buffer, 0, buffer.length);
6✔
818
        }
819
    }
1✔
820

821
    /**
822
     * Writes a byte array to the stream.
823
     * The array will be written with the following syntax:
824
     * <pre>
825
     * <code>
826
     * B b16 b18 bytes
827
     * </code>
828
     * If the value is null, it will be written as
829
     * <code>
830
     * N
831
     * </code>
832
     * </pre>
833
     */
834
    @Override
835
    public void writeBytes(byte[] buffer, int offset, int length) throws IOException {
836
        if (buffer == null) {
2✔
837
            if (SIZE < this.offset + 16) {
6!
838
                flushBuffer();
×
839
            }
840

841
            this.buffer[this.offset++] = (byte) BC_NULL;
12✔
842
        } else {
843
            while (SIZE - this.offset - 3 < length) {
8✔
844
                int sublen = SIZE - this.offset - 3;
7✔
845

846
                if (sublen < 16) {
3!
847
                    flushBuffer();
×
848

849
                    sublen = SIZE - this.offset - 3;
×
850

851
                    if (length < sublen) {
×
852
                        sublen = length;
×
853
                    }
854
                }
855

856
                this.buffer[this.offset++] = (byte) BC_BINARY_CHUNK;
11✔
857
                this.buffer[this.offset++] = (byte) (sublen >> 8);
14✔
858
                this.buffer[this.offset++] = (byte) sublen;
12✔
859

860
                System.arraycopy(buffer, offset, this.buffer, this.offset, sublen);
8✔
861
                this.offset += sublen;
6✔
862

863
                length -= sublen;
4✔
864
                offset += sublen;
4✔
865

866
                flushBuffer();
2✔
867
            }
1✔
868

869
            if (SIZE < this.offset + 16) {
6!
870
                flushBuffer();
×
871
            }
872

873
            if (length <= BINARY_DIRECT_MAX) {
3✔
874
                this.buffer[this.offset++] = (byte) (BC_BINARY_DIRECT + length);
15✔
875
            } else if (length <= BINARY_SHORT_MAX) {
3✔
876
                this.buffer[this.offset++] = (byte) (BC_BINARY_SHORT + (length >> 8));
16✔
877
                this.buffer[this.offset++] = (byte) (length);
13✔
878
            } else {
879
                this.buffer[this.offset++] = (byte) 'B';
11✔
880
                this.buffer[this.offset++] = (byte) (length >> 8);
14✔
881
                this.buffer[this.offset++] = (byte) (length);
12✔
882
            }
883

884
            System.arraycopy(buffer, offset, this.buffer, this.offset, length);
8✔
885

886
            this.offset += length;
6✔
887
        }
888
    }
1✔
889

890
    /**
891
     * Writes a byte buffer to the stream.
892
     */
893
    @Override
894
    public void writeByteBufferStart() {}
1✔
895

896
    /**
897
     * Writes a byte buffer to the stream.
898
     * <pre>
899
     * <code>
900
     * b b16 b18 bytes
901
     * </code>
902
     * </pre>
903
     */
904
    @Override
905
    public void writeByteBufferPart(byte[] buffer, int offset, int length) throws IOException {
906
        while (length > 0) {
2✔
907
            flushIfFull();
2✔
908

909
            int sublen = this.buffer.length - this.offset;
7✔
910

911
            if (length < sublen) {
3!
912
                sublen = length;
2✔
913
            }
914

915
            this.buffer[this.offset++] = BC_BINARY_CHUNK;
11✔
916
            this.buffer[this.offset++] = (byte) (sublen >> 8);
14✔
917
            this.buffer[this.offset++] = (byte) sublen;
12✔
918

919
            System.arraycopy(buffer, offset, this.buffer, this.offset, sublen);
8✔
920

921
            this.offset += sublen;
6✔
922
            length -= sublen;
4✔
923
            offset += sublen;
4✔
924
        }
1✔
925
    }
1✔
926

927
    /**
928
     * Writes a byte buffer to the stream.
929
     * <pre>
930
     * <code>
931
     * b b16 b18 bytes
932
     * </code>
933
     * </pre>
934
     */
935
    @Override
936
    public void writeByteBufferEnd(byte[] buffer, int offset, int length) throws IOException {
937
        writeBytes(buffer, offset, length);
5✔
938
    }
1✔
939

940
    /**
941
     * Returns an output stream to write binary data.
942
     */
943
    public OutputStream getBytesOutputStream() throws IOException {
944
        return new BytesOutputStream();
5✔
945
    }
946

947
    /**
948
     * Writes a full output stream.
949
     */
950
    @Override
951
    public void writeByteStream(InputStream is) throws IOException {
952
        while (true) {
953
            int len = SIZE - offset - 3;
7✔
954

955
            if (len < 16) {
3✔
956
                flushBuffer();
2✔
957
                len = SIZE - offset - 3;
7✔
958
            }
959

960
            len = is.read(buffer, offset + 3, len);
10✔
961

962
            if (len <= 0) {
2✔
963
                buffer[offset++] = BC_BINARY_DIRECT;
11✔
964
                return;
1✔
965
            }
966

967
            buffer[offset] = (byte) BC_BINARY_CHUNK;
6✔
968
            buffer[offset + 1] = (byte) (len >> 8);
11✔
969
            buffer[offset + 2] = (byte) (len);
9✔
970

971
            offset += len + 3;
8✔
972
        }
1✔
973
    }
974

975
    /**
976
     * Writes a reference.
977
     * <pre>
978
     * x51 &lt;int&gt;
979
     * </pre>
980
     *
981
     * @param value the integer value to write.
982
     */
983
    @Override
984
    protected void writeRef(int value) throws IOException {
985
        if (SIZE < offset + 16) {
6!
986
            flushBuffer();
×
987
        }
988

989
        buffer[offset++] = (byte) BC_REF;
11✔
990

991
        writeInt(value);
3✔
992
    }
1✔
993

994
    /**
995
     * If the object has already been written, just write its ref.
996
     *
997
     * @return true if we're writing a ref.
998
     */
999
    @Override
1000
    public boolean addRef(Object object) throws IOException {
1001
        if (isUnshared) {
3✔
1002
            refCount++;
6✔
1003
            return false;
2✔
1004
        }
1005

1006
        int newRef = refCount;
3✔
1007

1008
        int ref = addRef(object, newRef, false);
6✔
1009

1010
        if (ref != newRef) {
3✔
1011
            writeRef(ref);
3✔
1012

1013
            return true;
2✔
1014
        } else {
1015
            refCount++;
6✔
1016

1017
            return false;
2✔
1018
        }
1019
    }
1020

1021
    @Override
1022
    public int getRef(Object obj) {
1023
        if (isUnshared) {
3!
1024
            return -1;
×
1025
        }
1026

1027
        return refs.get(obj);
5✔
1028
    }
1029

1030
    /**
1031
     * Removes a reference.
1032
     */
1033
    public boolean removeRef(Object obj) {
1034
        if (isUnshared) {
×
1035
            return false;
×
1036
        } else {
1037
            refs.remove(obj);
×
1038

1039
            return true;
×
1040
        }
1041
    }
1042

1043
    /**
1044
     * Replaces a reference from one object to another.
1045
     */
1046
    @Override
1047
    public boolean replaceRef(Object oldRef, Object newRef) {
1048
        if (isUnshared) {
3!
1049
            return false;
×
1050
        }
1051

1052
        int value = refs.get(oldRef);
5✔
1053

1054
        if (value >= 0) {
2!
1055
            addRef(newRef, value, true);
6✔
1056

1057
            refs.remove(oldRef);
4✔
1058

1059
            return true;
2✔
1060
        } else {
1061
            return false;
×
1062
        }
1063
    }
1064

1065
    private int addRef(Object value, int newRef, boolean isReplace) {
1066

1067
        return refs.put(value, newRef, isReplace);
7✔
1068
    }
1069

1070
    protected void registerRef(Object obj, boolean flag) {
1071
        addRef(obj, refCount++, flag);
×
1072
    }
×
1073

1074
    /**
1075
     * Starts the streaming message
1076
     *
1077
     * <p>A streaming message starts with 'P'</p>
1078
     *
1079
     * <pre>
1080
     * P x02 x00
1081
     * </pre>
1082
     */
1083
    public void writeStreamingObject(Object obj) throws IOException {
1084
        startPacket();
2✔
1085

1086
        writeObject(obj);
3✔
1087

1088
        endPacket();
2✔
1089
    }
1✔
1090

1091
    /**
1092
     * Starts a streaming packet
1093
     *
1094
     * <p>A streaming contains a set of chunks, ending with a zero chunk.
1095
     * Each chunk is a length followed by data where the length is
1096
     * encoded by (b1xxxxxxxx)* b0xxxxxxxx</p>
1097
     */
1098
    public void startPacket() throws IOException {
1099
        refs.clear();
3✔
1100
        refCount = 0;
3✔
1101

1102
        flushBuffer();
2✔
1103

1104
        isPacket = true;
3✔
1105
        offset = 4;
3✔
1106
        // 0x05 = binary
1107
        buffer[0] = (byte) 0x05;
5✔
1108
        buffer[1] = (byte) 0x55;
5✔
1109
        buffer[2] = (byte) 0x55;
5✔
1110
        buffer[3] = (byte) 0x55;
5✔
1111
    }
1✔
1112

1113
    public void endPacket() throws IOException {
1114
        int i = this.offset;
3✔
1115

1116
        if (os == null) {
3!
1117
            this.offset = 0;
×
1118
            return;
×
1119
        }
1120

1121
        int len = i - 4;
4✔
1122

1123
        if (len < 0x7e) {
3✔
1124
            buffer[2] = buffer[0];
9✔
1125
        } else {
1126
            buffer[1] = (byte) (0x7e);
5✔
1127
            buffer[2] = (byte) (len >> 8);
8✔
1128
        }
1129
        buffer[3] = (byte) (len);
6✔
1130

1131
        isPacket = false;
3✔
1132
        this.offset = 0;
3✔
1133

1134
        if (len < 0x7e) {
3✔
1135
            os.write(buffer, 2, i - 2);
10✔
1136
        } else {
1137
            os.write(buffer, 0, i);
7✔
1138
        }
1139
    }
1✔
1140

1141
    /**
1142
     * Prints a string to the stream, encoded as UTF-8 with preceeding length
1143
     *
1144
     * @param v the string to print.
1145
     */
1146
    public void printLenString(String v) throws IOException {
1147
        if (SIZE < offset + 16) {
6!
1148
            flushBuffer();
×
1149
        }
1150

1151
        if (v == null) {
2✔
1152
            buffer[offset++] = (byte) (0);
11✔
1153
            buffer[offset++] = (byte) (0);
12✔
1154
        } else {
1155
            int len = v.length();
3✔
1156
            buffer[offset++] = (byte) (len >> 8);
14✔
1157
            buffer[offset++] = (byte) (len);
12✔
1158

1159
            printString(v, 0, len);
5✔
1160
        }
1161
    }
1✔
1162

1163
    /**
1164
     * Prints a string to the stream, encoded as UTF-8
1165
     *
1166
     * @param v the string to print.
1167
     */
1168
    public void printString(String v) throws IOException {
1169
        printString(v, 0, v.length());
6✔
1170
    }
1✔
1171

1172
    /**
1173
     * Prints a string to the stream, encoded as UTF-8
1174
     *
1175
     * @param v the string to print.
1176
     */
1177
    public void printString(String v, int strOffset, int length) throws IOException {
1178
        int ioffset = this.offset;
3✔
1179

1180
        for (int i = 0; i < length; i++) {
7✔
1181
            if (SIZE <= ioffset + 16) {
5✔
1182
                this.offset = ioffset;
3✔
1183
                flushBuffer();
2✔
1184
                ioffset = this.offset;
3✔
1185
            }
1186

1187
            char ch = v.charAt(i + strOffset);
6✔
1188

1189
            if (ch < 0x80) {
3✔
1190
                buffer[ioffset++] = (byte) (ch);
8✔
1191
            } else if (ch < 0x800) {
3!
1192
                buffer[ioffset++] = (byte) (0xc0 + ((ch >> 6) & 0x1f));
×
1193
                buffer[ioffset++] = (byte) (0x80 + (ch & 0x3f));
×
1194
            } else {
1195
                buffer[ioffset++] = (byte) (0xe0 + ((ch >> 12) & 0xf));
13✔
1196
                buffer[ioffset++] = (byte) (0x80 + ((ch >> 6) & 0x3f));
13✔
1197
                buffer[ioffset++] = (byte) (0x80 + (ch & 0x3f));
11✔
1198
            }
1199
        }
1200

1201
        this.offset = ioffset;
3✔
1202
    }
1✔
1203

1204
    /**
1205
     * Prints a string to the stream, encoded as UTF-8
1206
     *
1207
     * @param v the string to print.
1208
     */
1209
    public void printString(char[] v, int strOffset, int length) throws IOException {
1210
        int ioffset = this.offset;
3✔
1211

1212
        for (int i = 0; i < length; i++) {
7✔
1213
            if (SIZE <= ioffset + 16) {
5✔
1214
                this.offset = ioffset;
3✔
1215
                flushBuffer();
2✔
1216
                ioffset = this.offset;
3✔
1217
            }
1218

1219
            char ch = v[i + strOffset];
6✔
1220

1221
            if (ch < 0x80) {
3!
1222
                buffer[ioffset++] = (byte) (ch);
8✔
1223
            } else if (ch < 0x800) {
×
1224
                buffer[ioffset++] = (byte) (0xc0 + ((ch >> 6) & 0x1f));
×
1225
                buffer[ioffset++] = (byte) (0x80 + (ch & 0x3f));
×
1226
            } else {
1227
                buffer[ioffset++] = (byte) (0xe0 + ((ch >> 12) & 0xf));
×
1228
                buffer[ioffset++] = (byte) (0x80 + ((ch >> 6) & 0x3f));
×
1229
                buffer[ioffset++] = (byte) (0x80 + (ch & 0x3f));
×
1230
            }
1231
        }
1232

1233
        this.offset = ioffset;
3✔
1234
    }
1✔
1235

1236
    protected final void flushIfFull() throws IOException {
1237

1238
        if (SIZE < offset + 32) {
6!
1239
            flushBuffer();
×
1240
        }
1241
    }
1✔
1242

1243
    @Override
1244
    public final void flush() throws IOException {
1245
        flushBuffer();
2✔
1246

1247
        if (os != null) {
3!
1248
            os.flush();
3✔
1249
        }
1250
    }
1✔
1251

1252
    public final void flushBuffer() throws IOException {
1253
        int ioffset = this.offset;
3✔
1254

1255
        if (!isPacket && ioffset > 0) {
5!
1256
            this.offset = 0;
3✔
1257
            if (os != null) {
3!
1258
                os.write(buffer, 0, ioffset);
8✔
1259
            }
1260
        } else if (isPacket && ioffset > 4) {
3!
1261
            int len = ioffset - 4;
×
1262

1263
            buffer[0] |= (byte) 0x80;
×
1264
            buffer[1] = (byte) (0x7e);
×
1265
            buffer[2] = (byte) (len >> 8);
×
1266
            buffer[3] = (byte) (len);
×
1267
            this.offset = 4;
×
1268

1269
            if (os != null) {
×
1270
                os.write(buffer, 0, ioffset);
×
1271
            }
1272

1273
            buffer[0] = (byte) 0x00;
×
1274
            buffer[1] = (byte) 0x56;
×
1275
            buffer[2] = (byte) 0x56;
×
1276
            buffer[3] = (byte) 0x56;
×
1277
        }
1278
    }
1✔
1279

1280
    @Override
1281
    public void close() throws IOException {
1282
        // hessian/3a8c
1283
        flush();
2✔
1284

1285
        OutputStream los = this.os;
3✔
1286
        this.os = null;
3✔
1287

1288
        if (los != null && isCloseStreamOnClose) {
5!
1289
            los.close();
×
1290
        }
1291
    }
1✔
1292

1293
    public void free() {
1294
        reset();
2✔
1295

1296
        os = null;
3✔
1297
        isCloseStreamOnClose = false;
3✔
1298
    }
1✔
1299

1300
    /**
1301
     * Resets the references for streaming.
1302
     */
1303
    @Override
1304
    public void resetReferences() {
1305
        refs.clear();
3✔
1306
        refCount = 0;
3✔
1307
    }
1✔
1308

1309
    /**
1310
     * Resets all counters and references
1311
     */
1312
    public void reset() {
1313
        refs.clear();
3✔
1314
        refCount = 0;
3✔
1315

1316
        classRefs.clear();
3✔
1317
        typeRefs = null;
3✔
1318
        offset = 0;
3✔
1319
        isPacket = false;
3✔
1320
        isUnshared = false;
3✔
1321
    }
1✔
1322

1323
    class BytesOutputStream extends OutputStream {
1324
        private int startOffset;
1325

1326
        BytesOutputStream() throws IOException {
5✔
1327
            if (SIZE < offset + 16) {
6!
1328
                HessianEncoder.this.flushBuffer();
×
1329
            }
1330

1331
            startOffset = offset;
4✔
1332
            offset += 3; // skip 'b' xNN xNN
6✔
1333
        }
1✔
1334

1335
        @Override
1336
        public void write(int ch) throws IOException {
1337
            if (SIZE <= offset) {
5!
1338
                int length = (offset - startOffset) - 3;
×
1339

1340
                buffer[startOffset] = (byte) BC_BINARY_CHUNK;
×
1341
                buffer[startOffset + 1] = (byte) (length >> 8);
×
1342
                buffer[startOffset + 2] = (byte) (length);
×
1343

1344
                HessianEncoder.this.flushBuffer();
×
1345

1346
                startOffset = offset;
×
1347
                offset += 3;
×
1348
            }
1349

1350
            buffer[offset++] = (byte) ch;
14✔
1351
        }
1✔
1352

1353
        @Override
1354
        public void write(byte[] buffer, int offset, int length) throws IOException {
1355
            while (length > 0) {
2✔
1356
                int sublen = SIZE - HessianEncoder.this.offset;
6✔
1357

1358
                if (length < sublen) {
3✔
1359
                    sublen = length;
2✔
1360
                }
1361

1362
                if (sublen > 0) {
2!
1363
                    System.arraycopy(buffer, offset, HessianEncoder.this.buffer, HessianEncoder.this.offset, sublen);
10✔
1364
                    HessianEncoder.this.offset += sublen;
7✔
1365
                }
1366

1367
                length -= sublen;
4✔
1368
                offset += sublen;
4✔
1369

1370
                if (SIZE <= HessianEncoder.this.offset) {
5✔
1371
                    int chunkLength = (HessianEncoder.this.offset - startOffset) - 3;
9✔
1372

1373
                    HessianEncoder.this.buffer[startOffset] = (byte) BC_BINARY_CHUNK;
7✔
1374
                    HessianEncoder.this.buffer[startOffset + 1] = (byte) (chunkLength >> 8);
12✔
1375
                    HessianEncoder.this.buffer[startOffset + 2] = (byte) (chunkLength);
10✔
1376

1377
                    HessianEncoder.this.flushBuffer();
3✔
1378

1379
                    startOffset = HessianEncoder.this.offset;
5✔
1380
                    HessianEncoder.this.offset += 3;
7✔
1381
                }
1382
            }
1✔
1383
        }
1✔
1384

1385
        @Override
1386
        public void close() throws IOException {
1387
            int i = this.startOffset;
3✔
1388
            this.startOffset = -1;
3✔
1389

1390
            if (i < 0) {
2!
1391
                return;
×
1392
            }
1393

1394
            int length = (offset - i) - 3;
8✔
1395

1396
            buffer[i] = (byte) 'B';
6✔
1397
            buffer[i + 1] = (byte) (length >> 8);
11✔
1398
            buffer[i + 2] = (byte) (length);
9✔
1399

1400
            HessianEncoder.this.flushBuffer();
3✔
1401
        }
1✔
1402
    }
1403
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc