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.proxy.utils;
21  
22  import org.apache.mina.core.buffer.IoBuffer;
23  import org.apache.mina.core.session.IoSession;
24  import org.apache.mina.filter.codec.textline.LineDelimiter;
25  
26  /**
27   * IoBufferDecoder.java - Handles an {@link IoBuffer} decoder which supports 
28   * two methods : 
29   * - dynamic delimiter decoding
30   * - fixed length content reading
31   * 
32   * @author The Apache MINA Project (dev@mina.apache.org)
33   * @since MINA 2.0.0-M3
34   */
35  public class IoBufferDecoder {
36  
37      /**
38       * The class holding the decoding context.
39       */
40      public class DecodingContext {
41  
42          /**
43           * The buffered data.
44           */
45          private IoBuffer decodedBuffer;
46  
47          /**
48           * The delimiter in use. Set if delimiter mode is in use.
49           */
50          private IoBuffer delimiter;
51  
52          /**
53           * The currently matched bytes of the delimiter. 
54           */
55          private int matchCount = 0;
56          
57          /**
58           * Holds the current content length of decoded data if in
59           * content-length mode.
60           */
61          private int contentLength = -1;
62  
63          /**
64           * Resets the decoding state.
65           */
66          public void reset() {
67              contentLength = -1;
68              matchCount = 0;
69              decodedBuffer = null;
70          }
71  
72          public int getContentLength() {
73              return contentLength;
74          }
75  
76          public void setContentLength(int contentLength) {
77              this.contentLength = contentLength;
78          }
79  
80          public int getMatchCount() {
81              return matchCount;
82          }
83  
84          public void setMatchCount(int matchCount) {
85              this.matchCount = matchCount;
86          }
87  
88          public IoBuffer getDecodedBuffer() {
89              return decodedBuffer;
90          }
91  
92          public void setDecodedBuffer(IoBuffer decodedBuffer) {
93              this.decodedBuffer = decodedBuffer;
94          }
95  
96          public IoBuffer getDelimiter() {
97              return delimiter;
98          }
99  
100         public void setDelimiter(IoBuffer delimiter) {
101             this.delimiter = delimiter;
102         }
103     }
104 
105     /**
106      * The decoding context.
107      */
108     private DecodingContext ctx = new DecodingContext();
109 
110     /**
111      * Creates a new instance that uses specified <tt>delimiter</tt> byte array as a
112      * message delimiter.
113      * 
114      * @param delimiter an array of characters which delimits messages
115      */
116     public IoBufferDecoder(byte[] delimiter) {
117         setDelimiter(delimiter, true);
118     }
119 
120     /**
121      * Creates a new instance that will read messages of <tt>contentLength</tt> bytes.
122      * 
123      * @param contentLength the exact length to read
124      */
125     public IoBufferDecoder(int contentLength) {
126         setContentLength(contentLength, false);
127     }
128 
129     /**
130      * Sets the the length of the content line to be decoded.
131      * When set, it overrides the dynamic delimiter setting and content length 
132      * method will be used for decoding on the next decodeOnce call.
133      * The default value is <tt>-1</tt>.
134      * 
135      * @param contentLength the content length to match
136      * @param resetMatchCount delimiter matching is reset if true
137      */
138     public void setContentLength(int contentLength, boolean resetMatchCount) {
139         if (contentLength <= 0) {
140             throw new IllegalArgumentException("contentLength: "
141                     + contentLength);
142         }
143 
144         ctx.setContentLength(contentLength);
145         if (resetMatchCount) {
146             ctx.setMatchCount(0);
147         }
148     }
149 
150     /**
151      * Dynamically sets a new delimiter. Next time 
152      * {@link IoBufferDecoder#decodeOnce(IoSession, int) } will be called it will use the new 
153      * delimiter. Delimiter matching is reset only if <tt>resetMatchCount</tt> is true but 
154      * decoding will continue from current position.
155      * 
156      * NB : Delimiter {@link LineDelimiter#AUTO} is not allowed.
157      * 
158      * @param delim the new delimiter as a byte array
159      * @param resetMatchCount delimiter matching is reset if true
160      */
161     public void setDelimiter(byte[] delim, boolean resetMatchCount) {
162         if (delim == null) {
163             throw new NullPointerException("Null delimiter not allowed");
164         }
165 
166         // Convert delimiter to IoBuffer.
167         IoBuffer delimiter = IoBuffer.allocate(delim.length);
168         delimiter.put(delim);
169         delimiter.flip();
170 
171         ctx.setDelimiter(delimiter);
172         ctx.setContentLength(-1);
173         if (resetMatchCount) {
174             ctx.setMatchCount(0);
175         }
176     }
177 
178     /**
179      * Will return null unless it has enough data to decode. If <code>contentLength</code>
180      * is set then it tries to retrieve <code>contentLength</code> bytes from the buffer
181      * otherwise it will scan the buffer to find the data <code>delimiter</code> and return
182      * all the data and the trailing delimiter.
183      * 
184      * @param in the data to decode
185      */
186     public IoBuffer decodeFully(IoBuffer in) {
187         int contentLength = ctx.getContentLength();
188         IoBuffer decodedBuffer = ctx.getDecodedBuffer();
189 
190         int oldLimit = in.limit();
191 
192         // Retrieve fixed length content
193         if (contentLength > -1) {
194             if (decodedBuffer == null) {
195                 decodedBuffer = IoBuffer.allocate(contentLength).setAutoExpand(
196                         true);
197             }
198 
199             // If not enough data to complete the decoding
200             if (in.remaining() < contentLength) {
201                 int readBytes = in.remaining();
202                 decodedBuffer.put(in);
203                 ctx.setDecodedBuffer(decodedBuffer);
204                 ctx.setContentLength(contentLength - readBytes);
205                 return null;
206 
207             }
208 
209             int newLimit = in.position() + contentLength;
210             in.limit(newLimit);
211             decodedBuffer.put(in);
212             decodedBuffer.flip();
213             in.limit(oldLimit);
214             ctx.reset();
215 
216             return decodedBuffer;
217         }
218 
219         // Not a fixed length matching so try to find a delimiter match
220         int oldPos = in.position();
221         int matchCount = ctx.getMatchCount();
222         IoBuffer delimiter = ctx.getDelimiter();
223 
224         while (in.hasRemaining()) {
225             byte b = in.get();
226             if (delimiter.get(matchCount) == b) {
227                 matchCount++;
228                 if (matchCount == delimiter.limit()) {
229                     // Found a match.
230                     int pos = in.position();
231                     in.position(oldPos);
232 
233                     in.limit(pos);
234 
235                     if (decodedBuffer == null) {
236                         decodedBuffer = IoBuffer.allocate(in.remaining())
237                                 .setAutoExpand(true);
238                     }
239 
240                     decodedBuffer.put(in);
241                     decodedBuffer.flip();
242 
243                     in.limit(oldLimit);
244                     ctx.reset();
245 
246                     return decodedBuffer;
247                 }
248             } else {
249                 in.position(Math.max(0, in.position() - matchCount));
250                 matchCount = 0;
251             }
252         }
253 
254         // Copy remainder from buf.
255         if (in.remaining() > 0) {
256             in.position(oldPos);
257             decodedBuffer.put(in);
258             in.position(in.limit());
259         }
260 
261         // Save decoding state
262         ctx.setMatchCount(matchCount);
263         ctx.setDecodedBuffer(decodedBuffer);
264 
265         return decodedBuffer;
266     }
267 }