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 (" + maxLineLength + ") should be a positive value");
147 }
148
149 this.maxLineLength = maxLineLength;
150 }
151
152
153
154
155
156
157
158 public void setBufferLength(int bufferLength) {
159 if (bufferLength <= 0) {
160 throw new IllegalArgumentException("bufferLength (" + maxLineLength + ") should be a positive value");
161
162 }
163
164 this.bufferLength = bufferLength;
165 }
166
167
168
169
170
171 public int getBufferLength() {
172 return bufferLength;
173 }
174
175
176
177
178 public void decode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception {
179 Context ctx = getContext(session);
180
181 if (LineDelimiter.AUTO.equals(delimiter)) {
182 decodeAuto(ctx, session, in, out);
183 } else {
184 decodeNormal(ctx, session, in, out);
185 }
186 }
187
188
189
190
191 private Context getContext(IoSession session) {
192 Context ctx;
193 ctx = (Context) session.getAttribute(CONTEXT);
194
195 if (ctx == null) {
196 ctx = new Context(bufferLength);
197 session.setAttribute(CONTEXT, ctx);
198 }
199
200 return ctx;
201 }
202
203
204
205
206 public void finishDecode(IoSession session, ProtocolDecoderOutput out) throws Exception {
207
208 }
209
210
211
212
213 public void dispose(IoSession session) throws Exception {
214 Context ctx = (Context) session.getAttribute(CONTEXT);
215
216 if (ctx != null) {
217 session.removeAttribute(CONTEXT);
218 }
219 }
220
221
222
223
224 private void decodeAuto(Context ctx, IoSession session, IoBuffer in, ProtocolDecoderOutput out)
225 throws CharacterCodingException, ProtocolDecoderException {
226 int matchCount = ctx.getMatchCount();
227
228
229 int oldPos = in.position();
230 int oldLimit = in.limit();
231
232 while (in.hasRemaining()) {
233 byte b = in.get();
234 boolean matched = false;
235
236 switch (b) {
237 case '\r':
238
239
240 matchCount++;
241 break;
242
243 case '\n':
244
245 matchCount++;
246 matched = true;
247 break;
248
249 default:
250 matchCount = 0;
251 }
252
253 if (matched) {
254
255 int pos = in.position();
256 in.limit(pos);
257 in.position(oldPos);
258
259 ctx.append(in);
260
261 in.limit(oldLimit);
262 in.position(pos);
263
264 if (ctx.getOverflowPosition() == 0) {
265 IoBuffer buf = ctx.getBuffer();
266 buf.flip();
267 buf.limit(buf.limit() - matchCount);
268
269 try {
270 byte[] data = new byte[buf.limit()];
271 buf.get(data);
272 writeText(session, new String(data, ctx.getDecoder().charset()), out);
273 } finally {
274 buf.clear();
275 }
276 } else {
277 int overflowPosition = ctx.getOverflowPosition();
278 ctx.reset();
279 throw new RecoverableProtocolDecoderException("Line is too long: " + overflowPosition);
280 }
281
282 oldPos = pos;
283 matchCount = 0;
284 }
285 }
286
287
288 in.position(oldPos);
289 ctx.append(in);
290
291 ctx.setMatchCount(matchCount);
292 }
293
294
295
296
297 private void decodeNormal(Context ctx, IoSession session, IoBuffer in, ProtocolDecoderOutput out)
298 throws CharacterCodingException, ProtocolDecoderException {
299 int matchCount = ctx.getMatchCount();
300
301
302 int oldPos = in.position();
303 int oldLimit = in.limit();
304
305 while (in.hasRemaining()) {
306 byte b = in.get();
307
308 if (delimBuf.get(matchCount) == b) {
309 matchCount++;
310
311 if (matchCount == delimBuf.limit()) {
312
313 int pos = in.position();
314 in.limit(pos);
315 in.position(oldPos);
316
317 ctx.append(in);
318
319 in.limit(oldLimit);
320 in.position(pos);
321
322 if (ctx.getOverflowPosition() == 0) {
323 IoBuffer buf = ctx.getBuffer();
324 buf.flip();
325 buf.limit(buf.limit() - matchCount);
326
327 try {
328 writeText(session, buf.getString(ctx.getDecoder()), out);
329 } finally {
330 buf.clear();
331 }
332 } else {
333 int overflowPosition = ctx.getOverflowPosition();
334 ctx.reset();
335 throw new RecoverableProtocolDecoderException("Line is too long: " + overflowPosition);
336 }
337
338 oldPos = pos;
339 matchCount = 0;
340 }
341 } else {
342
343 in.position(Math.max(0, in.position() - matchCount));
344 matchCount = 0;
345 }
346 }
347
348
349 in.position(oldPos);
350 ctx.append(in);
351
352 ctx.setMatchCount(matchCount);
353 }
354
355
356
357
358
359
360
361
362
363
364 protected void writeText(IoSession session, String text, ProtocolDecoderOutput out) {
365 out.write(text);
366 }
367
368
369
370
371
372
373
374
375 private class Context {
376
377 private final CharsetDecoder decoder;
378
379
380 private final IoBuffer buf;
381
382
383 private int matchCount = 0;
384
385
386 private int overflowPosition = 0;
387
388
389 private Context(int bufferLength) {
390 decoder = charset.newDecoder();
391 buf = IoBuffer.allocate(bufferLength).setAutoExpand(true);
392 }
393
394 public CharsetDecoder getDecoder() {
395 return decoder;
396 }
397
398 public IoBuffer getBuffer() {
399 return buf;
400 }
401
402 public int getOverflowPosition() {
403 return overflowPosition;
404 }
405
406 public int getMatchCount() {
407 return matchCount;
408 }
409
410 public void setMatchCount(int matchCount) {
411 this.matchCount = matchCount;
412 }
413
414 public void reset() {
415 overflowPosition = 0;
416 matchCount = 0;
417 decoder.reset();
418 }
419
420 public void append(IoBuffer in) {
421 if (overflowPosition != 0) {
422 discard(in);
423 } else if (buf.position() > maxLineLength - in.remaining()) {
424 overflowPosition = buf.position();
425 buf.clear();
426 discard(in);
427 } else {
428 getBuffer().put(in);
429 }
430 }
431
432 private void discard(IoBuffer in) {
433 if (Integer.MAX_VALUE - in.remaining() < overflowPosition) {
434 overflowPosition = Integer.MAX_VALUE;
435 } else {
436 overflowPosition += in.remaining();
437 }
438
439 in.position(in.limit());
440 }
441 }
442 }