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  import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
39  
40  /**
41   * Reading from and writing to byte arrays with a stream-like interface (<i>primarily for internal use</i>) .
42   *
43   * @author Attila Kovacs
44   *
45   * @since  1.16
46   */
47  public class ByteArrayIO implements ReadWriteAccess {
48  
49      /** Mask for 1-byte */
50      private static final int BYTE_MASK = 0xFF;
51  
52      /** The underlying buffer */
53      private byte[] buf;
54  
55      /** Whether the buffer is allowed to grow as needed to contain more data */
56      private boolean isGrowable;
57  
58      /** The current pointer position, for the next read or write in the buffer */
59      private int pos;
60  
61      /** The current end of the buffer, that is the total number of bytes available for reading from the buffer */
62      private int end;
63  
64      /**
65       * Instantiates a new byte array with an IO interface, with a fixed-size buffer using the specified array as its
66       * backing storage.
67       *
68       * @param buffer the fixed buffer.
69       */
70      @SuppressFBWarnings(value = "EI_EXPOSE_REP2", justification = "by design this class provides an IO interface for an accessible array.")
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 synchronized ByteArrayIO copy() {
100         ByteArrayIO copy = new ByteArrayIO(Arrays.copyOf(buf, buf.length));
101         synchronized (copy) {
102             copy.isGrowable = isGrowable;
103             copy.pos = pos;
104             copy.end = end;
105         }
106         return copy;
107     }
108 
109     /**
110      * Returns the underlying byte array, which is the backing array of this buffer.
111      *
112      * @return the backing array of this buffer.
113      */
114     @SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "by design this class provides an IO interface for an accessible array.")
115     public synchronized byte[] getBuffer() {
116         return buf;
117     }
118 
119     /**
120      * Returns the current capacity of this buffer, that is the total number of bytes that may be written into the
121      * current backing buffer.
122      *
123      * @return the current size of the backing array.
124      */
125     public final synchronized int capacity() {
126         return buf.length;
127     }
128 
129     @Override
130     public final synchronized long length() {
131         return end;
132     }
133 
134     /**
135      * Returns the number of bytes available for reading from the current position.
136      *
137      * @return the number of bytes that can be read from this buffer from the current position.
138      */
139     public final synchronized int getRemaining() {
140         if (pos >= end) {
141             return 0;
142         }
143         return end - pos;
144     }
145 
146     @Override
147     public final synchronized long position() {
148         return pos;
149     }
150 
151     @Override
152     public synchronized void position(long offset) throws IOException {
153         if (offset < 0) {
154             throw new EOFException("Negative buffer index: " + offset);
155         }
156 
157         if (offset > buf.length) {
158             if (!isGrowable) {
159                 throw new EOFException("Position " + offset + " beyond fixed buffer size " + buf.length);
160             }
161         }
162 
163         pos = (int) offset;
164     }
165 
166     /**
167      * Changes the length of this buffer. The total number of bytes available from reading from this buffer will be that
168      * of the new length. If the buffer is truncated and its pointer is positioned beyond the new size, then the pointer
169      * is changed to point to the new buffer end. If the buffer is enlarged beyond it current capacity, its capacity
170      * will grow as necessary provided the buffer is growable. Otherwise, an EOFException is thrown if the new length is
171      * beyond the fixed buffer capacity. If the new length is larger than the old one, the added buffer segment may have
172      * undefined contents.
173      *
174      * @param  length                   The buffer length, that is number of bytes available for reading.
175      *
176      * @throws IllegalArgumentException if the length is negative or if the new new length exceeds the capacity of a
177      *                                      fixed-type buffer.
178      *
179      * @see                             #length()
180      * @see                             #capacity()
181      */
182     public synchronized void setLength(int length) throws IllegalArgumentException {
183         if (length < 0) {
184             throw new IllegalArgumentException("Buffer set to negative length: " + length);
185         }
186 
187         if (length > capacity()) {
188             if (!isGrowable) {
189                 throw new IllegalArgumentException(
190                         "the new length " + length + " is larger than the fixed capacity " + capacity());
191             }
192             grow(length - capacity());
193         }
194         end = length;
195 
196         // If the pointer is beyond the new size, move it back to the new end...
197         if (pos > end) {
198             pos = end;
199         }
200     }
201 
202     /**
203      * Grows the buffer by at least the number of specified bytes. A new buffer is allocated, and the contents of the
204      * previous buffer are copied over.
205      *
206      * @param need the minimum number of extra bytes needed beyond the current capacity.
207      */
208     private synchronized void grow(int need) {
209         long size = capacity() + need;
210         long below = Long.highestOneBit(size);
211         if (below != size) {
212             size = below << 1;
213         }
214         byte[] newbuf = new byte[(int) Math.min(size, Integer.MAX_VALUE)];
215         System.arraycopy(buf, 0, newbuf, 0, buf.length);
216         buf = newbuf;
217     }
218 
219     @Override
220     public final synchronized void write(int b) throws IOException {
221         if (pos + 1 > buf.length) {
222             if (!isGrowable) {
223                 throw new EOFException("buffer is full (size=" + length() + ")");
224             }
225             grow(pos + 1 - buf.length);
226         }
227         buf[pos++] = (byte) b;
228         if (pos > end) {
229             end = pos;
230         }
231     }
232 
233     @Override
234     public final synchronized void write(byte[] b, int from, int length) throws IOException {
235         if (length <= 0) {
236             return;
237         }
238 
239         if (pos > buf.length || (isGrowable && pos + length > buf.length)) {
240             // This may only happen in a growable buffer...
241             grow(buf.length + length - pos);
242         }
243 
244         int l = Math.min(length, buf.length - pos);
245 
246         System.arraycopy(b, from, buf, pos, l);
247         pos += l;
248         if (pos > end) {
249             end = pos;
250         }
251 
252         if (l < length) {
253             throw new EOFException("Incomplete write of " + l + " of " + length + " bytes in buffer of size " + length());
254         }
255     }
256 
257     @Override
258     public final synchronized int read() throws IOException {
259         if (getRemaining() <= 0) {
260             return -1;
261         }
262         return buf[pos++] & BYTE_MASK;
263     }
264 
265     @Override
266     public final synchronized int read(byte[] b, int from, int length) {
267         if (length <= 0) {
268             return 0;
269         }
270 
271         int remaining = getRemaining();
272 
273         if (remaining <= 0) {
274             return -1;
275         }
276 
277         int n = Math.min(remaining, length);
278         System.arraycopy(buf, pos, b, from, n);
279         pos += n;
280 
281         return n;
282     }
283 }