View Javadoc

1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    *
10   *    http://www.apache.org/licenses/LICENSE-2.0
11   *
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License.
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   * A {@link ProtocolDecoder} which decodes a text line into a string.
37   *
38   * @author The Apache MINA Project (dev@mina.apache.org)
39   * @version $Rev: 683500 $, $Date: 2008-08-07 06:02:01 +0200 (jeu, 07 aoĆ» 2008) $,
40   */
41  public class TextLineDecoder implements ProtocolDecoder {
42      private final AttributeKey CONTEXT = new AttributeKey(getClass(), "context");
43  
44      private final Charset charset;
45  
46      private final LineDelimiter delimiter;
47  
48      private IoBuffer delimBuf;
49  
50      private int maxLineLength = 1024;
51  
52      /**
53       * Creates a new instance with the current default {@link Charset}
54       * and {@link LineDelimiter#AUTO} delimiter.
55       */
56      public TextLineDecoder() {
57          this(LineDelimiter.AUTO);
58      }
59  
60      /**
61       * Creates a new instance with the current default {@link Charset}
62       * and the specified <tt>delimiter</tt>.
63       */
64      public TextLineDecoder(String delimiter) {
65          this(new LineDelimiter(delimiter));
66      }
67  
68      /**
69       * Creates a new instance with the current default {@link Charset}
70       * and the specified <tt>delimiter</tt>.
71       */
72      public TextLineDecoder(LineDelimiter delimiter) {
73          this(Charset.defaultCharset(), delimiter);
74      }
75  
76      /**
77       * Creates a new instance with the spcified <tt>charset</tt>
78       * and {@link LineDelimiter#AUTO} delimiter.
79       */
80      public TextLineDecoder(Charset charset) {
81          this(charset, LineDelimiter.AUTO);
82      }
83  
84      /**
85       * Creates a new instance with the spcified <tt>charset</tt>
86       * and the specified <tt>delimiter</tt>.
87       */
88      public TextLineDecoder(Charset charset, String delimiter) {
89          this(charset, new LineDelimiter(delimiter));
90      }
91  
92      /**
93       * Creates a new instance with the specified <tt>charset</tt>
94       * and the specified <tt>delimiter</tt>.
95       */
96      public TextLineDecoder(Charset charset, LineDelimiter delimiter) {
97          if (charset == null) {
98              throw new NullPointerException("charset");
99          }
100         if (delimiter == null) {
101             throw new NullPointerException("delimiter");
102         }
103 
104         this.charset = charset;
105         this.delimiter = delimiter;
106     }
107 
108     /**
109      * Returns the allowed maximum size of the line to be decoded.
110      * If the size of the line to be decoded exceeds this value, the
111      * decoder will throw a {@link BufferDataException}.  The default
112      * value is <tt>1024</tt> (1KB).
113      */
114     public int getMaxLineLength() {
115         return maxLineLength;
116     }
117 
118     /**
119      * Sets the allowed maximum size of the line to be decoded.
120      * If the size of the line to be decoded exceeds this value, the
121      * decoder will throw a {@link BufferDataException}.  The default
122      * value is <tt>1024</tt> (1KB).
123      */
124     public void setMaxLineLength(int maxLineLength) {
125         if (maxLineLength <= 0) {
126             throw new IllegalArgumentException("maxLineLength: "
127                     + maxLineLength);
128         }
129 
130         this.maxLineLength = maxLineLength;
131     }
132 
133     public void decode(IoSession session, IoBuffer in,
134             ProtocolDecoderOutput out) throws Exception {
135         Context ctx = getContext(session);
136 
137         if (LineDelimiter.AUTO.equals(delimiter)) {
138             decodeAuto(ctx, session, in, out);
139         } else {
140             decodeNormal(ctx, session, in, out);
141         }
142     }
143 
144     private Context getContext(IoSession session) {
145         Context ctx;
146         ctx = (Context) session.getAttribute(CONTEXT);
147         if (ctx == null) {
148             ctx = new Context();
149             session.setAttribute(CONTEXT, ctx);
150         }
151         return ctx;
152     }
153 
154     public void finishDecode(IoSession session, ProtocolDecoderOutput out)
155             throws Exception {
156     }
157 
158     public void dispose(IoSession session) throws Exception {
159         Context ctx = (Context) session.getAttribute(CONTEXT);
160         if (ctx != null) {
161             session.removeAttribute(CONTEXT);
162         }
163     }
164 
165     private void decodeAuto(Context ctx, IoSession session, IoBuffer in, ProtocolDecoderOutput out)
166             throws CharacterCodingException, ProtocolDecoderException {
167 
168         int matchCount = ctx.getMatchCount();
169 
170         // Try to find a match
171         int oldPos = in.position();
172         int oldLimit = in.limit();
173         while (in.hasRemaining()) {
174             byte b = in.get();
175             boolean matched = false;
176             switch (b) {
177             case '\r':
178                 // Might be Mac, but we don't auto-detect Mac EOL
179                 // to avoid confusion.
180                 matchCount++;
181                 break;
182             case '\n':
183                 // UNIX
184                 matchCount++;
185                 matched = true;
186                 break;
187             default:
188                 matchCount = 0;
189             }
190 
191             if (matched) {
192                 // Found a match.
193                 int pos = in.position();
194                 in.limit(pos);
195                 in.position(oldPos);
196 
197                 ctx.append(in);
198 
199                 in.limit(oldLimit);
200                 in.position(pos);
201 
202                 if (ctx.getOverflowPosition() == 0) {
203                     IoBuffer buf = ctx.getBuffer();
204                     buf.flip();
205                     buf.limit(buf.limit() - matchCount);
206                     try {
207                     	writeText(session, buf.getString(ctx.getDecoder()), out);
208                     } finally {
209                         buf.clear();
210                     }
211                 } else {
212                     int overflowPosition = ctx.getOverflowPosition();
213                     ctx.reset();
214                     throw new RecoverableProtocolDecoderException(
215                             "Line is too long: " + overflowPosition);
216                 }
217 
218                 oldPos = pos;
219                 matchCount = 0;
220             }
221         }
222 
223         // Put remainder to buf.
224         in.position(oldPos);
225         ctx.append(in);
226 
227         ctx.setMatchCount(matchCount);
228     }
229 
230     private void decodeNormal(Context ctx, IoSession session, IoBuffer in, ProtocolDecoderOutput out)
231             throws CharacterCodingException, ProtocolDecoderException {
232 
233         int matchCount = ctx.getMatchCount();
234 
235         // Convert delimiter to ByteBuffer if not done yet.
236         if (delimBuf == null) {
237             IoBuffer tmp = IoBuffer.allocate(2).setAutoExpand(true);
238             tmp.putString(delimiter.getValue(), charset.newEncoder());
239             tmp.flip();
240             delimBuf = tmp;
241         }
242 
243         // Try to find a match
244         int oldPos = in.position();
245         int oldLimit = in.limit();
246         while (in.hasRemaining()) {
247             byte b = in.get();
248             if (delimBuf.get(matchCount) == b) {
249                 matchCount++;
250                 if (matchCount == delimBuf.limit()) {
251                     // Found a match.
252                     int pos = in.position();
253                     in.limit(pos);
254                     in.position(oldPos);
255 
256                     ctx.append(in);
257 
258                     in.limit(oldLimit);
259                     in.position(pos);
260                     if (ctx.getOverflowPosition() == 0) {
261                         IoBuffer buf = ctx.getBuffer();
262                         buf.flip();
263                         buf.limit(buf.limit() - matchCount);
264                         try {
265                         	writeText(session, buf.getString(ctx.getDecoder()), out);
266                         } finally {
267                             buf.clear();
268                         }
269                     } else {
270                         int overflowPosition = ctx.getOverflowPosition();
271                         ctx.reset();
272                         throw new RecoverableProtocolDecoderException(
273                                 "Line is too long: " + overflowPosition);
274                     }
275 
276                     oldPos = pos;
277                     matchCount = 0;
278                 }
279             } else {
280                 // fix for DIRMINA-506 & DIRMINA-536
281                 in.position(Math.max(0, in.position() - matchCount));
282                 matchCount = 0;
283             }
284         }
285 
286         // Put remainder to buf.
287         in.position(oldPos);
288         ctx.append(in);
289 
290         ctx.setMatchCount(matchCount);
291     }
292 
293     /**
294      * By default, this method propagates the decoded line of text to
295      * {@code ProtocolDecoderOutput#write(Object)}.  You may override this method to modify
296      * the default behavior.
297      *
298      * @param session  the {@code IoSession} the received data.
299      * @param text  the decoded text
300      * @param out  the upstream {@code ProtocolDecoderOutput}.
301      */
302     protected void writeText(IoSession session, String text, ProtocolDecoderOutput out) {
303     	out.write(text);
304     }
305 
306     private class Context {
307         private final CharsetDecoder decoder;
308         private final IoBuffer buf;
309         private int matchCount = 0;
310         private int overflowPosition = 0;
311 
312         private Context() {
313             decoder = charset.newDecoder();
314             buf = IoBuffer.allocate(80).setAutoExpand(true);
315         }
316 
317         public CharsetDecoder getDecoder() {
318             return decoder;
319         }
320 
321         public IoBuffer getBuffer() {
322             return buf;
323         }
324 
325         public int getOverflowPosition() {
326             return overflowPosition;
327         }
328 
329         public int getMatchCount() {
330             return matchCount;
331         }
332 
333         public void setMatchCount(int matchCount) {
334             this.matchCount = matchCount;
335         }
336 
337         public void reset() {
338             overflowPosition = 0;
339             matchCount = 0;
340             decoder.reset();
341         }
342 
343         public void append(IoBuffer in) {
344             if (overflowPosition != 0) {
345                 discard(in);
346             } else if (buf.position() > maxLineLength - in.remaining()) {
347                     overflowPosition = buf.position();
348                     buf.clear();
349                     discard(in);
350             } else {
351                 getBuffer().put(in);
352             }
353         }
354 
355         private void discard(IoBuffer in) {
356             if (Integer.MAX_VALUE - in.remaining() < overflowPosition) {
357                 overflowPosition = Integer.MAX_VALUE;
358             } else {
359                 overflowPosition += in.remaining();
360             }
361             in.position(in.limit());
362         }
363     }
364 }