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

wuwen5 / hessian / 17882005206

20 Sep 2025 01:06PM UTC coverage: 70.892% (+1.5%) from 69.377%
17882005206

push

github

web-flow
refactor: code clean (#50)

* refactor: code clean

* refactor: code clean,remove hessian1 code

* refactor: fix HessianDebugState

1780 of 2691 branches covered (66.15%)

Branch coverage included in aggregate %.

4226 of 5781 relevant lines covered (73.1%)

3.17 hits per line

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

82.77
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();
10✔
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();
10✔
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
        this.os = 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
        HessianSerializer 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;
11✔
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) BC_OBJECT;
11✔
308
                writeInt(ref);
3✔
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
     * <pre>
332
     * <code>
333
     * type ::= string
334
     *      ::= int
335
     * </code>
336
     * </pre>
337
     */
338
    private void writeType(String type) throws IOException {
339
        flushIfFull();
2✔
340

341
        int len = type.length();
3✔
342
        if (len == 0) {
2!
343
            throw new IllegalArgumentException("empty type is not allowed");
×
344
        }
345

346
        if (typeRefs == null) {
3✔
347
            typeRefs = new HashMap<>(32);
6✔
348
        }
349

350
        Integer typeRefV = typeRefs.get(type);
6✔
351

352
        if (typeRefV != null) {
2✔
353
            int typeRef = typeRefV;
3✔
354

355
            writeInt(typeRef);
3✔
356
        } else {
1✔
357
            typeRefs.put(type, typeRefs.size());
9✔
358

359
            writeString(type);
3✔
360
        }
361
    }
1✔
362

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

381
        if (value) {
2✔
382
            buffer[offset++] = (byte) 'T';
12✔
383
        } else {
384
            buffer[offset++] = (byte) 'F';
11✔
385
        }
386
    }
1✔
387

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

403
        if (SIZE <= i + 16) {
5!
404
            flushBuffer();
×
405
            i = this.offset;
×
406
        }
407

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

425
        this.offset = i;
3✔
426
    }
1✔
427

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

443
        if (SIZE <= i + 16) {
5!
444
            flushBuffer();
×
445
            i = this.offset;
×
446
        }
447

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

464
            i += 5;
2✔
465
        } else {
466
            buffer[i] = (byte) 'L';
5✔
467
            buffer[i + 1] = (byte) (value >> 56);
11✔
468
            buffer[i + 2] = (byte) (value >> 48);
11✔
469
            buffer[i + 3] = (byte) (value >> 40);
11✔
470
            buffer[i + 4] = (byte) (value >> 32);
11✔
471
            buffer[i + 5] = (byte) (value >> 24);
11✔
472
            buffer[i + 6] = (byte) (value >> 16);
11✔
473
            buffer[i + 7] = (byte) (value >> 8);
11✔
474
            buffer[i + 8] = (byte) (value);
9✔
475

476
            i += 9;
1✔
477
        }
478

479
        this.offset = i;
3✔
480
    }
1✔
481

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

498
        if (SIZE <= i + 16) {
5!
499
            flushBuffer();
×
500
            i = this.offset;
×
501
        }
502

503
        int intValue = (int) value;
3✔
504

505
        if (intValue == value) {
5✔
506
            if (intValue == 0) {
2✔
507
                buffer[i++] = (byte) BC_DOUBLE_ZERO;
6✔
508

509
                this.offset = i;
3✔
510

511
                return;
1✔
512
            } else if (intValue == 1) {
3✔
513
                buffer[i++] = (byte) BC_DOUBLE_ONE;
6✔
514

515
                this.offset = i;
3✔
516

517
                return;
1✔
518
            } else if (-0x80 <= intValue && intValue < 0x80) {
6✔
519
                buffer[i++] = (byte) BC_DOUBLE_BYTE;
6✔
520
                buffer[i++] = (byte) intValue;
7✔
521

522
                this.offset = i;
3✔
523

524
                return;
1✔
525
            } else if (-0x8000 <= intValue && intValue < 0x8000) {
6!
526
                buffer[i] = (byte) BC_DOUBLE_SHORT;
5✔
527
                buffer[i + 1] = (byte) (intValue >> 8);
10✔
528
                buffer[i + 2] = (byte) intValue;
8✔
529

530
                this.offset = i + 3;
5✔
531

532
                return;
1✔
533
            }
534
        }
