SeekableInMemoryByteChannel.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *   http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */

  17. package org.apache.commons.compress.utils;

  18. import java.io.IOException;
  19. import java.nio.ByteBuffer;
  20. import java.nio.channels.ClosedChannelException;
  21. import java.nio.channels.SeekableByteChannel;
  22. import java.util.Arrays;
  23. import java.util.concurrent.atomic.AtomicBoolean;

  24. /**
  25.  * A {@link SeekableByteChannel} implementation that wraps a byte[].
  26.  * <p>
  27.  * 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}
  28.  * 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
  29.  * via {@link SeekableInMemoryByteChannel#array()}.
  30.  * </p>
  31.  *
  32.  * @since 1.13
  33.  * @NotThreadSafe
  34.  */
  35. public class SeekableInMemoryByteChannel implements SeekableByteChannel {

  36.     private static final int NAIVE_RESIZE_LIMIT = Integer.MAX_VALUE >> 1;

  37.     private byte[] data;
  38.     private final AtomicBoolean closed = new AtomicBoolean();
  39.     private int position, size;

  40.     /**
  41.      * Constructs a new instance using a default empty buffer.
  42.      */
  43.     public SeekableInMemoryByteChannel() {
  44.         this(ByteUtils.EMPTY_BYTE_ARRAY);
  45.     }

  46.     /**
  47.      * Constructs a new instance from a byte array.
  48.      * <p>
  49.      * This constructor is intended to be used with pre-allocated buffer or when reading from a given byte array.
  50.      * </p>
  51.      *
  52.      * @param data input data or pre-allocated array.
  53.      */
  54.     public SeekableInMemoryByteChannel(final byte[] data) {
  55.         this.data = data;
  56.         this.size = data.length;
  57.     }

  58.     /**
  59.      * Constructs a new instance from a size of storage to be allocated.
  60.      * <p>
  61.      * Creates a channel and allocates internal storage of a given size.
  62.      * </p>
  63.      *
  64.      * @param size size of internal buffer to allocate, in bytes.
  65.      */
  66.     public SeekableInMemoryByteChannel(final int size) {
  67.         this(new byte[size]);
  68.     }

  69.     /**
  70.      * Obtains the array backing this channel.
  71.      * <p>
  72.      * NOTE: The returned buffer is not aligned with containing data, use {@link #size()} to obtain the size of data stored in the buffer.
  73.      * </p>
  74.      *
  75.      * @return internal byte array.
  76.      */
  77.     public byte[] array() {
  78.         return data;
  79.     }

  80.     @Override
  81.     public void close() {
  82.         closed.set(true);
  83.     }

  84.     private void ensureOpen() throws ClosedChannelException {
  85.         if (!isOpen()) {
  86.             throw new ClosedChannelException();
  87.         }
  88.     }

  89.     @Override
  90.     public boolean isOpen() {
  91.         return !closed.get();
  92.     }

  93.     /**
  94.      * Returns this channel's position.
  95.      * <p>
  96.      * This method violates the contract of {@link SeekableByteChannel#position()} as it will not throw any exception when invoked on a closed channel. Instead
  97.      * it will return the position the channel had when close has been called.
  98.      * </p>
  99.      */
  100.     @Override
  101.     public long position() {
  102.         return position;
  103.     }

  104.     @Override
  105.     public SeekableByteChannel position(final long newPosition) throws IOException {
  106.         ensureOpen();
  107.         if (newPosition < 0L || newPosition > Integer.MAX_VALUE) {
  108.             throw new IOException("Position has to be in range 0.. " + Integer.MAX_VALUE);
  109.         }
  110.         position = (int) newPosition;
  111.         return this;
  112.     }

  113.     @Override
  114.     public int read(final ByteBuffer buf) throws IOException {
  115.         ensureOpen();
  116.         int wanted = buf.remaining();
  117.         final int possible = size - position;
  118.         if (possible <= 0) {
  119.             return -1;
  120.         }
  121.         if (wanted > possible) {
  122.             wanted = possible;
  123.         }
  124.         buf.put(data, position, wanted);
  125.         position += wanted;
  126.         return wanted;
  127.     }

  128.     private void resize(final int newLength) {
  129.         int len = data.length;
  130.         if (len <= 0) {
  131.             len = 1;
  132.         }
  133.         if (newLength < NAIVE_RESIZE_LIMIT) {
  134.             while (len < newLength) {
  135.                 len <<= 1;
  136.             }
  137.         } else { // avoid overflow
  138.             len = newLength;
  139.         }
  140.         data = Arrays.copyOf(data, len);
  141.     }

  142.     /**
  143.      * Returns the current size of entity to which this channel is connected.
  144.      * <p>
  145.      * This method violates the contract of {@link SeekableByteChannel#size} as it will not throw any exception when invoked on a closed channel. Instead it
  146.      * will return the size the channel had when close has been called.
  147.      * </p>
  148.      */
  149.     @Override
  150.     public long size() {
  151.         return size;
  152.     }

  153.     /**
  154.      * Truncates the entity, to which this channel is connected, to the given size.
  155.      * <p>
  156.      * This method violates the contract of {@link SeekableByteChannel#truncate} as it will not throw any exception when invoked on a closed channel.
  157.      * </p>
  158.      *
  159.      * @throws IllegalArgumentException if size is negative or bigger than the maximum of a Java integer
  160.      */
  161.     @Override
  162.     public SeekableByteChannel truncate(final long newSize) {
  163.         if (newSize < 0L || newSize > Integer.MAX_VALUE) {
  164.             throw new IllegalArgumentException("Size has to be in range 0.. " + Integer.MAX_VALUE);
  165.         }
  166.         if (size > newSize) {
  167.             size = (int) newSize;
  168.         }
  169.         if (position > newSize) {
  170.             position = (int) newSize;
  171.         }
  172.         return this;
  173.     }

  174.     @Override
  175.     public int write(final ByteBuffer b) throws IOException {
  176.         ensureOpen();
  177.         int wanted = b.remaining();
  178.         final int possibleWithoutResize = size - position;
  179.         if (wanted > possibleWithoutResize) {
  180.             final int newSize = position + wanted;
  181.             if (newSize < 0) { // overflow
  182.                 resize(Integer.MAX_VALUE);
  183.                 wanted = Integer.MAX_VALUE - position;
  184.             } else {
  185.                 resize(newSize);
  186.             }
  187.         }
  188.         b.get(data, position, wanted);
  189.         position += wanted;
  190.         if (size < position) {
  191.             size = position;
  192.         }
  193.         return wanted;
  194.     }

  195. }