View Javadoc
1   package nom.tam.image.compression.bintable;
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.IOException;
35  import java.nio.Buffer;
36  import java.nio.ByteBuffer;
37  import java.util.Arrays;
38  
39  import nom.tam.fits.BinaryTable;
40  import nom.tam.fits.compression.algorithm.api.ICompressorControl;
41  import nom.tam.image.compression.hdu.CompressedTableData;
42  import nom.tam.util.ArrayOutputStream;
43  import nom.tam.util.ByteBufferOutputStream;
44  import nom.tam.util.ColumnTable;
45  import nom.tam.util.FitsOutputStream;
46  import nom.tam.util.type.ElementType;
47  
48  /**
49   * (<i>for internal use</i>) Handles the compression of binary table 'tiles'.
50   */
51  @SuppressWarnings("javadoc")
52  public class BinaryTableTileCompressor extends BinaryTableTile {
53  
54      private static final double NORMAL_OVERHEAD = 1.2;
55  
56      private static final int MINIMUM_EXTRA_SPACE = 1024;
57  
58      private final CompressedTableData compressed;
59  
60      /** The original (uncompressed) binary table, if known (otherwise we cannot handle variable-sized columns) */
61      private BinaryTable orig;
62  
63      // Intermediate data stored between parallel compression and serialization steps.
64      private byte[][] compressedBytes;
65      private long[][] cdesc;
66      private Object udesc;
67  
68      /**
69       * @deprecated (<i>for internal use</i>) Its visibility will be reduced in the future, not to mention that it should
70       *                 take a BinaryTable as its argument with heap and all. It cannot be used for compressing binary
71       *                 tables with variable-length columns.
72       */
73      @Deprecated
74      public BinaryTableTileCompressor(CompressedTableData compressedTable, ColumnTable<?> columnTable,
75              BinaryTableTileDescription description) {
76          super(columnTable, description);
77          this.compressed = compressedTable;
78      }
79  
80      /**
81       * (<i>for internal use</i>)
82       * 
83       * @param compressedTable a compressed table in which we'll insert the data for the compressed tile
84       * @param table           the original uncompressed binary table
85       * @param description     the tile description.
86       */
87      public BinaryTableTileCompressor(CompressedTableData compressedTable, BinaryTable table,
88              BinaryTableTileDescription description) {
89          this(compressedTable, table.getData(), description);
90          this.orig = table;
91      }
92  
93      private int getCushion(int size, double factor) throws IllegalStateException {
94          long lsize = (long) Math.ceil(size * factor + MINIMUM_EXTRA_SPACE);
95          return (lsize > Integer.MAX_VALUE) ? Integer.MAX_VALUE : (int) lsize;
96      }
97  
98      private byte[] getCompressedBytes(ByteBuffer buffer, ElementType<?> t, ICompressorControl compressor) {
99          buffer.flip();
100 
101         // give the compression 10% more space and a minimum of 1024 bytes
102         int need = getCushion(getUncompressedSizeInBytes(), NORMAL_OVERHEAD);
103         ByteBuffer cbuf = ByteBuffer.allocateDirect(need);
104 
105         Buffer tb = t.asTypedBuffer(buffer);
106 
107         if (!compressor.compress(tb, cbuf, null)) {
108             throw new IllegalStateException("Compression error");
109         }
110 
111         cbuf.flip();
112         byte[] cdata = new byte[cbuf.limit()];
113         cbuf.get(cdata);
114 
115         buffer.clear();
116 
117         return cdata;
118     }
119 
120     private void compressRegular() throws IOException {
121         compressedBytes = new byte[1][];
122 
123         ByteBuffer buffer = ByteBuffer.allocateDirect(getUncompressedSizeInBytes());
124         try (FitsOutputStream os = new FitsOutputStream(new ByteBufferOutputStream(buffer))) {
125             data.write(os, rowStart, rowEnd, column);
126         }
127 
128         compressedBytes[0] = getCompressedBytes(buffer, type, getCompressorControl());
129     }
130 
131     private void compressVariable() throws IOException {
132         int nRows = rowEnd - rowStart;
133         boolean longPointers = orig.getDescriptor(column).hasLongPointers();
134         long max = 0;
135 
136         udesc = longPointers ? new long[nRows][] : new int[nRows][2]; // Original Q or P type heap pointers
137 
138         // Find out what's the largest variable-sized entry and store the original pointers for the tile
139         for (int r = 0; r < nRows; r++) {
140             Object desc = data.getElement(rowStart + r, column);
141             long n = 0;
142 
143             if (longPointers) {
144                 ((long[][]) udesc)[r] = (long[]) desc;
145                 n = ((long[]) desc)[0];
146             } else {
147                 ((int[][]) udesc)[r] = (int[]) desc;
148                 n = ((int[]) desc)[0];
149             }
150 
151             if (n > max) {
152                 max = n;
153             }
154         }
155 
156         max *= type.size();
157 
158         // Uh-oh, we can only handle 32-bit address space...
159         if (max > Integer.MAX_VALUE) {
160             throw new IllegalStateException("Uncompressed data too large for Java arrays: max=" + max);
161         }
162 
163         // Buffer for the original data chunks to compress
164         ByteBuffer buffer = ByteBuffer.allocateDirect((int) max);
165         ElementType<?> dataType = ElementType.forClass(orig.getDescriptor(column).getElementClass());
166 
167         ICompressorControl compressor = getCompressorControl(dataType.primitiveClass());
168         compressedBytes = new byte[nRows][];
169 
170         for (int r = 0; r < nRows; r++) {
171             try (FitsOutputStream os = new FitsOutputStream(new ByteBufferOutputStream(buffer))) {
172                 // Get the VLA data from the heap
173                 Object entry = orig.get(rowStart + r, column);
174                 os.writeArray(entry);
175             }
176 
177             compressedBytes[r] = getCompressedBytes(buffer, dataType, compressor);
178         }
179 
180     }
181 
182     @Override
183     public void run() {
184         try {
185             if (orig != null && orig.getDescriptor(column).isVariableSize()) {
186                 // binary table with variable sized column
187                 compressVariable();
188             } else {
189                 // regular column table with fixed width columns
190                 compressRegular();
191             }
192         } catch (IOException e) {
193             throw new IllegalStateException(e.getMessage(), e);
194         }
195     }
196 
197     private Object setCompressedData(byte[] data) {
198         synchronized (compressed) {
199             // Reset the stored heap pointers so we force a new location on the heap.
200             Object p = compressed.getData().getElement(getTileIndex(), column);
201             if (p instanceof long[]) {
202                 Arrays.fill((long[]) p, 0L);
203             } else {
204                 Arrays.fill((int[]) p, 0);
205             }
206             compressed.getData().setElement(getTileIndex(), column, p);
207 
208             // Now set the variable size data, which we'll place on a new heap location.
209             compressed.setElement(getTileIndex(), column, data);
210 
211             // Retrieve the heap pointers for the compressed data
212             // We only really handle 32-bit heap descriptors...
213             return compressed.getData().getElement(getTileIndex(), column);
214         }
215 
216     }
217 
218     private void setRegularData() {
219         setCompressedData(compressedBytes[0]);
220 
221         // Discard temporary resources.
222         compressedBytes = null;
223     }
224 
225     private void setVariableData() throws IOException {
226         int nRows = compressedBytes.length;
227         boolean longPointers = orig.getDescriptor(column).hasLongPointers();
228 
229         cdesc = new long[nRows][2]; // Compressed Q-type heap pointers
230 
231         ByteBuffer buffer = ByteBuffer
232                 .allocateDirect((nRows * 2) * (Long.SIZE + (longPointers ? Long.BYTES : Integer.BYTES)));
233 
234         for (int r = 0; r < nRows; r++) {
235             // Now set the variable size data, which we'll place on a new heap location.
236             Object cp = setCompressedData(compressedBytes[r]);
237 
238             if (cp instanceof long[]) {
239                 cdesc[r] = (long[]) cp;
240             } else {
241                 // Convert to 64-bit for saving / compressing pointers later
242                 cdesc[r][0] = ((int[]) cp)[0];
243                 cdesc[r][1] = ((int[]) cp)[1];
244             }
245         }
246 
247         try (ArrayOutputStream os = new FitsOutputStream(new ByteBufferOutputStream(buffer))) {
248             // --- The fpack / funpack way ---
249             // Serialize the original heap descritors
250             os.writeArray(udesc);
251             // Append the compressed heap descriptors
252             os.writeArray(cdesc);
253         }
254 
255         // Compress the combined descriptors with GZIP_1 -- and we'll store the pointers to that in the
256         // compressed table
257         setCompressedData(getCompressedBytes(buffer, ElementType.BYTE, getGZipCompressorControl()));
258 
259         // Discard temporary resources.
260         compressedBytes = null;
261         cdesc = null;
262         udesc = null;
263     }
264 
265     @Override
266     public void waitForResult() {
267         super.waitForResult();
268 
269         if (orig != null && orig.getDescriptor(column).isVariableSize()) {
270             try {
271                 setVariableData();
272             } catch (IOException e) {
273                 throw new IllegalStateException(e.getMessage(), e);
274             }
275         } else {
276             setRegularData();
277         }
278 
279     }
280 
281 }