    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 incoming data. The natural size
    032 * limit is the value of {@link Integer#MAX_VALUE} and it is not possible to {@link #position(long) set the position} or
    033 * {@link #truncate truncate} to a value bigger than that.  Internal buffer can be accessed via {@link
    034 * SeekableInMemoryByteChannel#array()}.</p>
    035 *
    036 * @since 1.13
    037 * @NotThreadSafe
    038 */
    039public class SeekableInMemoryByteChannel implements SeekableByteChannel {
    040
    041    private static final int NAIVE_RESIZE_LIMIT = Integer.MAX_VALUE >> 1;
    042
    043    private byte[] data;
    044    private final AtomicBoolean closed = new AtomicBoolean();
    045    private int position, size;
    046
    047    /**
    048     * Parameterless constructor - allocates internal buffer by itself.
    049     */
    050    public SeekableInMemoryByteChannel() {
    051        this(ByteUtils.EMPTY_BYTE_ARRAY);
    052    }
    053
    054    /**
    055     * Constructor taking a byte array.
    056     *
    057     * <p>This constructor is intended to be used with pre-allocated buffer or when
    058     * reading from a given byte array.</p>
    059     *
    060     * @param data input data or pre-allocated array.
    061     */
    062    public SeekableInMemoryByteChannel(final byte[] data) {
    063        this.data = data;
    064        size = data.length;
    065    }
    066
    067    /**
    068     * Constructor taking a size of storage to be allocated.
    069     *
    070     * <p>Creates a channel and allocates internal storage of a given size.</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     *
    081     * <p>NOTE:
    082     * The returned buffer is not aligned with containing data, use
    083     * {@link #size()} to obtain the size of data stored in the buffer.</p>
    084     *
    085     * @return internal byte array.
    086     */
    087    public byte[] array() {
    088        return data;
    089    }
    090
    091    @Override
    092    public void close() {
    093        closed.set(true);
    094    }
    095
    096    private void ensureOpen() throws ClosedChannelException {
    097        if (!isOpen()) {
    098            throw new ClosedChannelException();
    099        }
    100    }
    101
    102    @Override
    103    public boolean isOpen() {
    104        return !closed.get();
    105    }
    106
    107    /**
    108     * Returns this channel's position.
    109     *
    110     * <p>This method violates the contract of {@link SeekableByteChannel#position()} as it will not throw any exception
    111     * when invoked on a closed channel. Instead it will return the position the channel had when close has been
    112     * called.</p>
    113     */
    114    @Override
    115    public long position() {
    116        return position;
    117    }
    118
    119    @Override
    120    public SeekableByteChannel position(final long newPosition) throws IOException {
    121        ensureOpen();
    122        if (newPosition < 0L || newPosition > Integer.MAX_VALUE) {
    123            throw new IOException("Position has to be in range 0.. " + Integer.MAX_VALUE);
    124        }
    125        position = (int) newPosition;
    126        return this;
    127    }
    128
    129    @Override
    130    public int read(final ByteBuffer buf) throws IOException {
    131        ensureOpen();
    132        int wanted = buf.remaining();
    133        final int possible = size - position;
    134        if (possible <= 0) {
    135            return -1;
    136        }
    137        if (wanted > possible) {
    138            wanted = possible;
    139        }
    140        buf.put(data, position, wanted);
    141        position += wanted;
    142        return wanted;
    143    }
    144
    145    private void resize(final int newLength) {
    146        int len = data.length;
    147        if (len <= 0) {
    148            len = 1;
    149        }
    150        if (newLength < NAIVE_RESIZE_LIMIT) {
    151            while (len < newLength) {
    152                len <<= 1;
    153            }
    154        } else { // avoid overflow
    155            len = newLength;
    156        }
    157        data = Arrays.copyOf(data, len);
    158    }
    159
    160    /**
    161     * Returns the current size of entity to which this channel is connected.
    162     *
    163     * <p>This method violates the contract of {@link SeekableByteChannel#size} as it will not throw any exception when
    164     * invoked on a closed channel. Instead it will return the size the channel had when close has been called.</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     *
    174     * <p>This method violates the contract of {@link SeekableByteChannel#truncate} as it will not throw any exception when
    175     * invoked on a closed channel.</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}