001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *   http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 *
017 */
018
019package org.apache.commons.compress.utils;
020
021import java.io.IOException;
022import java.nio.ByteBuffer;
023import java.nio.channels.ClosedChannelException;
024import java.nio.channels.SeekableByteChannel;
025import java.util.Arrays;
026import java.util.concurrent.atomic.AtomicBoolean;
027
028/**
029 * A {@link SeekableByteChannel} implementation that wraps a byte[].
030 *
031 * <p>When this channel is used for writing an internal buffer grows to accommodate
032 * incoming data. A natural size limit is the value of {@link Integer#MAX_VALUE}.
033 * Internal buffer can be accessed via {@link SeekableInMemoryByteChannel#array()}.</p>
034 *
035 * @since 1.13
036 * @NotThreadSafe
037 */
038public class SeekableInMemoryByteChannel implements SeekableByteChannel {
039
040    private static final int NAIVE_RESIZE_LIMIT = Integer.MAX_VALUE >> 1;
041
042    private byte[] data;
043    private final AtomicBoolean closed = new AtomicBoolean();
044    private int position, size;
045
046    /**
047     * Constructor taking a byte array.
048     *
049     * <p>This constructor is intended to be used with pre-allocated buffer or when
050     * reading from a given byte array.</p>
051     *
052     * @param data input data or pre-allocated array.
053     */
054    public SeekableInMemoryByteChannel(byte[] data) {
055        this.data = data;
056        size = data.length;
057    }
058
059    /**
060     * Parameterless constructor - allocates internal buffer by itself.
061     */
062    public SeekableInMemoryByteChannel() {
063        this(new byte[0]);
064    }
065
066    /**
067     * Constructor taking a size of storage to be allocated.
068     *
069     * <p>Creates a channel and allocates internal storage of a given size.</p>
070     *
071     * @param size size of internal buffer to allocate, in bytes.
072     */
073    public SeekableInMemoryByteChannel(int size) {
074        this(new byte[size]);
075    }
076
077    @Override
078    public long position() {
079        return position;
080    }
081
082    @Override
083    public SeekableByteChannel position(long newPosition) throws IOException {
084        ensureOpen();
085        if (newPosition < 0L || newPosition > Integer.MAX_VALUE) {
086            throw new IllegalArgumentException("Position has to be in range 0.. " + Integer.MAX_VALUE);
087        }
088        position = (int) newPosition;
089        return this;
090    }
091
092    @Override
093    public long size() {
094        return size;
095    }
096
097    @Override
098    public SeekableByteChannel truncate(long newSize) {
099        if (size > newSize) {
100            size = (int) newSize;
101        }
102        repositionIfNecessary();
103        return this;
104    }
105
106    @Override
107    public int read(ByteBuffer buf) throws IOException {
108        ensureOpen();
109        repositionIfNecessary();
110        int wanted = buf.remaining();
111        int possible = size - position;
112        if (possible <= 0) {
113            return -1;
114        }
115        if (wanted > possible) {
116            wanted = possible;
117        }
118        buf.put(data, position, wanted);
119        position += wanted;
120        return wanted;
121    }
122
123    @Override
124    public void close() {
125        closed.set(true);
126    }
127
128    @Override
129    public boolean isOpen() {
130        return !closed.get();
131    }
132
133    @Override
134    public int write(ByteBuffer b) throws IOException {
135        ensureOpen();
136        int wanted = b.remaining();
137        int possibleWithoutResize = size - position;
138        if (wanted > possibleWithoutResize) {
139            int newSize = position + wanted;
140            if (newSize < 0) { // overflow
141                resize(Integer.MAX_VALUE);
142                wanted = Integer.MAX_VALUE - position;
143            } else {
144                resize(newSize);
145            }
146        }
147        b.get(data, position, wanted);
148        position += wanted;
149        if (size < position) {
150            size = position;
151        }
152        return wanted;
153    }
154
155    /**
156     * Obtains the array backing this channel.
157     *
158     * <p>NOTE:
159     * The returned buffer is not aligned with containing data, use
160     * {@link #size()} to obtain the size of data stored in the buffer.</p>
161     *
162     * @return internal byte array.
163     */
164    public byte[] array() {
165        return data;
166    }
167
168    private void resize(int newLength) {
169        int len = data.length;
170        if (len <= 0) {
171            len = 1;
172        }
173        if (newLength < NAIVE_RESIZE_LIMIT) {
174            while (len < newLength) {
175                len <<= 1;
176            }
177        } else { // avoid overflow
178            len = newLength;
179        }
180        data = Arrays.copyOf(data, len);
181    }
182
183    private void ensureOpen() throws ClosedChannelException {
184        if (!isOpen()) {
185            throw new ClosedChannelException();
186        }
187    }
188
189    private void repositionIfNecessary() {
190        if (position > size) {
191            position = size;
192        }
193    }
194
195}