View Javadoc
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 }