    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
    018package org.apache.commons.compress.utils;
    019
    020import java.io.IOException;
    021import java.nio.ByteBuffer;
    022import java.nio.channels.ClosedChannelException;
    023import java.nio.channels.SeekableByteChannel;
    024import java.util.Arrays;
    025import java.util.concurrent.atomic.AtomicBoolean;
    026
    027/**
    028 * A {@link SeekableByteChannel} implementation that wraps a byte[].
    029 *
    030 * <p>When this channel is used for writing an internal buffer grows to accommodate incoming data. The natural size
    031 * limit is the value of {@link Integer#MAX_VALUE} and it is not possible to {@link #position(long) set the position} or
    032 * {@link #truncate truncate} to a value bigger than that.  Internal buffer can be accessed via {@link
    033 * 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     * Parameterless constructor - allocates internal buffer by itself.
    048     */
    049    public SeekableInMemoryByteChannel() {
    050        this(ByteUtils.EMPTY_BYTE_ARRAY);
    051    }
    052
    053    /**
    054     * Constructor taking a byte array.
    055     *
    056     * <p>This constructor is intended to be used with pre-allocated buffer or when
    057     * reading from a given byte array.</p>
    058     *
    059     * @param data input data or pre-allocated array.
    060     */
    061    public SeekableInMemoryByteChannel(final byte[] data) {
    062        this.data = data;
    063        size = data.length;
    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(final int size) {
    074        this(new byte[size]);
    075    }
    076
    077    /**
    078     * Obtains the array backing this channel.
    079     *
    080     * <p>NOTE:
    081     * The returned buffer is not aligned with containing data, use
    082     * {@link #size()} to obtain the size of data stored in the buffer.</p>
    083     *
    084     * @return internal byte array.
    085     */
    086    public byte[] array() {
    087        return data;
    088    }
    089
    090    @Override
    091    public void close() {
    092        closed.set(true);
    093    }
    094
    095    private void ensureOpen() throws ClosedChannelException {
    096        if (!isOpen()) {
    097            throw new ClosedChannelException();
    098        }
    099    }
    100
    101    @Override
    102    public boolean isOpen() {
    103        return !closed.get();
    104    }
    105
    106    /**
    107     * Returns this channel's position.
    108     *
    109     * <p>This method violates the contract of {@link SeekableByteChannel#position()} as it will not throw any exception
    110     * when invoked on a closed channel. Instead it will return the position the channel had when close has been
    111     * called.</p>
    112     */
    113    @Override
    114    public long position() {
    115        return position;
    116    }
    117
    118    @Override
    119    public SeekableByteChannel position(final long newPosition) throws IOException {
    120        ensureOpen();
    121        if (newPosition < 0L || newPosition > Integer.MAX_VALUE) {
    122            throw new IOException("Position has to be in range 0.. " + Integer.MAX_VALUE);
    123        }
    124        position = (int) newPosition;
    125        return this;
    126    }
    127
    128    @Override
    129    public int read(final ByteBuffer buf) throws IOException {
    130        ensureOpen();
    131        int wanted = buf.remaining();
    132        final int possible = size - position;
    133        if (possible <= 0) {
    134            return -1;
    135        }
    136        if (wanted > possible) {
    137            wanted = possible;
    138        }
    139        buf.put(data, position, wanted);
    140        position += wanted;
    141        return wanted;
    142    }
    143
    144    private void resize(final int newLength) {
    145        int len = data.length;
    146        if (len <= 0) {
    147            len = 1;
    148        }
    149        if (newLength < NAIVE_RESIZE_LIMIT) {
    150            while (len < newLength) {
    151                len <<= 1;
    152            }
    153        } else { // avoid overflow
    154            len = newLength;
    155        }
    156        data = Arrays.copyOf(data, len);
    157    }
    158
    159    /**
    160     * Returns the current size of entity to which this channel is connected.
    161     *
    162     * <p>This method violates the contract of {@link SeekableByteChannel#size} as it will not throw any exception when
    163     * invoked on a closed channel. Instead it will return the size the channel had when close has been called.</p>
    164     */
    165    @Override
    166    public long size() {
    167        return size;
    168    }
    169
    170    /**
    171     * Truncates the entity, to which this channel is connected, to the given size.
    172     *
    173     * <p>This method violates the contract of {@link SeekableByteChannel#truncate} as it will not throw any exception when
    174     * invoked on a closed channel.</p>
    175     *
    176     * @throws IllegalArgumentException if size is negative or bigger than the maximum of a Java integer
    177     */
    178    @Override
    179    public SeekableByteChannel truncate(final long newSize) {
    180        if (newSize < 0L || newSize > Integer.MAX_VALUE) {
    181            throw new IllegalArgumentException("Size has to be in range 0.. " + Integer.MAX_VALUE);
    182        }
    183        if (size > newSize) {
    184            size = (int) newSize;
    185        }
    186        if (position > newSize) {
    187            position = (int) newSize;
    188        }
    189        return this;
    190    }
    191
    192    @Override
    193    public int write(final ByteBuffer b) throws IOException {
    194        ensureOpen();
    195        int wanted = b.remaining();
    196        final int possibleWithoutResize = size - position;
    197        if (wanted > possibleWithoutResize) {
    198            final int newSize = position + wanted;
    199            if (newSize < 0) { // overflow
    200                resize(Integer.MAX_VALUE);
    201                wanted = Integer.MAX_VALUE - position;
    202            } else {
    203                resize(newSize);
    204            }
    205        }
    206        b.get(data, position, wanted);
    207        position += wanted;
    208        if (size < position) {
    209            size = position;
    210        }
    211        return wanted;
    212    }
    213
    214}