001/*
002 * ====================================================================
003 * Licensed to the Apache Software Foundation (ASF) under one
004 * or more contributor license agreements.  See the NOTICE file
005 * distributed with this work for additional information
006 * regarding copyright ownership.  The ASF licenses this file
007 * to you under the Apache License, Version 2.0 (the
008 * "License"); you may not use this file except in compliance
009 * with the License.  You may obtain a copy of the License at
010 *
011 *   http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing,
014 * software distributed under the License is distributed on an
015 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
016 * KIND, either express or implied.  See the License for the
017 * specific language governing permissions and limitations
018 * under the License.
019 * ====================================================================
020 *
021 * This software consists of voluntary contributions made by many
022 * individuals on behalf of the Apache Software Foundation.  For more
023 * information on the Apache Software Foundation, please see
024 * <http://www.apache.org/>.
025 *
026 */
027
028package org.apache.http.nio.util;
029
030import java.nio.ByteBuffer;
031
032import org.apache.http.io.BufferInfo;
033import org.apache.http.util.Args;
034
035/**
036 * A buffer that expand its capacity on demand using {@link ByteBufferAllocator}
037 * interface. Internally, this class is backed by an instance of
038 * {@link ByteBuffer}.
039 * <p>
040 * This class is not thread safe.
041 *
042 * @since 4.0
043 */
044@SuppressWarnings("deprecation")
045public class ExpandableBuffer implements BufferInfo, org.apache.http.nio.util.BufferInfo {
046
047    public final static int INPUT_MODE = 0;
048    public final static int OUTPUT_MODE = 1;
049
050    private final ByteBufferAllocator allocator;
051
052    private int mode;
053    protected ByteBuffer buffer = null;
054
055    /**
056     * Allocates buffer of the given size using the given allocator.
057     *
058     * @param buffersize the buffer size.
059     * @param allocator allocator to be used to allocate {@link ByteBuffer}s.
060     */
061    public ExpandableBuffer(final int buffersize, final ByteBufferAllocator allocator) {
062        super();
063        Args.notNull(allocator, "ByteBuffer allocator");
064        this.allocator = allocator;
065        this.buffer = allocator.allocate(buffersize);
066        this.mode = INPUT_MODE;
067    }
068
069    /**
070     * Returns the current mode:
071     * <p>
072     * {@link #INPUT_MODE}: the buffer is in the input mode.
073     * <p>
074     * {@link #OUTPUT_MODE}: the buffer is in the output mode.
075     *
076     * @return current input/output mode.
077     */
078    protected int getMode() {
079        return this.mode;
080    }
081
082    /**
083     * Sets output mode. The buffer can now be read from.
084     */
085    protected void setOutputMode() {
086        if (this.mode != OUTPUT_MODE) {
087            this.buffer.flip();
088            this.mode = OUTPUT_MODE;
089        }
090    }
091
092    /**
093     * Sets input mode. The buffer can now be written into.
094     */
095    protected void setInputMode() {
096        if (this.mode != INPUT_MODE) {
097            if (this.buffer.hasRemaining()) {
098                this.buffer.compact();
099            } else {
100                this.buffer.clear();
101            }
102            this.mode = INPUT_MODE;
103        }
104    }
105
106    private void expandCapacity(final int capacity) {
107        final ByteBuffer oldbuffer = this.buffer;
108        this.buffer = allocator.allocate(capacity);
109        oldbuffer.flip();
110        this.buffer.put(oldbuffer);
111    }
112
113    /**
114     * Expands buffer's capacity.
115     */
116    protected void expand() {
117        int newcapacity = (this.buffer.capacity() + 1) << 1;
118        if (newcapacity < 0) {
119            newcapacity = Integer.MAX_VALUE;
120        }
121        expandCapacity(newcapacity);
122    }
123
124    /**
125     * Ensures the buffer can accommodate the required capacity.
126     */
127    protected void ensureCapacity(final int requiredCapacity) {
128        if (requiredCapacity > this.buffer.capacity()) {
129            expandCapacity(requiredCapacity);
130        }
131    }
132
133    /**
134     * Returns the total capacity of this buffer.
135     *
136     * @return total capacity.
137     */
138    @Override
139    public int capacity() {
140        return this.buffer.capacity();
141    }
142
143    /**
144     * Determines if the buffer contains data.
145     *
146     * @return {@code true} if there is data in the buffer,
147     *   {@code false} otherwise.
148     */
149    public boolean hasData() {
150        setOutputMode();
151        return this.buffer.hasRemaining();
152    }
153
154    /**
155     * Returns the length of this buffer.
156     *
157     * @return buffer length.
158     */
159    @Override
160    public int length() {
161        setOutputMode();
162        return this.buffer.remaining();
163    }
164
165    /**
166     * Returns available capacity of this buffer.
167     *
168     * @return buffer length.
169     */
170    @Override
171    public int available() {
172        setInputMode();
173        return this.buffer.remaining();
174    }
175
176    /**
177     * Clears buffer.
178     */
179    protected void clear() {
180        this.buffer.clear();
181        this.mode = INPUT_MODE;
182    }
183
184    @Override
185    public String toString() {
186        final StringBuilder sb = new StringBuilder();
187        sb.append("[mode=");
188        if (getMode() == INPUT_MODE) {
189            sb.append("in");
190        } else {
191            sb.append("out");
192        }
193        sb.append(" pos=");
194        sb.append(this.buffer.position());
195        sb.append(" lim=");
196        sb.append(this.buffer.limit());
197        sb.append(" cap=");
198        sb.append(this.buffer.capacity());
199        sb.append("]");
200        return sb.toString();
201    }
202
203}