    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}