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 
19 package org.apache.commons.compress.utils;
20 
21 import java.io.IOException;
22 import java.nio.ByteBuffer;
23 import java.nio.channels.ClosedChannelException;
24 import java.nio.channels.SeekableByteChannel;
25 import java.util.Arrays;
26 import java.util.concurrent.atomic.AtomicBoolean;
27 
28 /**
29  * A {@link SeekableByteChannel} implementation that wraps a byte[].
30  *
31  * <p>When this channel is used for writing an internal buffer grows to accommodate
32  * incoming data. A natural size limit is the value of {@link Integer#MAX_VALUE}.
33  * Internal buffer can be accessed via {@link SeekableInMemoryByteChannel#array()}.</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      * Constructor taking a byte array.
48      *
49      * <p>This constructor is intended to be used with pre-allocated buffer or when
50      * reading from a given byte array.</p>
51      *
52      * @param data input data or pre-allocated array.
53      */
SeekableInMemoryByteChannel(byte[] data)54     public SeekableInMemoryByteChannel(byte[] data) {
55         this.data = data;
56         size = data.length;
57     }
58 
59     /**
60      * Parameterless constructor - allocates internal buffer by itself.
61      */
SeekableInMemoryByteChannel()62     public SeekableInMemoryByteChannel() {
63         this(new byte[0]);
64     }
65 
66     /**
67      * Constructor taking a size of storage to be allocated.
68      *
69      * <p>Creates a channel and allocates internal storage of a given size.</p>
70      *
71      * @param size size of internal buffer to allocate, in bytes.
72      */
SeekableInMemoryByteChannel(int size)73     public SeekableInMemoryByteChannel(int size) {
74         this(new byte[size]);
75     }
76 
77     @Override
position()78     public long position() {
79         return position;
80     }
81 
82     @Override
position(long newPosition)83     public SeekableByteChannel position(long newPosition) throws IOException {
84         ensureOpen();
85         if (newPosition < 0L || newPosition > Integer.MAX_VALUE) {
86             throw new IllegalArgumentException("Position has to be in range 0.. " + Integer.MAX_VALUE);
87         }
88         position = (int) newPosition;
89         return this;
90     }
91 
92     @Override
size()93     public long size() {
94         return size;
95     }
96 
97     @Override
truncate(long newSize)98     public SeekableByteChannel truncate(long newSize) {
99         if (size > newSize) {
100             size = (int) newSize;
101         }
102         repositionIfNecessary();
103         return this;
104     }
105 
106     @Override
read(ByteBuffer buf)107     public int read(ByteBuffer buf) throws IOException {
108         ensureOpen();
109         repositionIfNecessary();
110         int wanted = buf.remaining();
111         int possible = size - position;
112         if (possible <= 0) {
113             return -1;
114         }
115         if (wanted > possible) {
116             wanted = possible;
117         }
118         buf.put(data, position, wanted);
119         position += wanted;
120         return wanted;
121     }
122 
123     @Override
close()124     public void close() {
125         closed.set(true);
126     }
127 
128     @Override
isOpen()129     public boolean isOpen() {
130         return !closed.get();
131     }
132 
133     @Override
write(ByteBuffer b)134     public int write(ByteBuffer b) throws IOException {
135         ensureOpen();
136         int wanted = b.remaining();
137         int possibleWithoutResize = size - position;
138         if (wanted > possibleWithoutResize) {
139             int newSize = position + wanted;
140             if (newSize < 0) { // overflow
141                 resize(Integer.MAX_VALUE);
142                 wanted = Integer.MAX_VALUE - position;
143             } else {
144                 resize(newSize);
145             }
146         }
147         b.get(data, position, wanted);
148         position += wanted;
149         if (size < position) {
150             size = position;
151         }
152         return wanted;
153     }
154 
155     /**
156      * Obtains the array backing this channel.
157      *
158      * <p>NOTE:
159      * The returned buffer is not aligned with containing data, use
160      * {@link #size()} to obtain the size of data stored in the buffer.</p>
161      *
162      * @return internal byte array.
163      */
array()164     public byte[] array() {
165         return data;
166     }
167 
resize(int newLength)168     private void resize(int newLength) {
169         int len = data.length;
170         if (len <= 0) {
171             len = 1;
172         }
173         if (newLength < NAIVE_RESIZE_LIMIT) {
174             while (len < newLength) {
175                 len <<= 1;
176             }
177         } else { // avoid overflow
178             len = newLength;
179         }
180         data = Arrays.copyOf(data, len);
181     }
182 
ensureOpen()183     private void ensureOpen() throws ClosedChannelException {
184         if (!isOpen()) {
185             throw new ClosedChannelException();
186         }
187     }
188 
repositionIfNecessary()189     private void repositionIfNecessary() {
190         if (position > size) {
191             position = size;
192         }
193     }
194 
195 }
196