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 /**
63 * Instantiates a new byte array with an IO interface, with a fixed-size buffer using the specified array as its
64 * backing storage.
65 *
66 * @param buffer the fixed buffer.
67 */
68 public ByteArrayIO(byte[] buffer) {
69 buf = buffer;
70 end = 0;
71 isGrowable = false;
72 }
73
74 /**
75 * Instantiates a new byte array with an IO interface, with a growable buffer initialized to the specific size.
76 *
77 * @param initialCapacity the number of bytes to contain in the buffer.
78 *
79 * @throws IllegalArgumentException if the initial capacity is 0 or negative
80 */
81 public ByteArrayIO(int initialCapacity) throws IllegalArgumentException {
82 if (initialCapacity <= 0) {
83 throw new IllegalArgumentException("Illegal buffer size:" + initialCapacity);
84 }
85
86 buf = new byte[initialCapacity];
87 end = 0;
88 isGrowable = true;
89 }
90
91 /**
92 * Returns a copy of this byte array with an IO interface, including a deep copy of the buffered data.
93 *
94 * @return a deep copy of this byte array with an IO interface instance.
95 */
96 public synchronized ByteArrayIO copy() {
97 ByteArrayIO copy = new ByteArrayIO(Arrays.copyOf(buf, buf.length));
98 synchronized (copy) {
99 copy.isGrowable = isGrowable;
100 copy.pos = pos;
101 copy.end = end;
102 }
103 return copy;
104 }
105
106 /**
107 * Returns the underlying byte array, which is the backing array of this buffer.
108 *
109 * @return the backing array of this buffer.
110 */
111 public synchronized byte[] getBuffer() {
112 return buf;
113 }
114
115 /**
116 * Returns the current capacity of this buffer, that is the total number of bytes that may be written into the
117 * current backing buffer.
118 *
119 * @return the current size of the backing array.
120 */
121 public final synchronized int capacity() {
122 return buf.length;
123 }
124
125 @Override
126 public final synchronized long length() {
127 return end;
128 }
129
130 /**
131 * Returns the number of bytes available for reading from the current position.
132 *
133 * @return the number of bytes that can be read from this buffer from the current position.
134 */
135 public final synchronized int getRemaining() {
136 if (pos >= end) {
137 return 0;
138 }
139 return end - pos;
140 }
141
142 @Override
143 public final synchronized long position() {
144 return pos;
145 }
146
147 @Override
148 public synchronized void position(long offset) throws IOException {
149 if (offset < 0) {
150 throw new EOFException("Negative buffer index: " + offset);
151 }
152
153 if (offset > buf.length) {
154 if (!isGrowable) {
155 throw new EOFException("Position " + offset + " beyond fixed buffer size " + buf.length);
156 }
157 }
158
159 pos = (int) offset;
160 }
161
162 /**
163 * Changes the length of this buffer. The total number of bytes available from reading from this buffer will be that
164 * of the new length. If the buffer is truncated and its pointer is positioned beyond the new size, then the pointer
165 * is changed to point to the new buffer end. If the buffer is enlarged beyond it current capacity, its capacity
166 * will grow as necessary provided the buffer is growable. Otherwise, an EOFException is thrown if the new length is
167 * beyond the fixed buffer capacity. If the new length is larger than the old one, the added buffer segment may have
168 * undefined contents.
169 *
170 * @param length The buffer length, that is number of bytes available for reading.
171 *
172 * @throws IllegalArgumentException if the length is negative or if the new new length exceeds the capacity of a
173 * fixed-type buffer.
174 *
175 * @see #length()
176 * @see #capacity()
177 */
178 public synchronized void setLength(int length) throws IllegalArgumentException {
179 if (length < 0) {
180 throw new IllegalArgumentException("Buffer set to negative length: " + length);
181 }
182
183 if (length > capacity()) {
184 if (!isGrowable) {
185 throw new IllegalArgumentException(
186 "the new length " + length + " is larger than the fixed capacity " + capacity());
187 }
188 grow(length - capacity());
189 }
190 end = length;
191
192 // If the pointer is beyond the new size, move it back to the new end...
193 if (pos > end) {
194 pos = end;
195 }
196 }
197
198 /**
199 * Grows the buffer by at least the number of specified bytes. A new buffer is allocated, and the contents of the
200 * previous buffer are copied over.
201 *
202 * @param need the minimum number of extra bytes needed beyond the current capacity.
203 */
204 private synchronized void grow(int need) {
205 long size = capacity() + need;
206 long below = Long.highestOneBit(size);
207 if (below != size) {
208 size = below << 1;
209 }
210 byte[] newbuf = new byte[(int) Math.min(size, Integer.MAX_VALUE)];
211 System.arraycopy(buf, 0, newbuf, 0, buf.length);
212 buf = newbuf;
213 }
214
215 @Override
216 public final synchronized void write(int b) throws IOException {
217 if (pos + 1 > buf.length) {
218 if (!isGrowable) {
219 throw new EOFException("buffer is full (size=" + length() + ")");
220 }
221 grow(pos + 1 - buf.length);
222 }
223 buf[pos++] = (byte) b;
224 if (pos > end) {
225 end = pos;
226 }
227 }
228
229 @Override
230 public final synchronized void write(byte[] b, int from, int length) throws IOException {
231 if (length <= 0) {
232 return;
233 }
234
235 if (pos > buf.length || (isGrowable && pos + length > buf.length)) {
236 // This may only happen in a growable buffer...
237 grow(buf.length + length - pos);
238 }
239
240 int l = Math.min(length, buf.length - pos);
241
242 System.arraycopy(b, from, buf, pos, l);
243 pos += l;
244 if (pos > end) {
245 end = pos;
246 }
247
248 if (l < length) {
249 throw new EOFException("Incomplete write of " + l + " of " + length + " bytes in buffer of size " + length());
250 }
251 }
252
253 @Override
254 public final synchronized int read() throws IOException {
255 if (getRemaining() <= 0) {
256 return -1;
257 }
258 return buf[pos++] & BYTE_MASK;
259 }
260
261 @Override
262 public final synchronized int read(byte[] b, int from, int length) {
263 if (length <= 0) {
264 return 0;
265 }
266
267 int remaining = getRemaining();
268
269 if (remaining <= 0) {
270 return -1;
271 }
272
273 int n = Math.min(remaining, length);
274 System.arraycopy(buf, pos, b, from, n);
275 pos += n;
276
277 return n;
278 }
279 }