1 package nom.tam.util;
2
3 /*
4 * #%L
5 * nom.tam FITS library
6 * %%
7 * Copyright (C) 1996 - 2024 nom-tam-fits
8 * %%
9 * This is free and unencumbered software released into the public domain.
10 *
11 * Anyone is free to copy, modify, publish, use, compile, sell, or
12 * distribute this software, either in source code form or as a compiled
13 * binary, for any purpose, commercial or non-commercial, and by any
14 * means.
15 *
16 * In jurisdictions that recognize copyright laws, the author or authors
17 * of this software dedicate any and all copyright interest in the
18 * software to the public domain. We make this dedication for the benefit
19 * of the public at large and to the detriment of our heirs and
20 * successors. We intend this dedication to be an overt act of
21 * relinquishment in perpetuity of all present and future rights to this
22 * software under copyright law.
23 *
24 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
25 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
26 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
27 * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
28 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
29 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
30 * OTHER DEALINGS IN THE SOFTWARE.
31 * #L%
32 */
33
34 import java.io.EOFException;
35 import java.io.IOException;
36 import java.util.Arrays;
37
38 /**
39 * Reading from and writing to byte arrays with a stream-like interface (<i>primarily for internal use</i>) .
40 *
41 * @author Attila Kovacs
42 *
43 * @since 1.16
44 */
45 public class ByteArrayIO implements ReadWriteAccess {
46
47 /** Mask for 1-byte */
48 private static final int BYTE_MASK = 0xFF;
49
50 /** The underlying buffer */
51 private byte[] buf;
52
53 /** Whether the buffer is allowed to grow as needed to contain more data */
54 private boolean isGrowable;
55
56 /** The current pointer position, for the next read or write in the buffer */
57 private int pos;
58
59 /** The current end of the buffer, that is the total number of bytes available for reading from the buffer */
60 private int end;
61
62 /** For thread synchronization */
63 private Object lock = new Object();
64
65 /**
66 * Instantiates a new byte array with an IO interface, with a fixed-size buffer using the specified array as its
67 * backing storage.
68 *
69 * @param buffer the fixed buffer.
70 */
71 public ByteArrayIO(byte[] buffer) {
72 buf = buffer;
73 end = 0;
74 isGrowable = false;
75 }
76
77 /**
78 * Instantiates a new byte array with an IO interface, with a growable buffer initialized to the specific size.
79 *
80 * @param initialCapacity the number of bytes to contain in the buffer.
81 *
82 * @throws IllegalArgumentException if the initial capacity is 0 or negative
83 */
84 public ByteArrayIO(int initialCapacity) throws IllegalArgumentException {
85 if (initialCapacity <= 0) {
86 throw new IllegalArgumentException("Illegal buffer size:" + initialCapacity);
87 }
88
89 buf = new byte[initialCapacity];
90 end = 0;
91 isGrowable = true;
92 }
93
94 /**
95 * Returns a copy of this byte array with an IO interface, including a deep copy of the buffered data.
96 *
97 * @return a deep copy of this byte array with an IO interface instance.
98 */
99 public ByteArrayIO copy() {
100 synchronized (lock) {
101 ByteArrayIO copy = new ByteArrayIO(Arrays.copyOf(buf, buf.length));
102 synchronized (copy) {
103 copy.isGrowable = isGrowable;
104 copy.pos = pos;
105 copy.end = end;
106 }
107 return copy;
108 }
109 }
110
111 /**
112 * Returns the underlying byte array, which is the backing array of this buffer.
113 *
114 * @return the backing array of this buffer.
115 */
116 public byte[] getBuffer() {
117 synchronized (lock) {
118 return buf;
119 }
120 }
121
122 /**
123 * Returns the current capacity of this buffer, that is the total number of bytes that may be written into the
124 * current backing buffer.
125 *
126 * @return the current size of the backing array.
127 */
128 public final int capacity() {
129 synchronized (lock) {
130 return buf.length;
131 }
132 }
133
134 @Override
135 public final long length() {
136 synchronized (lock) {
137 return end;
138 }
139 }
140
141 /**
142 * Returns the number of bytes available for reading from the current position.
143 *
144 * @return the number of bytes that can be read from this buffer from the current position.
145 */
146 public final int getRemaining() {
147 synchronized (lock) {
148 if (pos >= end) {
149 return 0;
150 }
151 return end - pos;
152 }
153 }
154
155 @Override
156 public final long position() {
157 synchronized (lock) {
158 return pos;
159 }
160 }
161
162 @Override
163 public void position(long offset) throws IOException {
164 if (offset < 0) {
165 throw new EOFException("Negative buffer index: " + offset);
166 }
167
168 synchronized (lock) {
169 if (offset > buf.length) {
170 if (!isGrowable) {
171 throw new EOFException("Position " + offset + " beyond fixed buffer size " + buf.length);
172 }
173 }
174
175 pos = (int) offset;
176 }
177 }
178
179 /**
180 * Changes the length of this buffer. The total number of bytes available from reading from this buffer will be that
181 * of the new length. If the buffer is truncated and its pointer is positioned beyond the new size, then the pointer
182 * is changed to point to the new buffer end. If the buffer is enlarged beyond it current capacity, its capacity
183 * will grow as necessary provided the buffer is growable. Otherwise, an EOFException is thrown if the new length is
184 * beyond the fixed buffer capacity. If the new length is larger than the old one, the added buffer segment may have
185 * undefined contents.
186 *
187 * @param length The buffer length, that is number of bytes available for reading.
188 *
189 * @throws IllegalArgumentException if the length is negative or if the new new length exceeds the capacity of a
190 * fixed-type buffer.
191 *
192 * @see #length()
193 * @see #capacity()
194 */
195 public void setLength(int length) throws IllegalArgumentException {
196 if (length < 0) {
197 throw new IllegalArgumentException("Buffer set to negative length: " + length);
198 }
199
200 synchronized (lock) {
201 if (length > capacity()) {
202 if (!isGrowable) {
203 throw new IllegalArgumentException(
204 "the new length " + length + " is larger than the fixed capacity " + capacity());
205 }
206 grow(length - capacity());
207 }
208 end = length;
209
210 // If the pointer is beyond the new size, move it back to the new end...
211 if (pos > end) {
212 pos = end;
213 }
214 }
215 }
216
217 /**
218 * Grows the buffer by at least the number of specified bytes. A new buffer is allocated, and the contents of the
219 * previous buffer are copied over.
220 *
221 * @param need the minimum number of extra bytes needed beyond the current capacity.
222 */
223 private void grow(int need) {
224 synchronized (lock) {
225 long size = capacity() + need;
226 long below = Long.highestOneBit(size);
227 if (below != size) {
228 size = below << 1;
229 }
230 byte[] newbuf = new byte[(int) Math.min(size, Integer.MAX_VALUE)];
231 System.arraycopy(buf, 0, newbuf, 0, buf.length);
232 buf = newbuf;
233 }
234 }
235
236 @Override
237 public final void write(int b) throws IOException {
238 synchronized (lock) {
239 if (pos + 1 > buf.length) {
240 if (!isGrowable) {
241 throw new EOFException("buffer is full (size=" + length() + ")");
242 }
243 grow(pos + 1 - buf.length);
244 }
245 buf[pos++] = (byte) b;
246 if (pos > end) {
247 end = pos;
248 }
249 }
250 }
251
252 @Override
253 public final void write(byte[] b, int from, int length) throws IOException {
254 if (length <= 0) {
255 return;
256 }
257
258 synchronized (lock) {
259 if (pos > buf.length || (isGrowable && pos + length > buf.length)) {
260 // This may only happen in a growable buffer...
261 grow(buf.length + length - pos);
262 }
263
264 int l = Math.min(length, buf.length - pos);
265
266 System.arraycopy(b, from, buf, pos, l);
267 pos += l;
268 if (pos > end) {
269 end = pos;
270 }
271
272 if (l < length) {
273 throw new EOFException(
274 "Incomplete write of " + l + " of " + length + " bytes in buffer of size " + length());
275 }
276 }
277 }
278
279 @Override
280 public final int read() throws IOException {
281 synchronized (lock) {
282 if (getRemaining() <= 0) {
283 return -1;
284 }
285 return buf[pos++] & BYTE_MASK;
286 }
287 }
288
289 @Override
290 public final int read(byte[] b, int from, int length) {
291 if (length <= 0) {
292 return 0;
293 }
294
295 synchronized (lock) {
296 int remaining = getRemaining();
297
298 if (remaining <= 0) {
299 return -1;
300 }
301
302 int n = Math.min(remaining, length);
303 System.arraycopy(buf, pos, b, from, n);
304 pos += n;
305
306 return n;
307 }
308 }
309 }