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 }