535

536
        int mills = (int) (value * 1000);
5✔
537

538
        if (0.001 * mills == value) {
7✔
539
            buffer[i] = (byte) (BC_DOUBLE_MILL);
5✔
540
            buffer[i + 1] = (byte) (mills >> 24);
10✔
541
            buffer[i + 2] = (byte) (mills >> 16);
10✔
542
            buffer[i + 3] = (byte) (mills >> 8);
10✔
543
            buffer[i + 4] = (byte) (mills);
8✔
544

545
            this.offset = i + 5;
5✔
546

547
            return;
1✔
548
        }
549

550
        long bits = Double.doubleToLongBits(value);
3✔
551

552
        buffer[i] = (byte) 'D';
5✔
553
        buffer[i + 1] = (byte) (bits >> 56);
11✔
554
        buffer[i + 2] = (byte) (bits >> 48);
11✔
555
        buffer[i + 3] = (byte) (bits >> 40);
11✔
556
        buffer[i + 4] = (byte) (bits >> 32);
11✔
557
        buffer[i + 5] = (byte) (bits >> 24);
11✔
558
        buffer[i + 6] = (byte) (bits >> 16);
11✔
559
        buffer[i + 7] = (byte) (bits >> 8);
11✔
560
        buffer[i + 8] = (byte) (bits);
9✔
561

562
        this.offset = i + 9;
5✔
563
    }
1✔
564

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

582
        int i = this.offset;
3✔
583

584
        if (time % 60000L == 0) {
6✔
585
            // compact date ::= x65 b3 b2 b1 b0
586

587
            long minutes = time / 60000L;
4✔
588

589
            if ((minutes >> 31) == 0 || (minutes >> 31) == -1) {
6!
590
                buffer[i++] = (byte) BC_DATE_MINUTE;
6✔
591
                buffer[i++] = ((byte) (minutes >> 24));
10✔
592
                buffer[i++] = ((byte) (minutes >> 16));
10✔
593
                buffer[i++] = ((byte) (minutes >> 8));
10✔
594
                buffer[i++] = ((byte) (minutes >> 0));
10✔
595

596
                this.offset = i;
3✔
597
                return;
1✔
598
            }
599
        }
600

601
        buffer[i++] = (byte) BC_DATE;
6✔
602
        buffer[i++] = ((byte) (time >> 56));
10✔
603
        buffer[i++] = ((byte) (time >> 48));
10✔
604
        buffer[i++] = ((byte) (time >> 40));
10✔
605
        buffer[i++] = ((byte) (time >> 32));
10✔
606
        buffer[i++] = ((byte) (time >> 24));
10✔
607
        buffer[i++] = ((byte) (time >> 16));
10✔
608
        buffer[i++] = ((byte) (time >> 8));
10✔
609
        buffer[i++] = ((byte) (time));
8✔
610

611
        this.offset = i;
3✔
612
    }
1✔
613

614
    /**
615
     * Writes a null value to the stream.
616
     * The null will be written with the following syntax
617
     * <pre>
618
     * <code>
619
     * N
620
     * </code>
621
     * </pre>
622
     */
623
    @Override
624
    public void writeNull() throws IOException {
625
        int i = this.offset;
3✔
626

627
        if (SIZE <= i + 16) {
5!
628
            flushBuffer();
×
629
            i = this.offset;
×
630
        }
631

632
        buffer[i++] = BC_NULL;
6✔
633

634
        this.offset = i;
3✔
635
    }
1✔
636

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

656
        if (SIZE <= i + 16) {
5!
657
            flushBuffer();
×
658
            i = this.offset;
×
659
        }
