View Javadoc

1   /*
2    *   @(#) $Id: TextLineDecoder.java 355016 2005-12-08 07:00:30Z trustin $
3    *
4    *   Copyright 2004 The Apache Software Foundation
5    *
6    *   Licensed under the Apache License, Version 2.0 (the "License");
7    *   you may not use this file except in compliance with the License.
8    *   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, software
13   *   distributed under the License is distributed on an "AS IS" BASIS,
14   *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   *   See the License for the specific language governing permissions and
16   *   limitations under the License.
17   *
18   */
19  package org.apache.mina.filter.codec.textline;
20  
21  import java.nio.charset.CharacterCodingException;
22  import java.nio.charset.Charset;
23  import java.nio.charset.CharsetDecoder;
24  
25  import org.apache.mina.common.BufferDataException;
26  import org.apache.mina.common.ByteBuffer;
27  import org.apache.mina.common.IoSession;
28  import org.apache.mina.filter.codec.ProtocolDecoder;
29  import org.apache.mina.filter.codec.ProtocolDecoderOutput;
30  
31  /***
32   * A {@link ProtocolDecoder} which decodes a text line into a string.
33   * 
34   * @author The Apache Directory Project (dev@directory.apache.org)
35   * @version $Rev: 355016 $, $Date: 2005-12-08 16:00:30 +0900 (Thu, 08 Dec 2005) $,
36   */
37  public class TextLineDecoder implements ProtocolDecoder
38  {
39      private static final String CONTEXT = TextLineDecoder.class.getName() + ".context";
40  
41      private final Charset charset;
42      private final LineDelimiter delimiter;
43      private ByteBuffer delimBuf;
44      private int maxLineLength = 1024;
45      
46      /***
47       * Creates a new instance with the current default {@link Charset}
48       * and {@link LineDelimiter#AUTO} delimiter.
49       */
50      public TextLineDecoder()
51      {
52          this( Charset.defaultCharset(), LineDelimiter.AUTO );
53      }
54      
55      /***
56       * Creates a new instance with the spcified <tt>charset</tt>
57       * and {@link LineDelimiter#AUTO} delimiter.
58       */
59      public TextLineDecoder( Charset charset )
60      {
61          this( charset, LineDelimiter.AUTO );
62      }
63  
64      /***
65       * Creates a new instance with the specified <tt>charset</tt>
66       * and the specified <tt>delimiter</tt>.
67       */
68      public TextLineDecoder( Charset charset, LineDelimiter delimiter )
69      {
70          if( charset == null )
71          {
72              throw new NullPointerException( "charset" );
73          }
74          if( delimiter == null )
75          {
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      {
91          return maxLineLength;
92      }
93      
94      /***
95       * Sets the allowed maximum size of the line to be decoded.
96       * If the size of the line to be decoded exceeds this value, the
97       * decoder will throw a {@link BufferDataException}.  The default
98       * value is <tt>1024</tt> (1KB).
99       */
100     public void setMaxLineLength( int maxLineLength )
101     {
102         if( maxLineLength <= 0 )
103         {
104             throw new IllegalArgumentException( "maxLineLength: " + maxLineLength );
105         }
106         
107         this.maxLineLength = maxLineLength;
108     }
109 
110     public void decode( IoSession session, ByteBuffer in,
111                         ProtocolDecoderOutput out )
112             throws Exception
113     {
114         Context ctx = ( Context ) session.getAttribute( CONTEXT );
115         if( ctx == null )
116         {
117             ctx = new Context();
118             session.setAttribute( CONTEXT, ctx );
119         }
120         
121         if( LineDelimiter.AUTO.equals( delimiter ) )
122         {
123             ctx.setMatchCount(
124                     decodeAuto(
125                             in,
126                             ctx.getBuffer(),
127                             ctx.getMatchCount(),
128                             ctx.getDecoder(),
129                             out ) );
130         }
131         else
132         {
133             ctx.setMatchCount(
134                     decodeNormal(
135                             in,
136                             ctx.getBuffer(),
137                             ctx.getMatchCount(),
138                             ctx.getDecoder(),
139                             out ) );
140         }
141     }
142 
143     public void dispose( IoSession session ) throws Exception
144     {
145         Context ctx = ( Context ) session.getAttribute( CONTEXT );
146         if( ctx != null )
147         {
148             ctx.getBuffer().release();
149             session.removeAttribute( CONTEXT );
150         }
151     }
152 
153     private int decodeAuto( ByteBuffer in, ByteBuffer buf, int matchCount, CharsetDecoder decoder, ProtocolDecoderOutput out ) throws CharacterCodingException
154     {
155         // Try to find a match
156         int oldMatchCount = matchCount;
157         int oldPos = in.position();
158         int oldLimit = in.limit();
159         while( in.hasRemaining() )
160         {
161             byte b = in.get();
162             boolean matched = false;
163             switch( b )
164             {
165             case '\r':
166                 // Might be Mac, but we don't auto-detect Mac EOL
167                 // to avoid confusion.
168                 matchCount ++;
169                 break;
170             case '\n':
171                 // UNIX
172                 matchCount ++;
173                 matched = true;
174                 break;
175             default:
176                 matchCount = 0;
177             }
178 
179             if( matched )
180             {
181                 // Found a match.
182                 int pos = in.position();
183                 in.limit( pos - matchCount + oldMatchCount );
184                 in.position( oldPos );
185                 
186                 buf.put( in );
187                 if( buf.position() > maxLineLength )
188                 {
189                     throw new BufferDataException( "Line is too long: " + buf.position() );
190                 }
191                 buf.flip();
192                 out.write( buf.getString( decoder ) );
193                 buf.clear();
194                 
195                 in.limit( oldLimit );
196                 in.position( pos );
197                 oldPos = pos;
198                 matchCount = 0;
199             }
200         }
201         
202         // Put remainder to buf.
203         in.position( oldPos );
204         in.limit( in.limit() - matchCount + oldMatchCount );
205         buf.put( in );
206         
207         return matchCount;
208     }
209 
210     private int decodeNormal( ByteBuffer in, ByteBuffer buf, int matchCount, CharsetDecoder decoder, ProtocolDecoderOutput out ) throws CharacterCodingException
211     {
212         // Convert delimiter to ByteBuffer if not done yet.
213         if( delimBuf == null )
214         {
215             ByteBuffer tmp = ByteBuffer.allocate( 2 ).setAutoExpand( true );
216             tmp.putString( delimiter.getValue(), charset.newEncoder() );
217             tmp.flip();
218             delimBuf = tmp;
219         }
220         
221         // Try to find a match
222         int oldMatchCount = matchCount;
223         int oldPos = in.position();
224         int oldLimit = in.limit();
225         while( in.hasRemaining() )
226         {
227             byte b = in.get();
228             if( delimBuf.get( matchCount ) == b )
229             {
230                 matchCount ++;
231                 if( matchCount == delimBuf.limit() )
232                 {
233                     // Found a match.
234                     int pos = in.position();
235                     in.limit( pos - matchCount + oldMatchCount );
236                     in.position( oldPos );
237                     
238                     buf.put( in );
239                     if( buf.position() > maxLineLength )
240                     {
241                         throw new BufferDataException( "Line is too long: " + buf.position() );
242                     }
243                     buf.flip();
244                     out.write( buf.getString( decoder ) );
245                     buf.clear();
246                     
247                     in.limit( oldLimit );
248                     in.position( pos );
249                     oldPos = pos;
250                     matchCount = 0;
251                 }
252             }
253             else
254             {
255                 matchCount = 0;
256             }
257         }
258         
259         // Put remainder to buf.
260         in.position( oldPos );
261         in.limit( in.limit() - matchCount + oldMatchCount );
262         buf.put( in );
263         
264         return matchCount;
265     }
266     
267     private class Context
268     {
269         private final CharsetDecoder decoder;
270         private final ByteBuffer buf;
271         private int matchCount = 0;
272         
273         private Context()
274         {
275             decoder = charset.newDecoder();
276             buf = ByteBuffer.allocate( 80 ).setAutoExpand( true );
277         }
278         
279         public CharsetDecoder getDecoder()
280         {
281             return decoder;
282         }
283         
284         public ByteBuffer getBuffer()
285         {
286             return buf;
287         }
288         
289         public int getMatchCount()
290         {
291             return matchCount;
292         }
293         
294         public void setMatchCount( int matchCount )
295         {
296             this.matchCount = matchCount;
297         }
298     }
299 }