diff --git a/src/main/java/com/bc/zarr/Compressor.java b/src/main/java/com/bc/zarr/Compressor.java index b07c2e2..131b96f 100644 --- a/src/main/java/com/bc/zarr/Compressor.java +++ b/src/main/java/com/bc/zarr/Compressor.java @@ -40,7 +40,7 @@ public abstract class Compressor { public abstract void uncompress(InputStream is, OutputStream os) throws IOException; - void passThrough(InputStream is, OutputStream os) throws IOException { + protected void passThrough(InputStream is, OutputStream os) throws IOException { final byte[] bytes = new byte[4096]; int read = is.read(bytes); while (read >= 0) { diff --git a/src/main/java/com/bc/zarr/CompressorFactory.java b/src/main/java/com/bc/zarr/CompressorFactory.java index 12de2a4..da24090 100644 --- a/src/main/java/com/bc/zarr/CompressorFactory.java +++ b/src/main/java/com/bc/zarr/CompressorFactory.java @@ -40,7 +40,6 @@ import java.io.OutputStream; import java.nio.ByteBuffer; import java.util.Arrays; -import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.zip.Deflater; @@ -52,6 +51,17 @@ public class CompressorFactory { public final static Compressor nullCompressor = new NullCompressor(); + private static final Map> registeredCompressors = defaultCompressors(); + + private static Map> defaultCompressors() { + Map> compressors = new HashMap<>(); + compressors.put("null", NullCompressor.class); + compressors.put("zlib", ZlibCompressor.class); + compressors.put("blosc", BloscCompressor.class); + + return compressors; + } + /** * @return the properties of the default compressor as a key/value map. */ @@ -69,6 +79,26 @@ public static Map getDefaultCompressorProperties() { return map; } + /** + * Add or replace a Compressor class for a given id. + * + * @param id the type of the compression algorithm + * @param compressor the class to instantiate + */ + public static void registerCompressor(String id, Class compressor) { + if (id != null) { + registeredCompressors.put(id, compressor); + } + } + + public static String[] listRegisteredCompressorIds() { + return registeredCompressors.keySet().toArray(new String[0]); + } + + public static Map> listRegisteredCompressors() { + return new HashMap<>(registeredCompressors); + } + /** * @return a new Compressor instance using the method {@link #create(Map properties)} with {@link #getDefaultCompressorProperties()}. */ @@ -92,7 +122,7 @@ public static Compressor create(Map properties) { * Creates a new {@link Compressor} instance according to the id and the given properties. * * @param id the type of the compression algorithm - * @param keyValuePair an even count of key value pairs defining the compressor specific properties + * @param keyValuePair an even count of key value pairs defining the compressor-specific properties * @return a new Compressor instance according to the id and the properties * @throws IllegalArgumentException If it is not able to create a Compressor. */ @@ -107,20 +137,27 @@ public static Compressor create(String id, Object... keyValuePair) { * Creates a new {@link Compressor} instance according to the id and the given properties. * * @param id the type of the compression algorithm - * @param properties a Map containing the compressor specific properties + * @param properties a Map containing the compressor-specific properties * @return a new Compressor instance according to the id and the properties * @throws IllegalArgumentException If it is not able to create a Compressor. */ public static Compressor create(String id, Map properties) { - if ("null".equals(id)) { - return nullCompressor; - } - if ("zlib".equals(id)) { - return new ZlibCompressor(properties); - } - if ("blosc".equals(id)) { - return new BloscCompressor(properties); + try { + if (registeredCompressors.containsKey(id)) { + Class c = registeredCompressors.get(id); + if (c.equals(NullCompressor.class)) { + return nullCompressor; + } + return c.getDeclaredConstructor(Map.class).newInstance(properties); + } + } catch (ReflectiveOperationException e) { + if (e.getCause() instanceof IllegalArgumentException) { + throw (IllegalArgumentException) e.getCause(); + } + + throw new IllegalArgumentException("Could not create compressor with id:'" + id + "'", e); } + throw new IllegalArgumentException("Compressor id:'" + id + "' not supported."); } @@ -160,7 +197,7 @@ public void uncompress(InputStream is, OutputStream os) throws IOException { private static class ZlibCompressor extends Compressor { private final int level; - private ZlibCompressor(Map map) { + protected ZlibCompressor(Map map) { final Object levelObj = map.get("level"); if (levelObj == null) { this.level = 1; //default value @@ -230,12 +267,12 @@ public static class BloscCompressor extends Compressor { public final static String[] supportedCnames = new String[]{"zstd", "blosclz", defaultCname, "lz4hc", "zlib"/*, "snappy"*/}; public final static Map defaultProperties = new HashMap() {{ - put(keyCname, defaultCname); - put(keyClevel, defaultCLevel); - put(keyShuffle, defaultShuffle); - put(keyBlocksize, defaultBlocksize); - put(keyNumThreads, defaultNumThreads); - }}; + put(keyCname, defaultCname); + put(keyClevel, defaultCLevel); + put(keyShuffle, defaultShuffle); + put(keyBlocksize, defaultBlocksize); + put(keyNumThreads, defaultNumThreads); + }}; private final int clevel; private final int blocksize; @@ -243,7 +280,7 @@ public static class BloscCompressor extends Compressor { private final String cname; private final int nthreads; - private BloscCompressor(Map map) { + protected BloscCompressor(Map map) { final Object cnameObj = map.get(keyCname); if (cnameObj == null) { cname = defaultCname; @@ -330,8 +367,8 @@ public int getNumThreads() { @Override public String toString() { return "compressor=" + getId() - + "/cname=" + cname + "/clevel=" + clevel - + "/blocksize=" + blocksize + "/shuffle=" + shuffle; + + "/cname=" + cname + "/clevel=" + clevel + + "/blocksize=" + blocksize + "/shuffle=" + shuffle; } @Override @@ -364,16 +401,15 @@ public void uncompress(InputStream is, OutputStream os) throws IOException { os.write(outBuffer.array()); } - private BufferSizes cbufferSizes(ByteBuffer cbuffer) { + protected BufferSizes cbufferSizes(ByteBuffer cbuffer) { NativeLongByReference nbytes = new NativeLongByReference(); NativeLongByReference cbytes = new NativeLongByReference(); NativeLongByReference blocksize = new NativeLongByReference(); IBloscDll.blosc_cbuffer_sizes(cbuffer, nbytes, cbytes, blocksize); BufferSizes bs = new BufferSizes(nbytes.getValue().longValue(), - cbytes.getValue().longValue(), - blocksize.getValue().longValue()); + cbytes.getValue().longValue(), + blocksize.getValue().longValue()); return bs; } } } - diff --git a/src/test/java/com/bc/zarr/BloscCompressorTest.java b/src/test/java/com/bc/zarr/BloscCompressorTest.java index a54ed50..f819620 100644 --- a/src/test/java/com/bc/zarr/BloscCompressorTest.java +++ b/src/test/java/com/bc/zarr/BloscCompressorTest.java @@ -54,10 +54,10 @@ public class BloscCompressorTest { public static void beforeClass() { final String bloscJnaLibraryPath = System.getProperty("bloscJnaLibraryPath"); - System.err.println(bloscJnaLibraryPath); final boolean bloscPathDefined = bloscJnaLibraryPath != null; final boolean bloscAvailable = isBloscAvailable(); if (bloscPathDefined && !bloscAvailable) { + System.err.println(bloscJnaLibraryPath); Assert.fail("Property for blosc has been configured, but blosc is not available."); } else { Assume.assumeTrue("Blosc library path is not configured. Blosc specifc tests are not executed.", bloscAvailable); // test is skipped if blosc is not available diff --git a/src/test/java/com/bc/zarr/CompressorFactoryTest.java b/src/test/java/com/bc/zarr/CompressorFactoryTest.java index e337fe3..5dbafff 100644 --- a/src/test/java/com/bc/zarr/CompressorFactoryTest.java +++ b/src/test/java/com/bc/zarr/CompressorFactoryTest.java @@ -122,14 +122,14 @@ public void create_compressor_not_supported() { @Test public void createBloscValidCnames() { - String[] cnames = { "zstd", "blosclz", "lz4", "lz4hc", "zlib" }; + String[] cnames = {"zstd", "blosclz", "lz4", "lz4hc", "zlib"}; for (int i = 0; i < cnames.length; i += 1) { final Compressor compressor = CompressorFactory.create("blosc", "cname", cnames[i]); assertNotNull(compressor); assertEquals("blosc", compressor.getId()); assertEquals( - "compressor=blosc/cname=" + cnames[i] + - "/clevel=5/blocksize=0/shuffle=1", compressor.toString()); + "compressor=blosc/cname=" + cnames[i] + + "/clevel=5/blocksize=0/shuffle=1", compressor.toString()); } } @@ -141,74 +141,93 @@ public void createBloscInvalidCname() { } catch (IllegalArgumentException expected) { assertEquals("blosc: compressor not supported: 'unsupported'; expected one of [zstd, blosclz, lz4, lz4hc, zlib]", expected.getMessage()); } - } - - @Test - public void createBloscValidClevel() { - final Compressor compressor = CompressorFactory.create("blosc", "clevel", 1); - assertNotNull(compressor); - assertEquals("blosc", compressor.getId()); - assertEquals( - "compressor=blosc/cname=lz4" + - "/clevel=1/blocksize=0/shuffle=1", compressor.toString()); - } - - @Test - public void createBloscInvalidClevel() { - try { - CompressorFactory.create("blosc", "clevel", -1); - fail("IllegalArgumentException expected"); - } catch (IllegalArgumentException expected) { - assertEquals("blosc: clevel parameter must be between 0 and 9 but was: -1", expected.getMessage()); - } - - try { - CompressorFactory.create("blosc", "clevel", 10); - fail("IllegalArgumentException expected"); - } catch (IllegalArgumentException expected) { - assertEquals( - "blosc: clevel parameter must be between 0 and 9 but was: 10", - expected.getMessage()); - } - } - - @Test - public void createBloscValidShuffles() { - int[] shuffles = { 0, 1, 2 }; - for (int i = 0; i < shuffles.length; i += 1) { - final Compressor compressor = CompressorFactory.create("blosc", "shuffle", shuffles[i]); - assertNotNull(compressor); - assertEquals("blosc", compressor.getId()); - assertEquals( - "compressor=blosc/cname=lz4" + - "/clevel=5/blocksize=0/shuffle=" + - shuffles[i], compressor.toString()); - } - } - - @Test - public void createBloscInvalidShuffle() { - try { - CompressorFactory.create("blosc", "shuffle", -1); - fail("IllegalArgumentException expected"); - } catch (IllegalArgumentException expected) { - assertEquals( - "blosc: shuffle type not supported: '-1'; expected one of [0 (NOSHUFFLE), 1 (BYTESHUFFLE), 2 (BITSHUFFLE)]", - expected.getMessage()); - } - } - - @Test - public void createBloscValidBlockSizes() { - int[] blockSizes = { 0, 1, 20 }; - for (int i = 0; i < blockSizes.length; i += 1) { - final Compressor compressor = CompressorFactory.create("blosc", "blocksize", blockSizes[i]); - assertNotNull(compressor); - assertEquals("blosc", compressor.getId()); - assertEquals( - "compressor=blosc/cname=lz4" + - "/clevel=5/blocksize=" + blockSizes[i] + - "/shuffle=1", compressor.toString()); - } - } + } + + @Test + public void createBloscValidClevel() { + final Compressor compressor = CompressorFactory.create("blosc", "clevel", 1); + assertNotNull(compressor); + assertEquals("blosc", compressor.getId()); + assertEquals( + "compressor=blosc/cname=lz4" + + "/clevel=1/blocksize=0/shuffle=1", compressor.toString()); + } + + @Test + public void createBloscInvalidClevel() { + try { + CompressorFactory.create("blosc", "clevel", -1); + fail("IllegalArgumentException expected"); + } catch (IllegalArgumentException expected) { + assertEquals("blosc: clevel parameter must be between 0 and 9 but was: -1", expected.getMessage()); + } + + try { + CompressorFactory.create("blosc", "clevel", 10); + fail("IllegalArgumentException expected"); + } catch (IllegalArgumentException expected) { + assertEquals( + "blosc: clevel parameter must be between 0 and 9 but was: 10", + expected.getMessage()); + } + } + + @Test + public void createBloscValidShuffles() { + int[] shuffles = {0, 1, 2}; + for (int i = 0; i < shuffles.length; i += 1) { + final Compressor compressor = CompressorFactory.create("blosc", "shuffle", shuffles[i]); + assertNotNull(compressor); + assertEquals("blosc", compressor.getId()); + assertEquals( + "compressor=blosc/cname=lz4" + + "/clevel=5/blocksize=0/shuffle=" + + shuffles[i], compressor.toString()); + } + } + + @Test + public void createBloscInvalidShuffle() { + try { + CompressorFactory.create("blosc", "shuffle", -1); + fail("IllegalArgumentException expected"); + } catch (IllegalArgumentException expected) { + assertEquals( + "blosc: shuffle type not supported: '-1'; expected one of [0 (NOSHUFFLE), 1 (BYTESHUFFLE), 2 (BITSHUFFLE)]", + expected.getMessage()); + } + } + + @Test + public void createBloscValidBlockSizes() { + int[] blockSizes = {0, 1, 20}; + for (int i = 0; i < blockSizes.length; i += 1) { + final Compressor compressor = CompressorFactory.create("blosc", "blocksize", blockSizes[i]); + assertNotNull(compressor); + assertEquals("blosc", compressor.getId()); + assertEquals( + "compressor=blosc/cname=lz4" + + "/clevel=5/blocksize=" + blockSizes[i] + + "/shuffle=1", compressor.toString()); + } + } + + @Test + public void registerNewCompressor() { + final String id = "test"; + CompressorFactory.registerCompressor(id, TestUtils.TestCompressor.class); + Compressor compressor = CompressorFactory.create(id, TestUtils.createMap("level", 1)); + assertNotNull(compressor); + assertEquals(id, compressor.getId()); + assertEquals(TestUtils.TestCompressor.class, compressor.getClass());CompressorFactory.registerCompressor(id, TestUtils.TestCompressor.class); + + // Replacement + CompressorFactory.registerCompressor(id, CompressorFactory.BloscCompressor.class); + compressor = CompressorFactory.create(id); + assertNotNull(compressor); + // Expected not to match. + assertEquals("blosc", compressor.getId()); + assertEquals(CompressorFactory.BloscCompressor.class, compressor.getClass()); + + } } diff --git a/src/test/java/com/bc/zarr/TestUtils.java b/src/test/java/com/bc/zarr/TestUtils.java index 8044949..7024809 100644 --- a/src/test/java/com/bc/zarr/TestUtils.java +++ b/src/test/java/com/bc/zarr/TestUtils.java @@ -98,4 +98,23 @@ public static boolean deleteLineContaining(String str, InputStream is, OutputStr } return deleted; } + + public static class TestCompressor extends Compressor { + public TestCompressor(Map properties) { + } + + public String getId() { + return "test"; + } + + public String toString() { + return getId(); + } + + public void compress(InputStream is, OutputStream os) throws IOException { + } + + public void uncompress(InputStream is, OutputStream os) throws IOException { + } + } }