660

661
        if (value == null) {
2✔
662
            buffer[i++] = (byte) BC_NULL;
6✔
663

664
            this.offset = i;
4✔
665
        } else {
666
            int length = value.length();
3✔
667
            int strOffset = 0;
2✔
668

669
            while (length > 0x8000) {
3✔
670
                int sublen = 0x8000;
2✔
671

672
                i = this.offset;
3✔
673

674
                if (SIZE <= i + 16) {
5!
675
                    flushBuffer();
×
676
                    i = this.offset;
×
677
                }
678

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

682
                if (0xd800 <= tail && tail <= 0xdbff) {
3!
683
                    sublen--;
×
684
                }
685

686
                buffer[i] = (byte) BC_STRING_CHUNK;
5✔
687
                buffer[i + 1] = (byte) (sublen >> 8);
10✔
688
                buffer[i + 2] = (byte) (sublen);
8✔
689

690
                this.offset = i + 3;
5✔
691

692
                printString(value, strOffset, sublen);
5✔
693

694
                length -= sublen;
4✔
695
                strOffset += sublen;
4✔
696
            }
1✔
697

698
            i = this.offset;
3✔
699

700
            if (SIZE <= i + 16) {
5!
701
                flushBuffer();
×
702
                i = this.offset;
×
703
            }
704

705
            if (length <= STRING_DIRECT_MAX) {
3✔
706
                buffer[i++] = (byte) (BC_STRING_DIRECT + length);
10✔
707
            } else if (length <= STRING_SHORT_MAX) {
3✔
708
                buffer[i++] = (byte) (BC_STRING_SHORT + (length >> 8));
11✔
709
                buffer[i++] = (byte) (length);
8✔
710
            } else {
711
                buffer[i++] = (byte) ('S');
6✔
712
                buffer[i++] = (byte) (length >> 8);
9✔
713
                buffer[i++] = (byte) (length);
7✔
714
            }
715

716
            this.offset = i;
3✔
717

718
            printString(value, strOffset, length);
5✔
719
        }
720
    }
1✔
721

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

743
            this.buffer[this.offset++] = (byte) (BC_NULL);
12✔
744
        } else {
745
            while (length > 0x8000) {
3✔
746
                int sublen = 0x8000;
2✔
747

748
                if (SIZE < this.offset + 16) {
6!
749
                    flushBuffer();
×
750
                }
751

752
                // chunk can't end in high surrogate
753
                char tail = buffer[offset + sublen - 1];
8✔
754

755
                if (0xd800 <= tail && tail <= 0xdbff) {
3!
756
                    sublen--;
×
757
                }
758

759
                this.buffer[this.offset++] = (byte) BC_STRING_CHUNK;
11✔
760
                this.buffer[this.offset++] = (byte) (sublen >> 8);
14✔
761
                this.buffer[this.offset++] = (byte) (sublen);
12✔
762

763
                printString(buffer, offset, sublen);
5✔
764

765
                length -= sublen;
4✔
766
                offset += sublen;
4✔
767
            }
1✔
768

769
            if (SIZE < this.offset + 16) {
6!
770
                flushBuffer();
×
771
            }
772

773
            if (length <= STRING_DIRECT_MAX) {
3✔
774
                this.buffer[this.offset++] = (byte) (BC_STRING_DIRECT + length);
15✔
775
            } else if (length <= STRING_SHORT_MAX) {
3✔
776
                this.buffer[this.offset++] = (byte) (BC_STRING_SHORT + (length >> 8));
16✔
777
                this.buffer[this.offset++] = (byte) length;
13✔
778
            } else {
779
                this.buffer[this.offset++] = (byte) ('S');
11✔
780
                this.buffer[this.offset++] = (byte) (length >> 8);
14✔
781
                this.buffer[this.offset++] = (byte) (length);
12✔
782
            }
783

784
            printString(buffer, offset, length);
5✔
785
        }
786
    }
1✔
787

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

