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.common.BufferDataException;
27  import org.apache.mina.common.ByteBuffer;
28  import org.apache.mina.common.IoSession;
29  import org.apache.mina.filter.codec.ProtocolDecoder;
30  import org.apache.mina.filter.codec.ProtocolDecoderOutput;
31  
32  /**
33   * A {@link ProtocolDecoder} which decodes a text line into a string.
34   *
35   * @author The Apache Directory Project (mina-dev@directory.apache.org)
36   * @version $Rev: 608909 $, $Date: 2008-01-04 17:35:46 +0100 (Fri, 04 Jan 2008) $,
37   */
38  public class TextLineDecoder implements ProtocolDecoder {
39      private static final String CONTEXT = TextLineDecoder.class.getName()
40              + ".context";
41  
42      private final Charset charset;
43  
44      private final LineDelimiter delimiter;
45  
46      private ByteBuffer delimBuf;
47  
48      private int maxLineLength = 1024;
49  
50      /**
51       * Creates a new instance with the current default {@link Charset}
52       * and {@link LineDelimiter#AUTO} delimiter.
53       */
54      public TextLineDecoder() {
55          this(Charset.defaultCharset(), LineDelimiter.AUTO);
56      }
57  
58      /**
59       * Creates a new instance with the spcified <tt>charset</tt>
60       * and {@link LineDelimiter#AUTO} delimiter.
61       */
62      public TextLineDecoder(Charset charset) {
63          this(charset, LineDelimiter.AUTO);
64      }
65  
66      /**
67       * Creates a new instance with the specified <tt>charset</tt>
68       * and the specified <tt>delimiter</tt>.
69       */
70      public TextLineDecoder(Charset charset, LineDelimiter delimiter) {
71          if (charset == null) {
72              throw new NullPointerException("charset");
73          }
74          if (delimiter == null) {
75              throw new NullPointerException("delimiter");
76          }
77  
78          this.charset = charset;
79          this.delimiter = delimiter;
80      }
81  
82      /**
83       * Returns the allowed maximum size of the line to be decoded.
84       * If the size of the line to be decoded exceeds this value, the
85       * decoder will throw a {@link BufferDataException}.  The default
86       * value is <tt>1024</tt> (1KB).
87       */
88      public int getMaxLineLength() {
89          return maxLineLength;
90      }
91  
92      /**
93       * Sets the allowed maximum size of the line to be decoded.
94       * If the size of the line to be decoded exceeds this value, the
95       * decoder will throw a {@link BufferDataException}.  The default
96       * value is <tt>1024</tt> (1KB).
97       */
98      public void setMaxLineLength(int maxLineLength) {
99          if (maxLineLength <= 0) {
100             throw new IllegalArgumentException("maxLineLength: "
101                     + maxLineLength);
102         }
103 
104         this.maxLineLength = maxLineLength;
105     }
106 
107     public void decode(IoSession session, ByteBuffer in,
108             ProtocolDecoderOutput out) throws Exception {
109         Context ctx = getContext(session);
110 
111         if (LineDelimiter.AUTO.equals(delimiter)) {
112             decodeAuto(ctx, in, out);
113         } else {
114             decodeNormal(ctx, in, out);
115         }
116     }
117 
118     private Context getContext(IoSession session) {
119         Context ctx = (Context) session.getAttribute(CONTEXT);
120         if (ctx == null) {
121             ctx = new Context();
122             session.setAttribute(CONTEXT, ctx);
123         }
124         return ctx;
125     }
126 
127     public void finishDecode(IoSession session, ProtocolDecoderOutput out)
128             throws Exception {
129     }
130 
131     public void dispose(IoSession session) throws Exception {
132         Context ctx = (Context) session.getAttribute(CONTEXT);
133         if (ctx != null) {
134             ctx.getBuffer().release();
135             session.removeAttribute(CONTEXT);
136         }
137     }
138 
139     private void decodeAuto(Context ctx, ByteBuffer in, ProtocolDecoderOutput out)
140             throws CharacterCodingException {
141 
142         int matchCount = ctx.getMatchCount();
143         
144         // Try to find a match
145         int oldPos = in.position();
146         int oldLimit = in.limit();
147         while (in.hasRemaining()) {
148             byte b = in.get();
149             boolean matched = false;
150             switch (b) {
151             case '\r':
152                 // Might be Mac, but we don't auto-detect Mac EOL
153                 // to avoid confusion.
154                 matchCount++;
155                 break;
156             case '\n':
157                 // UNIX
158                 matchCount++;
159                 matched = true;
160                 break;
161             default:
162                 matchCount = 0;
163             }
164 
165             if (matched) {
166                 // Found a match.
167                 int pos = in.position();
168                 in.limit(pos);
169                 in.position(oldPos);
170 
171                 ctx.append(in);
172 
173                 in.limit(oldLimit);
174                 in.position(pos);
175 
176                 if (ctx.getOverflowPosition() == 0) {
177                     ByteBuffer buf = ctx.getBuffer();
178                     buf.flip();
179                     buf.limit(buf.limit() - matchCount);
180                     try {
181                         out.write(buf.getString(ctx.getDecoder()));
182                     } finally {
183                         buf.clear();
184                     }
185                 } else {
186                     int overflowPosition = ctx.getOverflowPosition();
187                     ctx.reset();
188                     throw new BufferDataException(
189                             "Line is too long: " + overflowPosition);
190                 }
191 
192                 oldPos = pos;
193                 matchCount = 0;
194             }
195         }
196 
197         // Put remainder to buf.
198         in.position(oldPos);
199         ctx.append(in);
200 
201         ctx.setMatchCount(matchCount);
202     }
203 
204     private void decodeNormal(Context ctx, ByteBuffer in, ProtocolDecoderOutput out)
205             throws CharacterCodingException {
206 
207         int matchCount = ctx.getMatchCount();
208         
209         // Convert delimiter to ByteBuffer if not done yet.
210         if (delimBuf == null) {
211             ByteBuffer tmp = ByteBuffer.allocate(2).setAutoExpand(true);
212             tmp.putString(delimiter.getValue(), charset.newEncoder());
213             tmp.flip();
214             delimBuf = tmp;
215         }
216 
217         // Try to find a match
218         int oldPos = in.position();
219         int oldLimit = in.limit();
220         while (in.hasRemaining()) {
221             byte b = in.get();
222             if (delimBuf.get(matchCount) == b) {
223                 matchCount++;
224                 if (matchCount == delimBuf.limit()) {
225                     // Found a match.
226                     int pos = in.position();
227                     in.limit(pos);
228                     in.position(oldPos);
229 
230                     ctx.append(in);
231                     
232                     in.limit(oldLimit);
233                     in.position(pos);
234 
235                     if (ctx.getOverflowPosition() == 0) {
236                         ByteBuffer buf = ctx.getBuffer();
237                         buf.flip();
238                         buf.limit(buf.limit() - matchCount);
239                         try {
240                             out.write(buf.getString(ctx.getDecoder()));
241                         } finally {
242                             buf.clear();
243                         }
244                     } else {
245                         int overflowPosition = ctx.getOverflowPosition();
246                         ctx.reset();
247                         throw new BufferDataException(
248                                 "Line is too long: " + overflowPosition);
249                     }
250 
251                     oldPos = pos;
252                     matchCount = 0;
253                 }
254             } else {
255 		// fix for DIRMINA-506
256 		in.position(in.position()-matchCount);
257                 matchCount = 0;
258             }
259         }
260 
261         // Put remainder to buf.
262         in.position(oldPos);
263         ctx.append(in);
264 
265         ctx.setMatchCount(matchCount);
266     }
267 
268     private class Context {
269         private final CharsetDecoder decoder;
270         private final ByteBuffer buf;
271         private int matchCount = 0;
272         private int overflowPosition = 0;
273 
274         private Context() {
275             decoder = charset.newDecoder();
276             buf = ByteBuffer.allocate(80).setAutoExpand(true);
277         }
278 
279         public CharsetDecoder getDecoder() {
280             return decoder;
281         }
282 
283         public ByteBuffer getBuffer() {
284             return buf;
285         }
286         
287         public int getOverflowPosition() {
288             return overflowPosition;
289         }
290         
291         public int getMatchCount() {
292             return matchCount;
293         }
294 
295         public void setMatchCount(int matchCount) {
296             this.matchCount = matchCount;
297         }
298         
299         public void reset() {
300             overflowPosition = 0;
301             matchCount = 0;
302             decoder.reset();
303         }
304         
305         public void append(ByteBuffer in) {
306             if (overflowPosition != 0) {
307                 discard(in);
308             } else if (buf.position() > maxLineLength - in.remaining()) {
309                     overflowPosition = buf.position();
310                     buf.clear();
311                     discard(in);
312             } else {
313                 getBuffer().put(in);
314             }
315         }
316 
317         private void discard(ByteBuffer in) {
318             if (Integer.MAX_VALUE - in.remaining() < overflowPosition) {
319                 overflowPosition = Integer.MAX_VALUE;
320             } else {
321                 overflowPosition += in.remaining();
322             }
323             in.position(in.limit());
324         }
325     }
326 }