View Javadoc
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  
18  package org.apache.commons.compress.utils;
19  
20  import java.io.IOException;
21  import java.nio.ByteBuffer;
22  import java.nio.channels.ClosedChannelException;
23  import java.nio.channels.SeekableByteChannel;
24  import java.util.Arrays;
25  import java.util.concurrent.atomic.AtomicBoolean;
26  
27  /**
28   * A {@link SeekableByteChannel} implementation that wraps a byte[].
29   * <p>
30   * 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}
31   * 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
32   * via {@link SeekableInMemoryByteChannel#array()}.
33   * </p>
34   *
35   * @since 1.13
36   * @NotThreadSafe
37   */
38  public class SeekableInMemoryByteChannel implements SeekableByteChannel {
39  
40      private static final int NAIVE_RESIZE_LIMIT = Integer.MAX_VALUE >> 1;
41  
42      private byte[] data;
43      private final AtomicBoolean closed = new AtomicBoolean();
44      private int position, size;
45  
46      /**
47       * Constructs a new instance using a default empty buffer.
48       */
49      public SeekableInMemoryByteChannel() {
50          this(ByteUtils.EMPTY_BYTE_ARRAY);
51      }
52  
53      /**
54       * Constructs a new instance from a byte array.
55       * <p>
56       * This constructor is intended to be used with pre-allocated buffer or when reading from a given byte array.
57       * </p>
58       *
59       * @param data input data or pre-allocated array.
60       */
61      public SeekableInMemoryByteChannel(final byte[] data) {
62          this.data = data;
63          this.size = data.length;
64      }
65  
66      /**
67       * Constructs a new instance from a size of storage to be allocated.
68       * <p>
69       * Creates a channel and allocates internal storage of a given size.
70       * </p>
71       *
72       * @param size size of internal buffer to allocate, in bytes.
73       */
74      public SeekableInMemoryByteChannel(final int size) {
75          this(new byte[size]);
76      }
77  
78      /**
79       * Obtains the array backing this channel.
80       * <p>
81       * NOTE: The returned buffer is not aligned with containing data, use {@link #size()} to obtain the size of data stored in the buffer.
82       * </p>
83       *
84       * @return internal byte array.
85       */
86      public byte[] array() {
87          return data;
88      }
89  
90      @Override
91      public void close() {
92          closed.set(true);
93      }
94  
95      private void ensureOpen() throws ClosedChannelException {
96          if (!isOpen()) {
97              throw new ClosedChannelException();
98          }
99      }
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 }