809
            this.buffer[offset++] = BC_NULL;
12✔
810
        } else {
811
            writeBytes(buffer, 0, buffer.length);
6✔
812
        }
813
    }
1✔
814

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

835
            this.buffer[this.offset++] = (byte) BC_NULL;
12✔
836
        } else {
837
            while (SIZE - this.offset - 3 < length) {
8✔
838
                int sublen = SIZE - this.offset - 3;
7✔
839

840
                if (sublen < 16) {
3!
841
                    flushBuffer();
×
842

843
                    sublen = SIZE - this.offset - 3;
×
844

845
                    if (length < sublen) {
×
846
                        sublen = length;
×
847
                    }
848
                }
849

850
                this.buffer[this.offset++] = (byte) BC_BINARY_CHUNK;
11✔
851
                this.buffer[this.offset++] = (byte) (sublen >> 8);
14✔
852
                this.buffer[this.offset++] = (byte) sublen;
12✔
853

854
                System.arraycopy(buffer, offset, this.buffer, this.offset, sublen);
8✔
855
                this.offset += sublen;
6✔
856

857
                length -= sublen;
4✔
858
                offset += sublen;
4✔
859

860
                flushBuffer();
2✔
861
            }
1✔
862

863
            if (SIZE < this.offset + 16) {
6!
864
                flushBuffer();
×
865
            }
866

867
            if (length <= BINARY_DIRECT_MAX) {
3✔
868
                this.buffer[this.offset++] = (byte) (BC_BINARY_DIRECT + length);
15✔
869
            } else if (length <= BINARY_SHORT_MAX) {
3✔
870
                this.buffer[this.offset++] = (byte) (BC_BINARY_SHORT + (length >> 8));
16✔
871
                this.buffer[this.offset++] = (byte) (length);
13✔
872
            } else {
873
                this.buffer[this.offset++] = (byte) 'B';
11✔
874
                this.buffer[this.offset++] = (byte) (length >> 8);
14✔
875
                this.buffer[this.offset++] = (byte) (length);
12✔
876
            }
877

878
            System.arraycopy(buffer, offset, this.buffer, this.offset, length);
8✔
879

880
            this.offset += length;
6✔
881
        }
882
    }
1✔
883

884
    /**
885
     * Writes a byte buffer to the stream.
886
     * <pre>
887
     * <code>
888
     * b b16 b18 bytes
889
     * </code>
890
     * </pre>
891
     */
892
    @Override
893
    public void writeByteBufferPart(byte[] buffer, int offset, int length) throws IOException {
894
        while (length > 0) {
2✔
895
            flushIfFull();
2✔
896

897
            int sublen = this.buffer.length - this.offset;
7✔
898

899
            if (length < sublen) {
3!
900
                sublen = length;
2✔
901
            }
902

903
            this.buffer[this.offset++] = BC_BINARY_CHUNK;
11✔
904
            this.buffer[this.offset++] = (byte) (sublen >> 8);
14✔
905
            this.buffer[this.offset++] = (byte) sublen;
12✔
906

907
            System.arraycopy(buffer, offset, this.buffer, this.offset, sublen);
8✔
908

909
            this.offset += sublen;
6✔
910
            length -= sublen;
4✔
911
            offset += sublen;
4✔
912
        }
1✔
913
    }
1✔
914

915
    /**
916
     * Writes a byte buffer to the stream.
917
     * <pre>
918
     * <code>
919
     * b b16 b18 bytes
920
     * </code>
921
     * </pre>
922
     */
923
    @Override
924
    public void writeByteBufferEnd(byte[] buffer, int offset, int length) throws IOException {
925
        writeBytes(buffer, offset, length);
5✔
926
    }
1✔
927

928
    /**
929
     * Returns an output stream to write binary data.
930
     */
931
    public OutputStream getBytesOutputStream() throws IOException {
932
        return new BytesOutputStream();
5✔
933
    }
934

935
    /**
936
     * Writes a full output stream.
937
     */
938
    @Override
