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