View Javadoc
1   package nom.tam.fits;
2   
3   /*
4    * #%L
5    * nom.tam FITS library
6    * %%
7    * Copyright (C) 2004 - 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.IOException;
35  
36  import nom.tam.util.ArrayDataInput;
37  import nom.tam.util.ArrayDataOutput;
38  import nom.tam.util.ByteArrayIO;
39  import nom.tam.util.FitsDecoder;
40  import nom.tam.util.FitsEncoder;
41  
42  /**
43   * Heap for storing variable-length entries in binary tables. FITS binary tables store variable length arrays on a heap,
44   * following the regular array data. The newer implementation of the heap now provides proper random access to the byte
45   * buffer as of version 1.16.
46   */
47  public class FitsHeap implements FitsElement {
48  
49      /** The minimum stoprage size to allocate for the heap, from which it can grow as necessary */
50      private static final int MIN_HEAP_CAPACITY = 16384;
51  
52      // TODO
53      // AK: In principle we could use ReadWriteAccess interface as the storage, which can be either an in-memory
54      // array or a buffered file region. The latter could support heaps over 2G, and could reduce memory overhead
55      // for heap access in some future release...
56      /** The underlying storage space of the heap */
57      private ByteArrayIO store;
58  
59      /** conversion from Java arrays to FITS binary representation */
60      private FitsEncoder encoder;
61  
62      /** conversion from FITS binary representation to Java arrays */
63      private FitsDecoder decoder;
64  
65      /** For thread synchronization */
66      private Object lock = new Object();
67  
68      /**
69       * Construct a new uninitialized FITS heap object.
70       */
71      private FitsHeap() {
72      }
73  
74      /**
75       * Creates a heap of a given initial size. The new heap is initialized with 0's, and up to the specified number of
76       * bytes are immediately available for reading (as zeroes). The heap can grow as needed if more data is written into
77       * it.
78       *
79       * @throws IllegalArgumentException if the size argument is negative.
80       */
81      FitsHeap(int size) throws IllegalArgumentException {
82          if (size < 0) {
83              throw new IllegalArgumentException("Illegal size for FITS heap: " + size);
84          }
85  
86          ByteArrayIO data = new ByteArrayIO(Math.max(size, MIN_HEAP_CAPACITY));
87          data.setLength(Math.max(0, size));
88          setData(data);
89          encoder = new FitsEncoder(store);
90          decoder = new FitsDecoder(store);
91      }
92  
93      /**
94       * Sets the underlying data storage for this heap instance. Constructors should call this.
95       *
96       * @param data the new underlying storage object for this heap instance.
97       */
98      protected void setData(ByteArrayIO data) {
99          synchronized (lock) {
100             store = data;
101         }
102     }
103 
104     /**
105      * Add a copy constructor to allow us to duplicate a heap. This would be necessary if we wanted to copy an HDU that
106      * included variable length columns.
107      */
108     FitsHeap copy() {
109         FitsHeap copy = new FitsHeap();
110         synchronized (lock) {
111             copy.setData(store.copy());
112             copy.encoder = new FitsEncoder(copy.store);
113             copy.decoder = new FitsDecoder(copy.store);
114         }
115         return copy;
116     }
117 
118     /**
119      * Gets data for a Java array from the heap. The array may be a multi-dimensional array of arrays.
120      *
121      * @param  offset        the heap byte offset at which the data begins.
122      * @param  array         The array of primitives to be extracted.
123      *
124      * @throws FitsException if the operation failed
125      */
126     public void getData(int offset, Object array) throws FitsException {
127         synchronized (lock) {
128             try {
129                 store.position(offset);
130                 decoder.readArrayFully(array);
131             } catch (Exception e) {
132                 throw new FitsException("Error decoding heap area at offset=" + offset + ", size="
133                         + FitsEncoder.computeSize(array) + " (heap size " + size() + "): " + e.getMessage(), e);
134             }
135         }
136     }
137 
138     @Override
139     public long getFileOffset() {
140         throw new IllegalStateException("FitsHeap should only be reset from inside its parent, never alone");
141     }
142 
143     @Override
144     public long getSize() {
145         synchronized (lock) {
146             return size();
147         }
148     }
149 
150     /**
151      * Puts data to the end of the heap.
152      * 
153      * @param  data a primitive array object, which may be multidimensional
154      * 
155      * @return      the number of bytes used by the data.
156      * 
157      * @see         #putData(Object, long)
158      * @see         #getData(int, Object)
159      */
160     long putData(Object data) throws FitsException {
161         synchronized (lock) {
162             return putData(data, store.length());
163         }
164     }
165 
166     /**
167      * Puts data onto the heap at a specific heap position.
168      * 
169      * @param  data a primitive array object, which may be multidimensional
170      * @param  pos  the byte offset at which the data should begin.
171      * 
172      * @return      the number of bytes used by the data.
173      * 
174      * @see         #putData(Object, long)
175      * @see         #getData(int, Object)
176      */
177     long putData(Object data, long pos) throws FitsException {
178         synchronized (lock) {
179             long lsize = pos + FitsEncoder.computeSize(data);
180             if (lsize > Integer.MAX_VALUE) {
181                 throw new FitsException("FITS Heap > 2 G");
182             }
183 
184             try {
185                 store.position(pos);
186                 encoder.writeArray(data);
187             } catch (Exception e) {
188                 throw new FitsException("Unable to write variable column length data: " + e.getMessage(), e);
189             }
190 
191             return store.position() - pos;
192         }
193     }
194 
195     /**
196      * Copies a segment of data from another heap to the end of this heap
197      * 
198      * @param  src    the heap to source data from
199      * @param  offset the byte offset of the data in the source heap
200      * @param  len    the number of bytes to copy
201      * 
202      * @return        the position of the copied data in this heap.
203      */
204     int copyFrom(FitsHeap src, int offset, int len) {
205         synchronized (lock) {
206             int pos = (int) store.length();
207             store.setLength(pos + len);
208             synchronized (src) {
209                 System.arraycopy(src.store.getBuffer(), offset, store.getBuffer(), pos, len);
210             }
211             return pos;
212         }
213     }
214 
215     @Override
216     public void read(ArrayDataInput str) throws FitsException {
217         synchronized (lock) {
218             if (store.length() == 0) {
219                 return;
220             }
221 
222             try {
223                 str.readFully(store.getBuffer(), 0, (int) store.length());
224             } catch (IOException e) {
225                 throw new FitsException("Error reading heap " + e.getMessage(), e);
226             }
227         }
228     }
229 
230     @Override
231     public boolean reset() {
232         throw new IllegalStateException("FitsHeap should only be reset from inside its parent, never alone");
233     }
234 
235     @Override
236     public void rewrite() throws IOException, FitsException {
237         throw new FitsException("FitsHeap should only be rewritten from inside its parent, never alone");
238     }
239 
240     @Override
241     public boolean rewriteable() {
242         return false;
243     }
244 
245     /**
246      * Returns the current heap size.
247      *
248      * @return the size of the heap in bytes
249      */
250     public int size() {
251         synchronized (lock) {
252             return (int) store.length();
253         }
254     }
255 
256     @Override
257     public void write(ArrayDataOutput str) throws FitsException {
258         try {
259             synchronized (lock) {
260                 str.write(store.getBuffer(), 0, (int) store.length());
261             }
262         } catch (IOException e) {
263             throw new FitsException("Error writing heap:" + e.getMessage(), e);
264         }
265     }
266 
267 }