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