View Javadoc
1   package nom.tam.fits.compression.provider;
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.lang.reflect.Constructor;
35  import java.lang.reflect.InvocationTargetException;
36  import java.nio.Buffer;
37  import java.nio.ByteBuffer;
38  import java.util.ServiceLoader;
39  import java.util.logging.Level;
40  import java.util.logging.Logger;
41  
42  import nom.tam.fits.FitsException;
43  import nom.tam.fits.compression.algorithm.api.ICompressOption;
44  import nom.tam.fits.compression.algorithm.api.ICompressor;
45  import nom.tam.fits.compression.algorithm.api.ICompressorControl;
46  import nom.tam.fits.compression.algorithm.gzip.GZipCompressor.ByteGZipCompressor;
47  import nom.tam.fits.compression.algorithm.gzip.GZipCompressor.DoubleGZipCompressor;
48  import nom.tam.fits.compression.algorithm.gzip.GZipCompressor.FloatGZipCompressor;
49  import nom.tam.fits.compression.algorithm.gzip.GZipCompressor.IntGZipCompressor;
50  import nom.tam.fits.compression.algorithm.gzip.GZipCompressor.LongGZipCompressor;
51  import nom.tam.fits.compression.algorithm.gzip.GZipCompressor.ShortGZipCompressor;
52  import nom.tam.fits.compression.algorithm.gzip2.GZip2Compressor.ByteGZip2Compressor;
53  import nom.tam.fits.compression.algorithm.gzip2.GZip2Compressor.DoubleGZip2Compressor;
54  import nom.tam.fits.compression.algorithm.gzip2.GZip2Compressor.FloatGZip2Compressor;
55  import nom.tam.fits.compression.algorithm.gzip2.GZip2Compressor.IntGZip2Compressor;
56  import nom.tam.fits.compression.algorithm.gzip2.GZip2Compressor.LongGZip2Compressor;
57  import nom.tam.fits.compression.algorithm.gzip2.GZip2Compressor.ShortGZip2Compressor;
58  import nom.tam.fits.compression.algorithm.hcompress.HCompressor.ByteHCompressor;
59  import nom.tam.fits.compression.algorithm.hcompress.HCompressor.DoubleHCompressor;
60  import nom.tam.fits.compression.algorithm.hcompress.HCompressor.FloatHCompressor;
61  import nom.tam.fits.compression.algorithm.hcompress.HCompressor.IntHCompressor;
62  import nom.tam.fits.compression.algorithm.hcompress.HCompressor.ShortHCompressor;
63  import nom.tam.fits.compression.algorithm.hcompress.HCompressorQuantizeOption;
64  import nom.tam.fits.compression.algorithm.plio.PLIOCompress.BytePLIOCompressor;
65  import nom.tam.fits.compression.algorithm.plio.PLIOCompress.IntPLIOCompressor;
66  import nom.tam.fits.compression.algorithm.plio.PLIOCompress.ShortPLIOCompressor;
67  import nom.tam.fits.compression.algorithm.quant.QuantizeOption;
68  import nom.tam.fits.compression.algorithm.quant.QuantizeProcessor.DoubleQuantCompressor;
69  import nom.tam.fits.compression.algorithm.quant.QuantizeProcessor.FloatQuantCompressor;
70  import nom.tam.fits.compression.algorithm.rice.RiceCompressor.ByteRiceCompressor;
71  import nom.tam.fits.compression.algorithm.rice.RiceCompressor.DoubleRiceCompressor;
72  import nom.tam.fits.compression.algorithm.rice.RiceCompressor.FloatRiceCompressor;
73  import nom.tam.fits.compression.algorithm.rice.RiceCompressor.IntRiceCompressor;
74  import nom.tam.fits.compression.algorithm.rice.RiceCompressor.ShortRiceCompressor;
75  import nom.tam.fits.compression.algorithm.rice.RiceQuantizeCompressOption;
76  import nom.tam.fits.compression.algorithm.uncompressed.NoCompressCompressor.ByteNoCompressCompressor;
77  import nom.tam.fits.compression.algorithm.uncompressed.NoCompressCompressor.DoubleNoCompressCompressor;
78  import nom.tam.fits.compression.algorithm.uncompressed.NoCompressCompressor.FloatNoCompressCompressor;
79  import nom.tam.fits.compression.algorithm.uncompressed.NoCompressCompressor.IntNoCompressCompressor;
80  import nom.tam.fits.compression.algorithm.uncompressed.NoCompressCompressor.LongNoCompressCompressor;
81  import nom.tam.fits.compression.algorithm.uncompressed.NoCompressCompressor.ShortNoCompressCompressor;
82  import nom.tam.fits.compression.provider.api.ICompressorProvider;
83  import nom.tam.fits.compression.provider.param.api.ICompressHeaderParameter;
84  import nom.tam.fits.compression.provider.param.api.ICompressParameters;
85  import nom.tam.fits.compression.provider.param.base.CompressParameters;
86  import nom.tam.fits.compression.provider.param.hcompress.HCompressParameters;
87  import nom.tam.fits.compression.provider.param.rice.RiceCompressParameters;
88  
89  /**
90   * (<i>for internal use</i>) Standard implementation of the {@code ICompressorProvider} interface.
91   */
92  @SuppressWarnings({"javadoc", "deprecation"})
93  public class CompressorProvider implements ICompressorProvider {
94  
95      /**
96       * private implementation of the tile compression provider, all is based on the option based constructor of the
97       * compressors.
98       */
99      protected static class TileCompressorControl implements ICompressorControl {
100 
101         private final Constructor<ICompressor<Buffer>>[] constructors;
102 
103         private Class<? extends ICompressOption> optionClass;
104 
105         private Class<?> quantType;
106 
107         @SuppressWarnings("unchecked")
108         protected TileCompressorControl(Class<?> compressorClass) {
109             constructors = (Constructor<ICompressor<Buffer>>[]) compressorClass.getConstructors();
110             for (Constructor<ICompressor<Buffer>> c : constructors) {
111                 if (c.getParameterTypes().length == 1) {
112                     optionClass = (Class<? extends ICompressOption>) c.getParameterTypes()[0];
113                     break;
114                 }
115             }
116         }
117 
118         /**
119          * Sets the floating-point type to quantize to use for this tile compressor.
120          *
121          * @param  floatingPointType Floating-point primitive type to quantize. Must be either <code>double.class</code>
122          *                               or else <code>float.class</code>.
123          *
124          * @return                   itself
125          *
126          * @since                    1.18
127          */
128         protected TileCompressorControl setQuantType(Class<?> floatingPointType) {
129             quantType = floatingPointType;
130             return this;
131         }
132 
133         @Override
134         public boolean compress(Buffer in, ByteBuffer out, ICompressOption option) {
135             try {
136                 return newCompressor(option).compress(in, out);
137             } catch (Exception e) {
138                 LOG.log(Level.FINE, "could not compress using " + constructors[0].getName()
139                         + " must fallback to other compression method", e);
140                 return false;
141             }
142         }
143 
144         @Override
145         public void decompress(ByteBuffer in, Buffer out, ICompressOption option) {
146             try {
147                 newCompressor(option).decompress(in, out);
148             } catch (Exception e) {
149                 throw new IllegalStateException("could not decompress " + constructors[0].getName(), e);
150             }
151         }
152 
153         @Override
154         public ICompressOption option() {
155             ICompressOption option = null;
156             if (optionClass != null) {
157                 try {
158                     option = optionClass.getDeclaredConstructor().newInstance();
159                 } catch (Exception e) {
160                     throw new IllegalStateException("could not instantiate option class for " + constructors[0].getName(),
161                             e);
162                 }
163             }
164 
165             if (option == null) {
166                 option = NULL_OPTION;
167             }
168 
169             if (quantType != null) {
170                 return new QuantizeOption(option);
171             }
172 
173             return option;
174         }
175 
176         @SuppressWarnings({"unchecked", "rawtypes"})
177         private ICompressor<Buffer> newCompressor(ICompressOption option)
178                 throws FitsException, InstantiationException, IllegalAccessException, InvocationTargetException {
179             ICompressor<Buffer> compressor = null;
180             QuantizeOption quantOption = null;
181 
182             if (option instanceof QuantizeOption) {
183                 quantOption = (QuantizeOption) option;
184                 option = quantOption.getCompressOption();
185             }
186 
187             if (option == NULL_OPTION) {
188                 option = null;
189             }
190 
191             for (Constructor<ICompressor<Buffer>> c : constructors) {
192                 Class<?>[] parms = c.getParameterTypes();
193 
194                 if (parms.length == 0 && option == null) {
195                     // Use constructor without special options...
196                     compressor = c.newInstance();
197                     break;
198                 }
199 
200                 if (parms.length == 1 && option != null) {
201                     // Use constructor with the option
202                     Class<? extends ICompressOption> p = (Class<? extends ICompressOption>) parms[0];
203                     if (quantOption != null && p.isAssignableFrom(quantOption.getClass())) {
204                         compressor = c.newInstance(quantOption);
205                         quantOption = null; // Don't wrap in a quantizer below...
206                         break;
207                     }
208                     if (p.isAssignableFrom(option.getClass())) {
209                         compressor = c.newInstance(option);
210                         break;
211                     }
212                 }
213             }
214 
215             if (compressor == null) {
216                 throw new FitsException("Could not instantiate (de)compressor for the specified options");
217             }
218 
219             if (quantOption != null && quantType != null) {
220                 if (quantType.equals(double.class)) {
221                     return (ICompressor) new DoubleQuantCompressor(quantOption, (ICompressor) compressor);
222                 }
223                 if (quantType.equals(float.class)) {
224                     return (ICompressor) new FloatQuantCompressor(quantOption, (ICompressor) compressor);
225                 }
226             }
227 
228             return compressor;
229         }
230     }
231 
232     private static final ICompressOption NULL_OPTION = new ICompressOption() {
233 
234         @Override
235         public ICompressOption copy() {
236             return this;
237         }
238 
239         @Override
240         public ICompressParameters getCompressionParameters() {
241             return NULL_PARAMETERS;
242         }
243 
244         @Override
245         public boolean isLossyCompression() {
246             return false;
247         }
248 
249         @Override
250         public void setParameters(ICompressParameters parameters) {
251         }
252 
253         @Override
254         public ICompressOption setTileHeight(int value) {
255             return this;
256         }
257 
258         @Override
259         public ICompressOption setTileWidth(int value) {
260             return this;
261         }
262 
263         @Override
264         public <T> T unwrap(Class<T> clazz) {
265             return clazz.isAssignableFrom(this.getClass()) ? clazz.cast(this) : null;
266         }
267     };
268 
269     private static final ICompressParameters NULL_PARAMETERS = new CompressParameters() {
270 
271         @Override
272         protected ICompressHeaderParameter[] headerParameters() {
273             return new ICompressHeaderParameter[0];
274         }
275 
276         @Override
277         public ICompressParameters copy(ICompressOption option) {
278             return this;
279         }
280     };
281 
282     // @formatter:off
283     private static final Class<?>[][] AVAILABLE_COMPRESSORS = {//
284             {ByteRiceCompressor.class, RiceCompressParameters.class}, //
285             {ShortRiceCompressor.class, RiceCompressParameters.class}, //
286             {IntRiceCompressor.class, RiceCompressParameters.class}, //
287             {FloatRiceCompressor.class, RiceQuantizeCompressOption.class}, //
288             {DoubleRiceCompressor.class, RiceQuantizeCompressOption.class}, //
289             {BytePLIOCompressor.class}, //
290             {ShortPLIOCompressor.class}, //
291             {IntPLIOCompressor.class}, //
292             {ByteHCompressor.class, HCompressParameters.class}, //
293             {ShortHCompressor.class, HCompressParameters.class}, //
294             {IntHCompressor.class, HCompressParameters.class}, //
295             {FloatHCompressor.class, HCompressorQuantizeOption.class}, //
296             {DoubleHCompressor.class, HCompressorQuantizeOption.class}, //
297             {ByteGZip2Compressor.class}, //
298             {ShortGZip2Compressor.class}, //
299             {IntGZip2Compressor.class}, //
300             {FloatGZip2Compressor.class}, //
301             {DoubleGZip2Compressor.class}, //
302             {LongGZip2Compressor.class}, //
303             {ByteGZipCompressor.class}, //
304             {ShortGZipCompressor.class}, //
305             {IntGZipCompressor.class}, //
306             {LongGZipCompressor.class}, //
307             {FloatGZipCompressor.class}, //
308             {DoubleGZipCompressor.class}, //
309             {ByteNoCompressCompressor.class}, //
310             {ShortNoCompressCompressor.class}, //
311             {IntNoCompressCompressor.class}, //
312             {LongNoCompressCompressor.class}, //
313             {FloatNoCompressCompressor.class}, //
314             {DoubleNoCompressCompressor.class}};
315     // @formatter:on
316 
317     private static final CompressorControlNameComputer NAME_COMPUTER = new CompressorControlNameComputer();
318 
319     /**
320      * logger to log to.
321      */
322     private static final Logger LOG = Logger.getLogger(CompressorProvider.class.getName());
323 
324     public static ICompressorControl findCompressorControl(String quantAlgorithm, String compressionAlgorithm,
325             Class<?> baseType) {
326         for (ICompressorProvider iTileCompressorProvider : ServiceLoader.load(ICompressorProvider.class,
327                 Thread.currentThread().getContextClassLoader())) {
328             ICompressorControl result = iTileCompressorProvider.createCompressorControl(quantAlgorithm,
329                     compressionAlgorithm, baseType);
330             if (result != null) {
331                 return result;
332             }
333         }
334         return new CompressorProvider().createCompressorControl(quantAlgorithm, compressionAlgorithm, baseType);
335     }
336 
337     @Override
338     public ICompressorControl createCompressorControl(String quantAlgorithm, String compressionAlgorithm,
339             Class<?> baseType) {
340         Class<?> quantType = null;
341 
342         if (quantAlgorithm != null) {
343             // Standard compression via 32-bit integers...
344             if (baseType.equals(double.class) || baseType.equals(float.class)) {
345                 quantType = baseType;
346                 baseType = int.class;
347                 quantAlgorithm = null;
348             }
349         }
350 
351         String className = NAME_COMPUTER.createCompressorClassName(quantAlgorithm, compressionAlgorithm, baseType);
352 
353         for (Class<?>[] types : AVAILABLE_COMPRESSORS) {
354             Class<?> compressorClass = types[0];
355             if (compressorClass.getSimpleName().equals(className)) {
356                 TileCompressorControl tc = new TileCompressorControl(compressorClass);
357                 tc.setQuantType(quantType);
358                 return tc;
359             }
360         }
361 
362         return null;
363     }
364 }