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