1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 package org.apache.hc.core5.http.nio.entity;
28
29 import java.io.IOException;
30 import java.nio.ByteBuffer;
31 import java.nio.CharBuffer;
32 import java.nio.charset.Charset;
33 import java.nio.charset.CharsetDecoder;
34 import java.nio.charset.CoderResult;
35 import java.nio.charset.StandardCharsets;
36 import java.util.List;
37
38 import org.apache.hc.core5.http.Header;
39 import org.apache.hc.core5.http.HttpException;
40 import org.apache.hc.core5.http.config.CharCodingConfig;
41 import org.apache.hc.core5.http.nio.AsyncDataConsumer;
42 import org.apache.hc.core5.http.nio.CapacityChannel;
43 import org.apache.hc.core5.util.Args;
44
45
46
47
48
49
50 public abstract class AbstractCharDataConsumer implements AsyncDataConsumer {
51
52 protected static final int DEF_BUF_SIZE = 8192;
53 private static final ByteBuffer EMPTY_BIN = ByteBuffer.wrap(new byte[0]);
54
55 private final CharBuffer charBuffer;
56 private final CharCodingConfig charCodingConfig;
57
58 private volatile Charset charset;
59 private volatile CharsetDecoder charsetDecoder;
60
61 protected AbstractCharDataConsumer(final int bufSize, final CharCodingConfig charCodingConfig) {
62 this.charBuffer = CharBuffer.allocate(Args.positive(bufSize, "Buffer size"));
63 this.charCodingConfig = charCodingConfig != null ? charCodingConfig : CharCodingConfig.DEFAULT;
64 }
65
66 public AbstractCharDataConsumer() {
67 this(DEF_BUF_SIZE, CharCodingConfig.DEFAULT);
68 }
69
70
71
72
73
74 protected abstract int capacityIncrement();
75
76
77
78
79
80
81
82
83 protected abstract void data(CharBuffer src, boolean endOfStream) throws IOException;
84
85
86
87
88 protected abstract void completed() throws IOException;
89
90 protected final void setCharset(final Charset charset) {
91 this.charset = charset != null ? charset : charCodingConfig.getCharset();
92 this.charsetDecoder = null;
93 }
94
95 @Override
96 public final void updateCapacity(final CapacityChannel capacityChannel) throws IOException {
97 capacityChannel.update(capacityIncrement());
98 }
99
100 private void checkResult(final CoderResult result) throws IOException {
101 if (result.isError()) {
102 result.throwException();
103 }
104 }
105
106 private void doDecode(final boolean endOfStream) throws IOException {
107 charBuffer.flip();
108 data(charBuffer, endOfStream);
109 charBuffer.clear();
110 }
111
112 private CharsetDecoder getCharsetDecoder() {
113 if (charsetDecoder == null) {
114 Charset charset = this.charset;
115 if (charset == null) {
116 charset = charCodingConfig.getCharset();
117 }
118 if (charset == null) {
119 charset = StandardCharsets.US_ASCII;
120 }
121 charsetDecoder = charset.newDecoder();
122 if (charCodingConfig.getMalformedInputAction() != null) {
123 charsetDecoder.onMalformedInput(charCodingConfig.getMalformedInputAction());
124 }
125 if (charCodingConfig.getUnmappableInputAction() != null) {
126 charsetDecoder.onUnmappableCharacter(charCodingConfig.getUnmappableInputAction());
127 }
128 }
129 return charsetDecoder;
130 }
131
132 @Override
133 public final void consume(final ByteBuffer src) throws IOException {
134 final CharsetDecoder charsetDecoder = getCharsetDecoder();
135 while (src.hasRemaining()) {
136 checkResult(charsetDecoder.decode(src, charBuffer, false));
137 doDecode(false);
138 }
139 }
140
141 @Override
142 public final void streamEnd(final List<? extends Header> trailers) throws HttpException, IOException {
143 final CharsetDecoder charsetDecoder = getCharsetDecoder();
144 checkResult(charsetDecoder.decode(EMPTY_BIN, charBuffer, true));
145 doDecode(false);
146 checkResult(charsetDecoder.flush(charBuffer));
147 doDecode(true);
148 completed();
149 }
150
151 }