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