939
    public void writeByteStream(InputStream is) throws IOException {
940
        while (true) {
941
            int len = SIZE - offset - 3;
7✔
942

943
            if (len < 16) {
3✔
944
                flushBuffer();
2✔
945
                len = SIZE - offset - 3;
7✔
946
            }
947

948
            len = is.read(buffer, offset + 3, len);
10✔
949

950
            if (len <= 0) {
2✔
951
                buffer[offset++] = BC_BINARY_DIRECT;
11✔
952
                return;
1✔
953
            }
954

955
            buffer[offset] = (byte) BC_BINARY_CHUNK;
6✔
956
            buffer[offset + 1] = (byte) (len >> 8);
11✔
957
            buffer[offset + 2] = (byte) (len);
9✔
958

959
            offset += len + 3;
8✔
960
        }
1✔
961
    }
962

963
    /**
964
     * Writes a reference.
965
     * <pre>
966
     * x51 &lt;int&gt;
967
     * </pre>
968
     *
969
     * @param value the integer value to write.
970
     */
971
    @Override
972
    protected void writeRef(int value) throws IOException {
973
        if (SIZE < offset + 16) {
6!
974
            flushBuffer();
×
975
        }
976

977
        buffer[offset++] = (byte) BC_REF;
11✔
978

979
        writeInt(value);
3✔
980
    }
1✔
981

982
    /**
983
     * If the object has already been written, just write its ref.
984
     *
985
     * @return true if we're writing a ref.
986
     */
987
    @Override
988
    public boolean addRef(Object object) throws IOException {
989
        if (isUnshared) {
3✔
990
            refCount++;
6✔
991
            return false;
2✔
992
        }
993

994
        int newRef = refCount;
3✔
995

996
        int ref = addRef(object, newRef, false);
6✔
997

998
        if (ref != newRef) {
3✔
999
            writeRef(ref);
3✔
1000

1001
            return true;
2✔
1002
        } else {
1003
            refCount++;
6✔
1004

1005
            return false;
2✔
1006
        }
1007
    }
1008

1009
    @Override
1010
    public int getRef(Object obj) {
1011
        if (isUnshared) {
3!
1012
            return -1;
×
1013
        }
1014

1015
        return refs.get(obj);
5✔
1016
    }
1017

1018
    /**
1019
     * Removes a reference.
1020
     */
1021
    public boolean removeRef(Object obj) {
1022
        if (isUnshared) {
×
1023
            return false;
×
1024
        } else {
1025
            refs.remove(obj);
×
1026

1027
            return true;
×
1028
        }
1029
    }
1030

1031
    /**
1032
     * Replaces a reference from one object to another.
1033
     */
1034
    @Override
1035
    public boolean replaceRef(Object oldRef, Object newRef) {
1036
        if (isUnshared) {
3!
1037
            return false;
×
1038
        }
1039

1040
        int value = refs.get(oldRef);
5✔
1041

1042
        if (value >= 0) {
2!
1043
            addRef(newRef, value, true);
6✔
1044

1045
            refs.remove(oldRef);
4✔
1046

1047
            return true;
2✔
1048
        } else {
1049
            return false;
×
1050
        }
1051
    }
1052

1053
    private int addRef(Object value, int newRef, boolean isReplace) {
1054

1055
        return refs.put(value, newRef, isReplace);
7✔
1056
    }
1057

1058
    protected void registerRef(Object obj, boolean flag) {
1059
        addRef(obj, refCount++, flag);
×
1060
    }
×
1061

1062
    /**
1063
     * Starts the streaming message
1064
     *
1065
     * <p>A streaming message starts with 'P'</p>
1066
     *
1067
     * <pre>
1068
     * P x02 x00
1069
     * </pre>
1070
     */
1071
    public void writeStreamingObject(Object obj) throws IOException {
1072
        startPacket();
2✔
1073

1074
        writeObject(obj);
3✔
1075

1076
        endPacket();
2✔
1077
    }
1✔
1078

