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