View Javadoc
1   package nom.tam.image.compression.bintable;
2   
3   /*-
4    * #%L
5    * nom.tam.fits
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  
38  import nom.tam.fits.BinaryTable;
39  import nom.tam.fits.FitsException;
40  import nom.tam.fits.compression.algorithm.api.ICompressorControl;
41  import nom.tam.image.compression.hdu.CompressedTableData;
42  import nom.tam.image.compression.hdu.CompressedTableHDU;
43  import nom.tam.util.ByteBufferInputStream;
44  import nom.tam.util.ColumnTable;
45  import nom.tam.util.FitsInputStream;
46  import nom.tam.util.type.ElementType;
47  
48  /**
49   * (<i>for internal use</i>) Handles the decompression of binary table 'tiles'.
50   */
51  @SuppressWarnings("javadoc")
52  public class BinaryTableTileDecompressor extends BinaryTableTile {
53  
54      private BinaryTable orig;
55  
56      private final CompressedTableData compressed;
57  
58      private int targetColumn = column;
59  
60      /**
61       * @deprecated (<i>for internal use</i>) The visibility will be reduced in the future, not to mention that it should
62       *                 take a binary table as its argument, with heap and all. It cannot be used for decompressing
63       *                 binary tables with variable-length columns.
64       */
65      public BinaryTableTileDecompressor(CompressedTableData compressedTable, ColumnTable<?> columnTable,
66              BinaryTableTileDescription description) throws FitsException {
67          super(columnTable, description);
68          compressed = compressedTable;
69      }
70  
71      public BinaryTableTileDecompressor(CompressedTableData compressedTable, BinaryTable table,
72              BinaryTableTileDescription description) throws FitsException {
73          this(compressedTable, table.getData(), description);
74          orig = table;
75      }
76  
77      private void decompressVariable() throws IOException {
78          int nRows = rowEnd - rowStart;
79          boolean longPointers = orig.getDescriptor(targetColumn).hasLongPointers();
80  
81          // Uncompress the adjoint heap pointer data stored in the compressed table using GZIP_1
82          ByteBuffer pdata = ByteBuffer.wrap((byte[]) compressed.getElement(getTileIndex(), column));
83          ByteBuffer pointers = ByteBuffer
84                  .allocateDirect((2 * nRows) * (Long.BYTES + (longPointers ? Long.BYTES : Integer.BYTES)));
85  
86          getGZipCompressorControl().decompress(pdata, pointers, null);
87          pointers.flip();
88  
89          long[][] cdesc = new long[nRows][2];
90          Object p = longPointers ? new long[nRows][2] : new int[nRows][2];
91  
92          try (FitsInputStream ips = new FitsInputStream(new ByteBufferInputStream(pointers))) {
93              if (CompressedTableHDU.hasOldStandardVLAIndexing()) {
94                  // --- The FITS standard way ---
95                  // Restore the heap pointers to the compressed data in the compressed heap
96                  ips.readLArray(cdesc);
97                  // Restore the heap pointers for the original uncompressed data locations
98                  ips.readLArray(p);
99              } else {
100                 // --- The fpack / funpack way ---
101                 // Restore the heap pointers for the original uncompressed data locations
102                 ips.readLArray(p);
103                 // Restore the heap pointers to the compressed data in the compressed heap
104                 ips.readLArray(cdesc);
105             }
106         }
107 
108         ElementType<?> dataType = ElementType.forClass(orig.getDescriptor(column).getElementClass());
109 
110         ICompressorControl compressor = getCompressorControl(dataType.primitiveClass());
111 
112         // Save the original pointers for the compressed tile
113         final Object bak = compressed.getData().getElement(getTileIndex(), column);
114 
115         try {
116             for (int r = 0; r < nRows; r++) {
117                 long csize = cdesc[r][0];
118                 long coffset = cdesc[r][1];
119 
120                 if (csize < 0 || csize > Integer.MAX_VALUE || coffset < 0 || coffset > Integer.MAX_VALUE) {
121                     throw new FitsException(
122                             "Illegal or unsupported compressed heap pointer (offset=" + coffset + ", size=" + csize);
123                 }
124 
125                 long dcount = longPointers ? ((long[][]) p)[r][0] : ((int[][]) p)[r][0];
126                 long doffset = longPointers ? ((long[][]) p)[r][1] : ((int[][]) p)[r][1];
127 
128                 if (dcount < 0 || dcount > Integer.MAX_VALUE || doffset < 0 || doffset > Integer.MAX_VALUE) {
129                     throw new FitsException(
130                             "Illegal or unsupported uncompressed heap pointer (offset=" + doffset + ", size=" + dcount);
131                 }
132 
133                 // Temporarily replace the heap pointers in the compressed table with the pointers to the compressed row
134                 // entry
135                 Object temp = bak instanceof long[] ? new long[] {csize, coffset} : new int[] {(int) csize, (int) coffset};
136                 compressed.getData().setElement(getTileIndex(), column, temp);
137 
138                 // Decompress the row entry, and write it to its original location on the heap
139                 ByteBuffer zip = ByteBuffer.wrap((byte[]) compressed.getElement(getTileIndex(), column));
140                 Buffer buf = dataType.newBuffer(dcount);
141                 compressor.decompress(zip, buf, null);
142                 buf.flip();
143 
144                 // Restore the heap pointer in the uncompressed table
145                 data.setElement(rowStart + r, targetColumn, longPointers ? ((long[][]) p)[r] : ((int[][]) p)[r]);
146 
147                 // Restore the uncompressed entry in the original heap location
148                 orig.setElement(rowStart + r, targetColumn, buf.array());
149             }
150         } finally {
151             // Restore the original pointers for the compressed tile.
152             compressed.getData().setElement(getTileIndex(), column, bak);
153         }
154     }
155 
156     private void decompressTableTile() throws IOException {
157         ByteBuffer zip = ByteBuffer.wrap((byte[]) compressed.getElement(getTileIndex(), column));
158         ByteBuffer buf = ByteBuffer.allocateDirect(getUncompressedSizeInBytes());
159 
160         getCompressorControl().decompress(zip, type.asTypedBuffer(buf), null);
161         buf.rewind();
162 
163         try (FitsInputStream is = new FitsInputStream(new ByteBufferInputStream(buf))) {
164             data.read(is, rowStart, rowEnd, targetColumn);
165         }
166     }
167 
168     @Override
169     public void run() {
170         try {
171             if (orig != null && orig.getDescriptor(targetColumn).isVariableSize()) {
172                 // binary table with variable sized column
173                 decompressVariable();
174             } else {
175                 // regular column table (fixed width columns)
176                 decompressTableTile();
177             }
178         } catch (IOException e) {
179             throw new IllegalStateException(e.getMessage(), e);
180         }
181     }
182 
183     /**
184      * Changes the comlumn index of into which the tile gets decompressed in the uncompressed table. By default the
185      * decompressed column index will match the compressed data column index, which is great if we decompress the all
186      * columns. However, we might decompress only selected table columns into a different table in which the column
187      * indices are different.
188      * 
189      * @param  col the decompressed column index for the tile
190      * 
191      * @return     itself.
192      * 
193      * @since      1.18
194      */
195     public BinaryTableTileDecompressor decompressToColumn(int col) {
196         targetColumn = col;
197         return this;
198     }
199 
200 }