1079
    /**
1080
     * Starts a streaming packet
1081
     *
1082
     * <p>A streaming contains a set of chunks, ending with a zero chunk.
1083
     * Each chunk is a length followed by data where the length is
1084
     * encoded by (b1xxxxxxxx)* b0xxxxxxxx</p>
1085
     */
1086
    public void startPacket() throws IOException {
1087
        refs.clear();
3✔
1088
        refCount = 0;
3✔
1089

1090
        flushBuffer();
2✔
1091

1092
        isPacket = true;
3✔
1093
        offset = 4;
3✔
1094
        // 0x05 = binary
1095
        buffer[0] = (byte) 0x05;
5✔
1096
        buffer[1] = (byte) 0x55;
5✔
1097
        buffer[2] = (byte) 0x55;
5✔
1098
        buffer[3] = (byte) 0x55;
5✔
1099
    }
1✔
1100

1101
    public void endPacket() throws IOException {
1102
        int i = this.offset;
3✔
1103

1104
        if (os == null) {
3!
1105
            this.offset = 0;
×
1106
            return;
×
1107
        }
1108

1109
        int len = i - 4;
4✔
1110

1111
        if (len < 0x7e) {
3✔
1112
            buffer[2] = buffer[0];
9✔
1113
        } else {
1114
            buffer[1] = (byte) (0x7e);
5✔
1115
            buffer[2] = (byte) (len >> 8);
8✔
1116
        }
1117
        buffer[3] = (byte) (len);
6✔
1118

1119
        isPacket = false;
3✔
1120
        this.offset = 0;
3✔
1121

1122
        if (len < 0x7e) {
3✔
1123
            os.write(buffer, 2, i - 2);
10✔
1124
        } else {
1125
            os.write(buffer, 0, i);
7✔
1126
        }
1127
    }
1✔
1128

1129
    /**
1130
     * Prints a string to the stream, encoded as UTF-8 with preceeding length
1131
     *
1132
     * @param v the string to print.
1133
     */
1134
    public void printLenString(String v) throws IOException {
1135
        if (SIZE < offset + 16) {
6!
1136
            flushBuffer();
×
1137
        }
1138

1139
        if (v == null) {
2✔
1140
            buffer[offset++] = (byte) (0);
11✔
1141
            buffer[offset++] = (byte) (0);
12✔
1142
        } else {
1143
            int len = v.length();
3✔
1144
            buffer[offset++] = (byte) (len >> 8);
14✔
1145
            buffer[offset++] = (byte) (len);
12✔
1146

1147
            printString(v, 0, len);
5✔
1148
        }
1149
    }
1✔
1150

1151
    /**
1152
     * Prints a string to the stream, encoded as UTF-8
1153
     *
1154
     * @param v the string to print.
1155
     */
1156
    public void printString(String v) throws IOException {
1157
        printString(v, 0, v.length());
6✔
1158
    }
1✔
1159

1160
    /**
1161
     * Prints a string to the stream, encoded as UTF-8
1162
     *
1163
     * @param v the string to print.
1164
     */
1165
    public void printString(String v, int strOffset, int length) throws IOException {
1166
        int ioffset = this.offset;
3✔
1167

1168
        for (int i = 0; i < length; i++) {
7✔
1169
            if (SIZE <= ioffset + 16) {
5✔
1170
                this.offset = ioffset;
3✔
1171
                flushBuffer();
2✔
1172
                ioffset = this.offset;
3✔
1173
            }
1174

1175
            char ch = v.charAt(i + strOffset);
6✔
1176

1177
            if (ch < 0x80) {
3✔
1178
                buffer[ioffset++] = (byte) (ch);
8✔
1179
            } else if (ch < 0x800) {
3!
1180
                buffer[ioffset++] = (byte) (0xc0 + ((ch >> 6) & 0x1f));
×
1181
                buffer[ioffset++] = (byte) (0x80 + (ch & 0x3f));
×
1182
            } else {
1183
                buffer[ioffset++] = (byte) (0xe0 + ((ch >> 12) & 0xf));
13✔
1184
                buffer[ioffset++] = (byte) (0x80 + ((ch >> 6) & 0x3f));
13✔
1185
                buffer[ioffset++] = (byte) (0x80 + (ch & 0x3f));
11✔
1186
            }
1187
        }
1188

1189
        this.offset = ioffset;
3✔
1190
    }
