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: 555855 $, $Date: 2007-07-13 12:19:00 +0900 (Fri, 13 Jul 2007) $,
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             ctx.setMatchCount(decodeAuto(in, ctx.getBuffer(), ctx
113                     .getMatchCount(), ctx.getDecoder(), out));
114         } else {
115             ctx.setMatchCount(decodeNormal(in, ctx.getBuffer(), ctx
116                     .getMatchCount(), ctx.getDecoder(), out));
117         }
118     }
119 
120     private Context getContext(IoSession session) {
121         Context 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 int decodeAuto(ByteBuffer in, ByteBuffer buf, int matchCount,
142             CharsetDecoder decoder, ProtocolDecoderOutput out)
143             throws CharacterCodingException {
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                 buf.put(in);
172                 if (buf.position() > maxLineLength) {
173                     throw new BufferDataException("Line is too long: "
174                             + buf.position());
175                 }
176                 buf.flip();
177                 buf.limit(buf.limit() - matchCount);
178                 out.write(buf.getString(decoder));
179                 buf.clear();
180 
181                 in.limit(oldLimit);
182                 in.position(pos);
183                 oldPos = pos;
184                 matchCount = 0;
185             }
186         }
187 
188         // Put remainder to buf.
189         in.position(oldPos);
190         buf.put(in);
191 
192         return matchCount;
193     }
194 
195     private int decodeNormal(ByteBuffer in, ByteBuffer buf, int matchCount,
196             CharsetDecoder decoder, ProtocolDecoderOutput out)
197             throws CharacterCodingException {
198         // Convert delimiter to ByteBuffer if not done yet.
199         if (delimBuf == null) {
200             ByteBuffer tmp = ByteBuffer.allocate(2).setAutoExpand(true);
201             tmp.putString(delimiter.getValue(), charset.newEncoder());
202             tmp.flip();
203             delimBuf = tmp;
204         }
205 
206         // Try to find a match
207         int oldPos = in.position();
208         int oldLimit = in.limit();
209         while (in.hasRemaining()) {
210             byte b = in.get();
211             if (delimBuf.get(matchCount) == b) {
212                 matchCount++;
213                 if (matchCount == delimBuf.limit()) {
214                     // Found a match.
215                     int pos = in.position();
216                     in.limit(pos);
217                     in.position(oldPos);
218 
219                     buf.put(in);
220                     if (buf.position() > maxLineLength) {
221                         throw new BufferDataException("Line is too long: "
222                                 + buf.position());
223                     }
224                     buf.flip();
225                     buf.limit(buf.limit() - matchCount);
226                     out.write(buf.getString(decoder));
227                     buf.clear();
228 
229                     in.limit(oldLimit);
230                     in.position(pos);
231                     oldPos = pos;
232                     matchCount = 0;
233                 }
234             } else {
235                 matchCount = 0;
236             }
237         }
238 
239         // Put remainder to buf.
240         in.position(oldPos);
241         buf.put(in);
242 
243         return matchCount;
244     }
245 
246     private class Context {
247         private final CharsetDecoder decoder;
248 
249         private final ByteBuffer buf;
250 
251         private int matchCount = 0;
252 
253         private Context() {
254             decoder = charset.newDecoder();
255             buf = ByteBuffer.allocate(80).setAutoExpand(true);
256         }
257 
258         public CharsetDecoder getDecoder() {
259             return decoder;
260         }
261 
262         public ByteBuffer getBuffer() {
263             return buf;
264         }
265 
266         public int getMatchCount() {
267             return matchCount;
268         }
269 
270         public void setMatchCount(int matchCount) {
271             this.matchCount = matchCount;
272         }
273     }
274 }