    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 * <p>
    030 * When this channel is used for writing an internal buffer grows to accommodate incoming data. The natural size limit is the value of {@link Integer#MAX_VALUE}
    031 * and it is not possible to {@link #position(long) set the position} or {@link #truncate truncate} to a value bigger than that. Internal buffer can be accessed
    032 * via {@link SeekableInMemoryByteChannel#array()}.
    033 * </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     * Constructs a new instance using a default empty buffer.
    048     */
    049    public SeekableInMemoryByteChannel() {
    050        this(ByteUtils.EMPTY_BYTE_ARRAY);
    051    }
    052
    053    /**
    054     * Constructs a new instance from a byte array.
    055     * <p>
    056     * This constructor is intended to be used with pre-allocated buffer or when reading from a given byte array.
    057     * </p>
    058     *
    059     * @param data input data or pre-allocated array.
    060     */
    061    public SeekableInMemoryByteChannel(final byte[] data) {
    062        this.data = data;
    063        this.size = data.length;
    064    }
    065
    066    /**
    067     * Constructs a new instance from a size of storage to be allocated.
    068     * <p>
    069     * Creates a channel and allocates internal storage of a given size.
    070     * </p>
    071     *
    072     * @param size size of internal buffer to allocate, in bytes.
    073     */
    074    public SeekableInMemoryByteChannel(final int size) {
    075        this(new byte[size]);
    076    }
    077
    078    /**
    079     * Obtains the array backing this channel.
    080     * <p>
    081     * NOTE: The returned buffer is not aligned with containing data, use {@link #size()} to obtain the size of data stored in the buffer.
    082     * </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     * <p>
    109     * This method violates the contract of {@link SeekableByteChannel#position()} as it will not throw any exception when invoked on a closed channel. Instead
    110     * it will return the position the channel had when close has been called.
    111     * </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     * <p>
    162     * This method violates the contract of {@link SeekableByteChannel#size} as it will not throw any exception when invoked on a closed channel. Instead it
    163     * will return the size the channel had when close has been called.
    164     * </p>
    165     */
    166    @Override
    167    public long size() {
    168        return size;
    169    }
    170
    171    /**
    172     * Truncates the entity, to which this channel is connected, to the given size.
    173     * <p>
    174     * This method violates the contract of {@link SeekableByteChannel#truncate} as it will not throw any exception when invoked on a closed channel.
    175     * </p>
    176     *
    177     * @throws IllegalArgumentException if size is negative or bigger than the maximum of a Java integer
    178     */
    179    @Override
    180    public SeekableByteChannel truncate(final long newSize) {
    181        if (newSize < 0L || newSize > Integer.MAX_VALUE) {
    182            throw new IllegalArgumentException("Size has to be in range 0.. " + Integer.MAX_VALUE);
    183        }
    184        if (size > newSize) {
    185            size = (int) newSize;
    186        }
    187        if (position > newSize) {
    188            position = (int) newSize;
    189        }
    190        return this;
    191    }
    192
    193    @Override
    194    public int write(final ByteBuffer b) throws IOException {
    195        ensureOpen();
    196        int wanted = b.remaining();
    197        final int possibleWithoutResize = size - position;
    198        if (wanted > possibleWithoutResize) {
    199            final int newSize = position + wanted;
    200            if (newSize < 0) { // overflow
    201                resize(Integer.MAX_VALUE);
    202                wanted = Integer.MAX_VALUE - position;
    203            } else {
    204                resize(newSize);
    205            }
    206        }
    207        b.get(data, position, wanted);
    208        position += wanted;
    209        if (size < position) {
    210            size = position;
    211        }
    212        return wanted;
    213    }
    214
    215}