1✔
1191

1192
    /**
1193
     * Prints a string to the stream, encoded as UTF-8
1194
     *
1195
     * @param v the string to print.
1196
     */
1197
    public void printString(char[] v, int strOffset, int length) throws IOException {
1198
        int ioffset = this.offset;
3✔
1199

1200
        for (int i = 0; i < length; i++) {
7✔
1201
            if (SIZE <= ioffset + 16) {
5✔
1202
                this.offset = ioffset;
3✔
1203
                flushBuffer();
2✔
1204
                ioffset = this.offset;
3✔
1205
            }
1206

1207
            char ch = v[i + strOffset];
6✔
1208

1209
            if (ch < 0x80) {
3!
1210
                buffer[ioffset++] = (byte) (ch);
8✔
1211
            } else if (ch < 0x800) {
×
1212
                buffer[ioffset++] = (byte) (0xc0 + ((ch >> 6) & 0x1f));
×
1213
                buffer[ioffset++] = (byte) (0x80 + (ch & 0x3f));
×
1214
            } else {
1215
                buffer[ioffset++] = (byte) (0xe0 + ((ch >> 12) & 0xf));
×
1216
                buffer[ioffset++] = (byte) (0x80 + ((ch >> 6) & 0x3f));
×
1217
                buffer[ioffset++] = (byte) (0x80 + (ch & 0x3f));
×
1218
            }
1219
        }
1220

1221
        this.offset = ioffset;
3✔
1222
    }
1✔
1223

1224
    protected final void flushIfFull() throws IOException {
1225

1226
        if (SIZE < offset + 32) {
6!
1227
            flushBuffer();
×
1228
        }
1229
    }
1✔
1230

1231
    @Override
1232
    public final void flush() throws IOException {
1233
        flushBuffer();
2✔
1234

1235
        if (os != null) {
3!
1236
            os.flush();
3✔
1237
        }
1238
    }
1✔
1239

1240
    public final void flushBuffer() throws IOException {
1241
        int ioffset = this.offset;
3✔
1242

1243
        if (!isPacket && ioffset > 0) {
5!
1244
            this.offset = 0;
3✔
1245
            if (os != null) {
3!
1246
                os.write(buffer, 0, ioffset);
8✔
1247
            }
1248
        } else if (isPacket && ioffset > 4) {
3!
1249
            int len = ioffset - 4;
×
1250

1251
            buffer[0] |= (byte) 0x80;
×
1252
            buffer[1] = (byte) (0x7e);
×
1253
            buffer[2] = (byte) (len >> 8);
×
1254
            buffer[3] = (byte) (len);
×
1255
            this.offset = 4;
×
1256

1257
            if (os != null) {
×
1258
                os.write(buffer, 0, ioffset);
×
1259
            }
1260

1261
            buffer[0] = (byte) 0x00;
×
1262
            buffer[1] = (byte) 0x56;
×
1263
            buffer[2] = (byte) 0x56;
×
1264
            buffer[3] = (byte) 0x56;
×
1265
        }
1266
    }
1✔
1267

1268
    @Override
1269
    public void close() throws IOException {
1270
        // hessian/3a8c
1271
        flush();
2✔
1272

1273
        OutputStream los = this.os;
3✔
1274
        this.os = null;
3✔
1275

1276
        if (los != null && isCloseStreamOnClose) {
5!
1277
            los.close();
×
1278
        }
1279
    }
1✔
1280

1281
    public void free() {
1282
        reset();
2✔
1283

1284
        os = null;
3✔
1285
        isCloseStreamOnClose = false;
3✔
1286
    }
