1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.apache.mina.filter.codec.textline;
21
22 import java.nio.ByteBuffer;
23 import java.nio.CharBuffer;
24 import java.nio.charset.CharacterCodingException;
25 import java.nio.charset.Charset;
26 import java.nio.charset.CharsetDecoder;
27
28 import org.apache.mina.core.buffer.BufferDataException;
29 import org.apache.mina.core.buffer.IoBuffer;
30 import org.apache.mina.core.session.AttributeKey;
31 import org.apache.mina.core.session.IoSession;
32 import org.apache.mina.filter.codec.ProtocolDecoder;
33 import org.apache.mina.filter.codec.ProtocolDecoderException;
34 import org.apache.mina.filter.codec.ProtocolDecoderOutput;
35 import org.apache.mina.filter.codec.RecoverableProtocolDecoderException;
36
37
38
39
40
41
42 public class TextLineDecoder implements ProtocolDecoder {
43 private static final AttributeKeyAttributeKey.html#AttributeKey">AttributeKey CONTEXT = new AttributeKey(TextLineDecoder.class, "context");
44
45 private final Charset charset;
46
47
48 private final LineDelimiter delimiter;
49
50
51 private IoBuffer delimBuf;
52
53
54 private int maxLineLength = 1024;
55
56
57 private int bufferLength = 128;
58
59
60
61
62
63 public TextLineDecoder() {
64 this(LineDelimiter.AUTO);
65 }
66
67
68
69
70
71
72
73 public TextLineDecoder(String delimiter) {
74 this(new LineDelimiter(delimiter));
75 }
76
77
78
79
80
81
82
83 public TextLineDecoder(LineDelimiter delimiter) {
84 this(Charset.defaultCharset(), delimiter);
85 }
86
87
88
89
90
91
92
93 public TextLineDecoder(Charset charset) {
94 this(charset, LineDelimiter.AUTO);
95 }
96
97
98
99
100
101
102
103
104 public TextLineDecoder(Charset charset, String delimiter) {
105 this(charset, new LineDelimiter(delimiter));
106 }
107
108
109
110
111
112
113
114
115 public TextLineDecoder(Charset charset, LineDelimiter delimiter) {
116 if (charset == null) {
117 throw new IllegalArgumentException("charset parameter shuld not be null");
118 }
119
120 if (delimiter == null) {
121 throw new IllegalArgumentException("delimiter parameter should not be null");
122 }
123
124 this.charset = charset;
125 this.delimiter = delimiter;
126
127
128 if (delimBuf == null) {
129 IoBuffer tmp = IoBuffer.allocate(2).setAutoExpand(true);
130
131 try {
132 tmp.putString(delimiter.getValue(), charset.newEncoder());
133 } catch (CharacterCodingException cce) {
134
135 }
136
137 tmp.flip();
138 delimBuf = tmp;
139 }
140 }
141
142
143
144
145
146
147
148 public int getMaxLineLength() {
149 return maxLineLength;
150 }
151
152
153
154
155
156
157
158
159
160 public void setMaxLineLength(int maxLineLength) {
161 if (maxLineLength <= 0) {
162 throw new IllegalArgumentException("maxLineLength (" + maxLineLength + ") should be a positive value");
163 }
164
165 this.maxLineLength = maxLineLength;
166 }
167
168
169
170
171
172
173
174 public void setBufferLength(int bufferLength) {
175 if (bufferLength <= 0) {
176 throw new IllegalArgumentException("bufferLength (" + maxLineLength + ") should be a positive value");
177
178 }
179
180 this.bufferLength = bufferLength;
181 }
182
183
184
185
186
187 public int getBufferLength() {
188 return bufferLength;
189 }
190
191
192
193
194 @Override
195 public void decode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception {
196 Context ctx = getContext(session);
197
198 if (LineDelimiter.AUTO.equals(delimiter)) {
199 decodeAuto(ctx, session, in, out);
200 } else {
201 decodeNormal(ctx, session, in, out);
202 }
203 }
204
205
206
207
208
209
210 private Context getContext(IoSession session) {
211 Context ctx;
212 ctx = (Context) session.getAttribute(CONTEXT);
213
214 if (ctx == null) {
215 ctx = new Context(bufferLength);
216 session.setAttribute(CONTEXT, ctx);
217 }
218
219 return ctx;
220 }
221
222
223
224
225 @Override
226 public void finishDecode(IoSession session, ProtocolDecoderOutput out) throws Exception {
227
228 }
229
230
231
232
233 @Override
234 public void dispose(IoSession session) throws Exception {
235 Context ctx = (Context) session.getAttribute(CONTEXT);
236
237 if (ctx != null) {
238 session.removeAttribute(CONTEXT);
239 }
240 }
241
242
243
244
245 private void decodeAuto(Context ctx, IoSession session, IoBuffer in, ProtocolDecoderOutput out)
246 throws CharacterCodingException, ProtocolDecoderException {
247 int matchCount = ctx.getMatchCount();
248
249
250 int oldPos = in.position();
251 int oldLimit = in.limit();
252
253 while (in.hasRemaining()) {
254 byte b = in.get();
255 boolean matched = false;
256
257 switch (b) {
258 case '\r':
259
260
261 matchCount++;
262 break;
263
264 case '\n':
265
266 matchCount++;
267 matched = true;
268 break;
269
270 default:
271 matchCount = 0;
272 }
273
274 if (matched) {
275
276 int pos = in.position();
277 in.limit(pos);
278 in.position(oldPos);
279
280 ctx.append(in);
281
282 in.limit(oldLimit);
283 in.position(pos);
284
285 if (ctx.getOverflowPosition() == 0) {
286 IoBuffer buf = ctx.getBuffer();
287 buf.flip();
288 buf.limit(buf.limit() - matchCount);
289
290 try {
291 byte[] data = new byte[buf.limit()];
292 buf.get(data);
293 CharsetDecoder decoder = ctx.getDecoder();
294
295 CharBuffer buffer = decoder.decode(ByteBuffer.wrap(data));
296 String str = buffer.toString();
297 writeText(session, str, out);
298 } finally {
299 buf.clear();
300 }
301 } else {
302 int overflowPosition = ctx.getOverflowPosition();
303 ctx.reset();
304 throw new RecoverableProtocolDecoderException("Line is too long: " + overflowPosition);
305 }
306
307 oldPos = pos;
308 matchCount = 0;
309 }
310 }
311
312
313 in.position(oldPos);
314 ctx.append(in);
315
316 ctx.setMatchCount(matchCount);
317 }
318
319
320
321
322 private void decodeNormal(Context ctx, IoSession session, IoBuffer in, ProtocolDecoderOutput out)
323 throws CharacterCodingException, ProtocolDecoderException {
324 int matchCount = ctx.getMatchCount();
325
326
327 int oldPos = in.position();
328 int oldLimit = in.limit();
329
330 while (in.hasRemaining()) {
331 byte b = in.get();
332
333 if (delimBuf.get(matchCount) == b) {
334 matchCount++;
335
336 if (matchCount == delimBuf.limit()) {
337
338 int pos = in.position();
339 in.limit(pos);
340 in.position(oldPos);
341
342 ctx.append(in);
343
344 in.limit(oldLimit);
345 in.position(pos);
346
347 if (ctx.getOverflowPosition() == 0) {
348 IoBuffer buf = ctx.getBuffer();
349 buf.flip();
350 buf.limit(buf.limit() - matchCount);
351
352 try {
353 writeText(session, buf.getString(ctx.getDecoder()), out);
354 } finally {
355 buf.clear();
356 }
357 } else {
358 int overflowPosition = ctx.getOverflowPosition();
359 ctx.reset();
360 throw new RecoverableProtocolDecoderException("Line is too long: " + overflowPosition);
361 }
362
363 oldPos = pos;
364 matchCount = 0;
365 }
366 } else {
367
368 in.position(Math.max(0, in.position() - matchCount));
369 matchCount = 0;
370 }
371 }
372
373
374 in.position(oldPos);
375 ctx.append(in);
376
377 ctx.setMatchCount(matchCount);
378 }
379
380
381
382
383
384
385
386
387
388
389 protected void writeText(IoSession session, String text, ProtocolDecoderOutput out) {
390 out.write(text);
391 }
392
393
394
395
396
397
398
399
400 private class Context {
401
402 private final CharsetDecoder decoder;
403
404
405 private final IoBuffer buf;
406
407
408 private int matchCount = 0;
409
410
411 private int overflowPosition = 0;
412
413
414 private Context(int bufferLength) {
415 decoder = charset.newDecoder();
416 buf = IoBuffer.allocate(bufferLength).setAutoExpand(true);
417 }
418
419 public CharsetDecoder getDecoder() {
420 return decoder;
421 }
422
423 public IoBuffer getBuffer() {
424 return buf;
425 }
426
427 public int getOverflowPosition() {
428 return overflowPosition;
429 }
430
431 public int getMatchCount() {
432 return matchCount;
433 }
434
435 public void setMatchCount(int matchCount) {
436 this.matchCount = matchCount;
437 }
438
439 public void reset() {
440 overflowPosition = 0;
441 matchCount = 0;
442 decoder.reset();
443 }
444
445 public void append(IoBuffer in) {
446 if (overflowPosition != 0) {
447 discard(in);
448 } else if (buf.position() > maxLineLength - in.remaining()) {
449 overflowPosition = buf.position();
450 buf.clear();
451 discard(in);
452 } else {
453 getBuffer().put(in);
454 }
455 }
456
457 private void discard(IoBuffer in) {
458 if (Integer.MAX_VALUE - in.remaining() < overflowPosition) {
459 overflowPosition = Integer.MAX_VALUE;
460 } else {
461 overflowPosition += in.remaining();
462 }
463
464 in.position(in.limit());
465 }
466 }
467 }