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