1✔
1287

1288
    /**
1289
     * Resets the references for streaming.
1290
     */
1291
    @Override
1292
    public void resetReferences() {
1293
        refs.clear();
3✔
1294
        refCount = 0;
3✔
1295
    }
1✔
1296

1297
    /**
1298
     * Resets all counters and references
1299
     */
1300
    public void reset() {
1301
        refs.clear();
3✔
1302
        refCount = 0;
3✔
1303

1304
        classRefs.clear();
3✔
1305
        typeRefs = null;
3✔
1306
        offset = 0;
3✔
1307
        isPacket = false;
3✔
1308
        isUnshared = false;
3✔
1309
    }
1✔
1310

1311
    class BytesOutputStream extends OutputStream {
1312
        private int startOffset;
1313

1314
        BytesOutputStream() throws IOException {
5✔
1315
            if (SIZE < offset + 16) {
6!
1316
                HessianEncoder.this.flushBuffer();
×
1317
            }
1318

1319
            startOffset = offset;
4✔
1320
            offset += 3; // skip 'b' xNN xNN
6✔
1321
        }
1✔
1322

1323
        @Override
1324
        public void write(int ch) throws IOException {
1325
            if (SIZE <= offset) {
5!
1326
                int length = (offset - startOffset) - 3;
×
1327

1328
                buffer[startOffset] = (byte) BC_BINARY_CHUNK;
×
1329
                buffer[startOffset + 1] = (byte) (length >> 8);
×
1330
                buffer[startOffset + 2] = (byte) (length);
×
1331

1332
                HessianEncoder.this.flushBuffer();
×
1333

1334
                startOffset = offset;
×
1335
                offset += 3;
×
1336
            }
1337

1338
            buffer[offset++] = (byte) ch;
14✔
1339
        }
1✔
1340

1341
        @Override
1342
        public void write(byte[] buffer, int offset, int length) throws IOException {
1343
            while (length > 0) {
2✔
1344
                int sublen = SIZE - HessianEncoder.this.offset;
6✔
1345

1346
                if (length < sublen) {
3✔
1347
                    sublen = length;
2✔
1348
                }
1349

1350
                if (sublen > 0) {
2!
1351
                    System.arraycopy(buffer, offset, HessianEncoder.this.buffer, HessianEncoder.this.offset, sublen);
10✔
1352
                    HessianEncoder.this.offset += sublen;
7✔
1353
                }
1354

1355
                length -= sublen;
4✔
1356
                offset += sublen;
4✔
1357

1358
                if (SIZE <= HessianEncoder.this.offset) {
5✔
1359
                    int chunkLength = (HessianEncoder.this.offset - startOffset) - 3;
9✔
1360

1361
                    HessianEncoder.this.buffer[startOffset] = (byte) BC_BINARY_CHUNK;
7✔
1362
                    HessianEncoder.this.buffer[startOffset + 1] = (byte) (chunkLength >> 8);
12✔
1363
                    HessianEncoder.this.buffer[startOffset + 2] = (byte) (chunkLength);
10✔
1364

1365
                    HessianEncoder.this.flushBuffer();
3✔
1366

1367
                    startOffset = HessianEncoder.this.offset;
5✔
1368
                    HessianEncoder.this.offset += 3;
7✔
1369
                }
1370
            }
1✔
1371
        }
1✔
1372

1373
        @Override
1374
        public void close() throws IOException {
1375
            int i = this.startOffset;
3✔
1376
            this.startOffset = -1;
3✔
1377

1378
            if (i < 0) {
2!
1379
                return;
×
1380
            }
1381

1382
            int length = (offset - i) - 3;
8✔
1383

1384
            buffer[i] = (byte) 'B';
6✔
1385
            buffer[i + 1] = (byte) (length >> 8);
11✔
1386
            buffer[i + 2] = (byte) (length);
9✔
1387

1388
            HessianEncoder.this.flushBuffer();
3✔
1389
        }
1✔
1390
    }
1391
}
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