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      /**
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 }