001/*
002 *  Licensed to the Apache Software Foundation (ASF) under one
003 *  or more contributor license agreements.  See the NOTICE file
004 *  distributed with this work for additional information
005 *  regarding copyright ownership.  The ASF licenses this file
006 *  to you under the Apache License, Version 2.0 (the
007 *  "License"); you may not use this file except in compliance
008 *  with the License.  You may obtain a copy of the License at
009 *
010 *    http://www.apache.org/licenses/LICENSE-2.0
011 *
012 *  Unless required by applicable law or agreed to in writing,
013 *  software distributed under the License is distributed on an
014 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 *  KIND, either express or implied.  See the License for the
016 *  specific language governing permissions and limitations
017 *  under the License.
018 *
019 */
020package org.apache.mina.proxy.utils;
021
022import org.apache.mina.core.buffer.IoBuffer;
023import org.apache.mina.filter.codec.textline.LineDelimiter;
024
025/**
026 * IoBufferDecoder.java - Handles an {@link IoBuffer} decoder which supports 
027 * two methods : 
028 * - dynamic delimiter decoding
029 * - fixed length content reading
030 * 
031 * @author <a href="http://mina.apache.org">Apache MINA Project</a>
032 * @since MINA 2.0.0-M3
033 */
034public class IoBufferDecoder {
035
036    /**
037     * The class holding the decoding context.
038     */
039    public class DecodingContext {
040
041        /**
042         * The buffered data.
043         */
044        private IoBuffer decodedBuffer;
045
046        /**
047         * The delimiter in use. Set if delimiter mode is in use.
048         */
049        private IoBuffer delimiter;
050
051        /**
052         * The currently matched bytes of the delimiter. 
053         */
054        private int matchCount = 0;
055
056        /**
057         * Holds the current content length of decoded data if in
058         * content-length mode.
059         */
060        private int contentLength = -1;
061
062        /**
063         * Resets the decoding state.
064         */
065        public void reset() {
066            contentLength = -1;
067            matchCount = 0;
068            decodedBuffer = null;
069        }
070
071        public int getContentLength() {
072            return contentLength;
073        }
074
075        public void setContentLength(int contentLength) {
076            this.contentLength = contentLength;
077        }
078
079        public int getMatchCount() {
080            return matchCount;
081        }
082
083        public void setMatchCount(int matchCount) {
084            this.matchCount = matchCount;
085        }
086
087        public IoBuffer getDecodedBuffer() {
088            return decodedBuffer;
089        }
090
091        public void setDecodedBuffer(IoBuffer decodedBuffer) {
092            this.decodedBuffer = decodedBuffer;
093        }
094
095        public IoBuffer getDelimiter() {
096            return delimiter;
097        }
098
099        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}