/*
 * Copyright 2007 - 2018 ETH Zuerich, CISD and SIS.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package ch.systemsx.cisd.hdf5;

import static ch.systemsx.cisd.hdf5.HDF5ArrayBlockParamsBuilder.array;
import static ch.systemsx.cisd.hdf5.HDF5ArrayBlockParamsBuilder.block;
import static ch.systemsx.cisd.hdf5.HDF5ArrayBlockParamsBuilder.blockIndex;
import static ch.systemsx.cisd.hdf5.HDF5ArrayBlockParamsBuilder.slice;
import static ch.systemsx.cisd.hdf5.HDF5CompoundMemberMapping.mapping;
import static ch.systemsx.cisd.hdf5.HDF5FloatStorageFeatures.FLOAT_CHUNKED;
import static ch.systemsx.cisd.hdf5.HDF5FloatStorageFeatures.FLOAT_DEFLATE;
import static ch.systemsx.cisd.hdf5.HDF5FloatStorageFeatures.FLOAT_SCALING1_DEFLATE;
import static ch.systemsx.cisd.hdf5.HDF5GenericStorageFeatures.GENERIC_DEFLATE;
import static ch.systemsx.cisd.hdf5.HDF5GenericStorageFeatures.GENERIC_DEFLATE_MAX;
import static ch.systemsx.cisd.hdf5.HDF5IntStorageFeatures.INT_AUTO_SCALING;
import static ch.systemsx.cisd.hdf5.HDF5IntStorageFeatures.INT_AUTO_SCALING_DEFLATE;
import static ch.systemsx.cisd.hdf5.HDF5IntStorageFeatures.INT_CHUNKED;
import static ch.systemsx.cisd.hdf5.HDF5IntStorageFeatures.INT_DEFLATE;
import static ch.systemsx.cisd.hdf5.HDF5IntStorageFeatures.INT_SHUFFLE_DEFLATE;
import static ch.systemsx.cisd.hdf5.MatrixUtils.dims;
import static ch.systemsx.cisd.hdf5.MatrixUtils.ldims;
import static ch.systemsx.cisd.hdf5.UnsignedIntUtils.toInt32;
import static ch.systemsx.cisd.hdf5.UnsignedIntUtils.toInt8;
import static hdf.hdf5lib.HDF5Constants.H5T_ENUM;
import static hdf.hdf5lib.HDF5Constants.H5T_FLOAT;
import static hdf.hdf5lib.HDF5Constants.H5T_INTEGER;
import static hdf.hdf5lib.HDF5Constants.H5T_REFERENCE;
import static org.testng.AssertJUnit.assertEquals;
import static org.testng.AssertJUnit.assertFalse;
import static org.testng.AssertJUnit.assertNotNull;
import static org.testng.AssertJUnit.assertNull;
import static org.testng.AssertJUnit.assertTrue;
import static org.testng.AssertJUnit.fail;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Random;
import java.util.Set;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.testng.annotations.AfterSuite;
import org.testng.annotations.BeforeSuite;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

import ch.systemsx.cisd.base.convert.NativeData;
import ch.systemsx.cisd.base.convert.NativeData.ByteOrder;
import ch.systemsx.cisd.base.mdarray.MDArray;
import ch.systemsx.cisd.base.mdarray.MDByteArray;
import ch.systemsx.cisd.base.mdarray.MDDoubleArray;
import ch.systemsx.cisd.base.mdarray.MDFloatArray;
import ch.systemsx.cisd.base.mdarray.MDIntArray;
import ch.systemsx.cisd.base.mdarray.MDLongArray;
import ch.systemsx.cisd.base.mdarray.MDShortArray;
import ch.systemsx.cisd.base.utilities.OSUtilities;
import ch.systemsx.cisd.hdf5.HDF5CompoundMappingHints.EnumReturnType;
import ch.systemsx.cisd.hdf5.HDF5DataTypeInformation.DataTypeInfoOptions;
import ch.systemsx.cisd.hdf5.IHDF5WriterConfigurator.SyncMode;
import ch.systemsx.cisd.hdf5.exceptions.HDF5SpaceRankMismatch;
import ch.systemsx.cisd.hdf5.hdf5lib.HDFHelper;
import hdf.hdf5lib.H5;
import hdf.hdf5lib.HDF5Constants;
import hdf.hdf5lib.exceptions.HDF5DatatypeInterfaceException;
import hdf.hdf5lib.exceptions.HDF5JavaException;
import hdf.hdf5lib.exceptions.HDF5LibraryException;
import hdf.hdf5lib.exceptions.HDF5SymbolTableException;

/**
 * Test cases for {@link IHDF5Writer} and {@link IHDF5Reader}, doing "round-trips" to the HDF5 disk
 * format and back.
 * 
 * @author Bernd Rinn
 */
public class HDF5RoundtripTest
{

    private static final File rootDirectory = new File("targets", "unit-test-wd");

    private static final File workingDirectory = new File(rootDirectory, "hdf5-roundtrip-wd");

    @BeforeSuite
    public void init()
    {
        workingDirectory.mkdirs();
        assertTrue(workingDirectory.isDirectory());
        workingDirectory.deleteOnExit();
        rootDirectory.deleteOnExit();
    }

    @AfterSuite
    protected void finalizeTest() throws Throwable
    {
        // Delete the working directory
        if (workingDirectory.exists() && workingDirectory.canWrite())
        {
            workingDirectory.delete();
        }
        // Delete root directory
        if (rootDirectory.exists() && rootDirectory.canWrite())
        {
            rootDirectory.delete();
        }

    }

    public static void main(String[] args) throws Throwable
    {
        // Print Java Version
        System.out.println("Java Version: " + System.getProperty("java.version"));

        // Print OS Version
        System.out.println("Platform: " + OSUtilities.getComputerPlatform());

        HDF5RoundtripTest test = new HDF5RoundtripTest();
        test.init();

        // Print Library Version
        final int[] libversion = new int[3];
        H5.H5get_libversion(libversion);
        System.out.println("HDF5 Version: " + libversion[0] + "." + libversion[1] + "."
                + libversion[2]);

        // Tests
        test.testWriteToEmptyFile();
        test.testStrangeDataSetName();
        test.testCreateSomeDeepGroup();
        test.testGetGroupMembersIteratively();
        test.testScalarValues();
        test.testUnsignedInt8ValuesArray();
        test.testUnsignedInt16ValuesArray();
        test.testOverwriteScalar();
        test.testOverwriteScalarKeepDataSet();
        test.testDataSets();
        test.testCopyDataSet();
        test.testFixedLengthStringArray();
        test.testVLStringCrash();
        test.testDataTypeInfoOptions();
        test.testCompactDataset();
        test.testCreateEmptyFixedSizeDataSets();
        test.testCreateEmptyDefaultFixedSizeDataSets();
        test.testCreateEmptyGrowableDataSets();
        test.testCreateZeroLengthGrowableDataSets();
        test.testExtendChunkedDataset();
        test.testMaxPathLength();
        test.testExceedMaxPathLength();
        test.testAccessClosedReaderWriter();
        test.testDataSetsNonExtendable();
        test.testOverwriteContiguousDataSet();
        test.testScaleOffsetFilterInt();
        test.testScaleOffsetFilterFloat();
        test.testBooleanArray();
        test.testBooleanArrayBlock();
        test.testFloatArrayBlockWithPreopenedDataSet();
        test.testFloatArraysFromTemplates();
        try
        {
            test.testMDShortArrayRankMismatch();
            fail("Failed to detect space rank mismatch.");
        } catch (HDF5SpaceRankMismatch ex)
        {
            // That is expected.
        }
        try
        {
            test.testMDShortArrayRankMismatchDetached();
            fail("Failed to detect space rank mismatch.");
        } catch (HDF5SpaceRankMismatch ex)
        {
            // That is expected.
        }
        try
        {
            test.testMDLongArrayArrayIndexOutOfBounds();
            fail("Failed to detect array out of bounds.");
        } catch (HDF5JavaException ex)
        {
            // That is expected.
        }
        try
        {
            test.testMDLongArrayOnReadArrayIndexOutOfBounds();
            fail("Failed to detect array out of bounds.");
        } catch (HDF5JavaException ex)
        {
            // That is expected.
        }
        test.testFloatMDArraysDetachedDataSets();
        test.testFloatMDArraysFromTemplates();
        test.testBitFieldArray();
        test.testBitFieldArrayBlockWise();
        test.testSmallString();
        test.testReadStringAttributeAsByteArray();
        test.testReadStringAsByteArray();
        test.testReadStringVLAsByteArray();
        test.testStringAttributeFixedLength();
        test.testStringAttributeFixedLengthExplicitlySaveLength();
        test.testStringAttributeLength0();
        test.testStringAttributeFixedLengthOverwriteWithShorter();
        test.testStringAttributeUTF8FixedLength();
        test.testStringArrayAttributeLengthFitsValue();
        test.testStringArrayAttributeFixedLength();
        test.testStringArrayAttributeUTF8LengthFitsValue();
        test.testStringArrayAttributeUTF8FixedLength();
        test.testStringMDArrayAttributeFixedLength();
        test.testStringMDArrayAttributeUTF8LengthFitsValue();
        test.testStringMDArrayAttributeUTF8FixedLength();
        test.testVeryLargeString();
        test.testOverwriteString();
        test.testOverwriteStringWithLarge();
        test.testOverwriteStringWithLargeKeepCompact();
        test.testStringCompact();
        test.testStringContiguous();
        test.testStringUnicode();
        test.testStringVariableLength();
        test.testStringArray();
        test.testStringArrayUTF8();
        test.testStringArrayUTF8WithZeroChar();
        test.testStringArrayWithNullStrings();
        test.testStringMDArrayWithNullStrings();
        test.testStringArrayBlock();
        test.testStringArrayBlockCompact();
        test.testStringArrayCompact();
        test.testStringCompression();
        test.testStringArrayCompression();
        test.testStringVLArray();
        test.testStringArrayBlockVL();
        test.testStringArrayMD();
        test.testStringArrayMDBlocks();
        test.testStringMDArrayVL();
        test.testStringMDArrayVLBlocks();
        test.testMDIntArrayDifferentSizesElementType();
        test.testMDIntArrayDifferentSizesElementTypeUnsignedByte();
        test.testReadMDFloatArrayWithSlicing();
        test.testReadToFloatMDArray();
        test.testFloatArrayTypeDataSet();
        test.testFloatArrayTypeDataSetOverwrite();
        test.testFloatArrayCreateCompactOverwriteBlock();
        test.testFloatMDArrayTypeDataSet();
        test.testIterateOverFloatArrayInNaturalBlocks(10, 99);
        test.testIterateOverFloatArrayInNaturalBlocks(10, 100);
        test.testIterateOverFloatArrayInNaturalBlocks(10, 101);
        test.testIterateOverStringArrayInNaturalBlocks(10, 99);
        test.testIterateOverStringArrayInNaturalBlocks(10, 100);
        test.testIterateOverStringArrayInNaturalBlocks(10, 101);
        test.testReadToFloatMDArrayBlockWithOffset();
        test.testReadToTimeDurationMDArrayBlockWithOffset();
        test.testIterateOverMDFloatArrayInNaturalBlocks(new int[]
            { 2, 2 }, new long[]
            { 4, 3 }, new float[]
            { 0f, 2f, 6f, 8f }, new int[][]
            {
                { 2, 2 },
                { 2, 1 },
                { 2, 2 },
                { 2, 1 } });
        test.testIterateOverMDFloatArrayInNaturalBlocks(new int[]
            { 2, 2 }, new long[]
            { 4, 4 }, new float[]
            { 0f, 2f, 8f, 10f }, new int[][]
            {
                { 2, 2 },
                { 2, 2 },
                { 2, 2 },
                { 2, 2 } });
        test.testIterateOverMDFloatArrayInNaturalBlocks(new int[]
            { 2, 2 }, new long[]
            { 4, 5 }, new float[]
            { 0f, 2f, 4f, 10f, 12f, 14f }, new int[][]
            {
                { 2, 2 },
                { 2, 2 },
                { 2, 1 },
                { 2, 2 },
                { 2, 2 },
                { 2, 1 } });
        test.testIterateOverMDFloatArrayInNaturalBlocks(new int[]
            { 3, 2 }, new long[]
            { 5, 4 }, new float[]
            { 0f, 2f, 12f, 14f }, new int[][]
            {
                { 3, 2 },
                { 3, 2 },
                { 2, 2 },
                { 2, 2 } });
        test.testIterateOverMDFloatArrayInNaturalBlocks(new int[]
            { 2, 2 }, new long[]
            { 5, 4 }, new float[]
            { 0f, 2f, 8f, 10f, 16f, 18f }, new int[][]
            {
                { 2, 2 },
                { 2, 2 },
                { 2, 2 },
                { 2, 2 },
                { 1, 2 },
                { 1, 2 } });
        test.testSetExtentBug();
        test.testMDFloatArrayBlockWise();
        test.testMDFloatArraySliced();
        test.testMDFloatArraySlicedDetachedDataSet();
        test.testMDFloatArrayBlockWiseWithMemoryOffset();
        test.testDoubleArrayAsByteArray();
        test.testCompressedDataSet();
        test.testCreateEmptyFloatMatrix();
        test.testFloatVectorLength1();
        test.testFloatMatrixLength1();
        test.testOneRowFloatMatrix();
        test.testEmptyVectorDataSets();
        test.testEmptyVectorDataSetsContiguous();
        test.testEmptyVectorDataSetsCompact();
        test.testEmptyMatrixDataSets();
        test.testEmptyMatrixDataSetsContiguous();
        test.testOverwriteVectorIncreaseSize();
        test.testOverwriteMatrixIncreaseSize();
        test.testOverwriteStringVectorDecreaseSize();
        test.testAttributes();
        test.testSimpleDataspaceAttributes();
        test.testTimeStampAttributes();
        test.testTimeDurationAttributes();
        test.testTimeStampArrayAttributes();
        test.testTimeDurationArrayAttributes();
        test.testAttributeDimensionArray();
        test.testAttributeDimensionArrayOverwrite();
        test.testCreateDataTypes();
        test.testGroups();
        test.testDefaultHousekeepingFile();
        test.testNonDefaultHousekeepingFile();
        test.testHousekeepingFileSuffixNonPrintable();
        test.testSoftLink();
        test.testBrokenSoftLink();
        test.testDeleteSoftLink();
        test.testRenameLink();
        try
        {
            test.testRenameLinkOverwriteFails();
            fail("testRenameLinkOverwriteFails(): failure not detected.");
        } catch (HDF5LibraryException ex)
        {
            // Expected.
        }
        try
        {
            test.testRenameLinkSrcNonExistentFails();
            fail("testRenameLinkSrcNonExistentFails(): failure not detected.");
        } catch (HDF5LibraryException ex)
        {
            // Expected.
        }
        test.testOverwriteKeepWithEmptyString();
        test.testOverwriteKeepWithShorterString();
        test.testOverwriteKeepWithLongerString();
        test.testReplaceWithLongerString();
        test.testNullOnGetSymbolicLinkTargetForNoLink();
        test.testUpdateSoftLink();
        test.testExternalLink();
        test.testEnum();
        test.testAnonymousEnum();
        test.testJavaEnum();
        test.testEnum16();
        try
        {
            test.testConfusedEnum();
            fail("testConfusedEnum(): failure not detected.");
        } catch (HDF5JavaException ex)
        {
            assertEquals("Enum member index 0 of enum testEnum is 'ONE', but should be 'THREE'",
                    ex.getMessage());
        }
        test.testReplaceConfusedEnum();
        test.testEnumArray();
        test.testEnumMDArray();
        test.testEnumMDArrayBlockWise();
        test.testJavaEnumArray();
        test.testEnumArrayBlock();
        test.testEnumArrayBlockScalingCompression();
        test.testEnumArrayFromIntArray();
        test.testEnumArray16BitFromIntArray();
        test.testEnumArray16BitFromIntArrayScaled();
        test.testEnumArray16BitFromIntArrayLarge();
        test.testEnumArrayBlock16Bit();
        test.testEnumArrayScaleCompression();
        test.testOpaqueType();
        test.testCompound();
        test.testCompoundInferStringLength();
        test.testCompoundVariableLengthString();
        test.testCompoundVariableLengthStringUsingHints();
        test.testCompoundReference();
        test.testCompoundHintVLString();
        test.testClosedCompoundType();
        test.testAnonCompound();
        test.testOverwriteCompound();
        test.testOverwriteCompoundKeepType();
        test.testCompoundJavaEnum();
        test.testEnumFromCompoundJavaEnum();
        test.testCompoundJavaEnumArray();
        test.testCompoundJavaEnumMap();
        test.testCompoundAttribute();
        test.testCompoundAttributeMemoryAlignment();
        test.testCompoundIncompleteJavaPojo();
        test.testCompoundManualMapping();
        test.testInferredCompoundType();
        test.testInferredIncompletelyMappedCompoundType();
        test.testNameChangeInCompoundMapping();
        test.testInferredCompoundTypedWithEnum();
        test.testInferredCompoundTypeWithEnumArray();
        test.testCompoundMap();
        test.testCompoundMapManualMapping();
        test.testCompoundMapManualMappingWithConversion();
        test.testDateCompound();
        test.testMatrixCompound();
        try
        {
            test.testMatrixCompoundSizeMismatch();
            fail("testMatrixCompoundSizeMismatch(): failure not detected.");
        } catch (IllegalArgumentException ex)
        {
            // Expected
        }
        try
        {
            test.testMatrixCompoundDifferentNumberOfColumnsPerRow();
           fail("testMatrixCompoundDifferentNumberOfColumnsPerRow(): failure not detected.");
        } catch (IllegalArgumentException ex)
        {
            // Expected
        }
        test.testCompoundOverflow();
        test.testBitFieldCompound();
        test.testCompoundMapArray();
        test.testCompoundArray();
        test.testCompoundArrayBlockWise();
        test.testCompoundMapMDArray();
        test.testCompoundMDArray();
        test.testCompoundMDArrayManualMapping();
        test.testCompoundMDArrayBlockWise();
        test.testIterateOverMDCompoundArrayInNaturalBlocks();
        test.testConfusedCompound();
        test.testMDArrayCompound();
        test.testMDArrayCompoundArray();
        test.testGetGroupMemberInformation();
        try
        {
            test.testGetLinkInformationFailed();
            fail("testGetObjectTypeFailed(): failure not detected.");
        } catch (HDF5JavaException ex)
        {
            // Expected
        }
        test.testGetObjectType();
        test.testHardLink();
        test.testNullOnGetSymbolicLinkTargetForNoLink();
        test.testReadByteArrayDataSetBlockWise();
        test.testWriteByteArrayDataSetBlockWise();
        test.testCreateByteArrayDataSetBlockSize0();
        test.testCreateFloatArrayWithDifferentStorageLayouts();
        test.testWriteByteArrayDataSetBlockWiseExtend();
        test.testWriteByteMatrixDataSetBlockWise();
        test.testWriteByteArrayDataSetBlockWiseMismatch();
        test.testWriteByteMatrixDataSetBlockWiseMismatch();
        test.testReadFloatMatrixDataSetBlockWise();
        test.testWriteFloatMatrixDataSetBlockWise();
        test.testWriteFloatMatrixDataSetBlockWiseWithOffset();
        test.testReadMDFloatArrayAsByteArray();
        test.testExtendContiguousDataset();
        test.testAutomaticDeletionOfDataSetOnWrite();
        test.testAutomaticDeletionOfDataSetOnCreate();
        test.testTimestamps();
        test.testTimestampArray();
        test.testTimestampArrayChunked();
        test.testTimeDurations();
        test.testSmallTimeDurations();
        test.testTimeDurationArray();
        test.testTimeDurationMDArray();
        test.testTimeDurationArrayChunked();
        test.testNumericConversion();
        test.testNumericConversionWithNumericConversionsSwitchedOff();
        test.testSetDataSetSize();
        test.testObjectReference();
        test.testObjectReferenceArray();
        test.testObjectReferenceOverwriteWithKeep();
        test.testObjectReferenceArrayBlockWise();
        test.testObjectReferenceAttribute();
        test.testObjectReferenceArrayAttribute();
        test.testObjectReferenceMDArrayAttribute();
        test.testObjectReferenceMDArray();
        test.testObjectReferenceMDArrayBlockWise();
        test.testObjectReferenceToMDFloatArray();
        test.testHDFJavaLowLevel();
        test.testMDCImageGeneration();
        test.testMDCImageGenerationBug();

        test.finalizeTest();
    }

    @Test
    public void testWriteToEmptyFile()
    {
        final File file = new File(workingDirectory, "testStrangeDataSetName.h5");
        file.delete();
        assertFalse(file.exists());
        try
        {
            new FileOutputStream(file).close();
        } catch (IOException e)
        {
            fail("Cannot touch file" + file.getAbsolutePath());
        }
        file.deleteOnExit();
        final IHDF5Writer writer = HDF5Factory.configure(file).writer();
        writer.writeString("a", "b");
        assertEquals("b", writer.readString("a"));
        writer.close();
    }
    
    @Test
    public void testStrangeDataSetName()
    {
        final File file = new File(workingDirectory, "testStrangeDataSetName.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer writer = HDF5Factory.configure(file).noAutoDereference().writer();
        writer.int32().write("\0\255", 15);
        writer.int32().write("\0\254", 13);
        writer.close();
        final IHDF5Reader reader =
                HDF5Factory.configureForReading(file).noAutoDereference().reader();
        assertEquals(15, reader.int32().read("\0\255"));
        assertEquals(13, reader.int32().read("\0\254"));
        reader.close();
    }

    @Test
    public void testCreateSomeDeepGroup()
    {
        final File datasetFile = new File(workingDirectory, "deepGroup.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        final IHDF5Writer writer =
                HDF5FactoryProvider.get().configure(datasetFile).useUTF8CharacterEncoding()
                        .writer();
        final String groupName = "/some/deep/and/non/existing/group";
        writer.object().createGroup(groupName);
        assertTrue(writer.isGroup(groupName));
        assertFalse(HDF5Factory.reset());
        writer.close();
        assertTrue(HDF5Factory.reset());
    }

    @Test
    public void testGetGroupMembersIteratively()
    {
        final File datasetFile = new File(workingDirectory, "writereadwriteread.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(datasetFile);
        final String groupName = "/test/group/";
        final String dset1Name = "dset1";
        final String dset1Path = groupName + dset1Name;
        final float[] dset1 = new float[]
            { 1.3f, 2.4f, 3.6f };
        writer.float32().writeArray(dset1Path, dset1);
        final List<String> members1 = writer.getGroupMembers(groupName);
        assertEquals(1, members1.size());
        assertEquals(dset1Name, members1.get(0));
        final String dset2Name = "dset2";
        final String dset2Path = groupName + dset2Name;
        final int[] dset2 = new int[]
            { 1, 2, 3 };
        writer.int32().writeArray(dset2Path, dset2);
        final Set<String> members2 = new HashSet<String>(writer.getGroupMembers(groupName));
        assertEquals(2, members2.size());
        assertTrue(members2.contains(dset1Name));
        assertTrue(members2.contains(dset2Name));
        writer.close();
    }

    @Test
    public void testOverwriteScalar()
    {
        final File datasetFile = new File(workingDirectory, "overwriteScalar.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        final IHDF5Writer writer = HDF5Factory.open(datasetFile);
        writer.int32().write("a", 4);
        assertEquals(HDF5DataClass.INTEGER, writer.getDataSetInformation("a").getTypeInformation()
                .getDataClass());
        assertTrue(writer.getDataSetInformation("a").isSigned());
        writer.float32().write("a", 1e6f);
        assertEquals(HDF5DataClass.FLOAT, writer.getDataSetInformation("a").getTypeInformation()
                .getDataClass());
        assertTrue(writer.getDataSetInformation("a").isSigned());
        assertEquals(1e6f, writer.float32().read("a"));
        writer.close();
    }

    @Test
    public void testOverwriteScalarKeepDataSet()
    {
        final File datasetFile = new File(workingDirectory, "overwriteScalarKeepDataSet.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        final IHDF5Writer writer =
                HDF5Factory.configure(datasetFile).keepDataSetsIfTheyExist().writer();
        writer.int32().write("a", 4);
        assertEquals(HDF5DataClass.INTEGER, writer.getDataSetInformation("a").getTypeInformation()
                .getDataClass());
        writer.float32().write("a", 5.1f);
        assertEquals(HDF5DataClass.INTEGER, writer.getDataSetInformation("a").getTypeInformation()
                .getDataClass());
        assertEquals(5, writer.int32().read("a"));
        writer.close();
    }

    @Test
    public void testScalarValues()
    {
        final File datasetFile = new File(workingDirectory, "values.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(datasetFile);
        final String booleanDatasetName = "/boolean";
        writer.writeBoolean(booleanDatasetName, true);
        final String byteDatasetName = "/byte";
        writer.int8().write(byteDatasetName, toInt8(17));
        final String unsignedByteOverflowDatasetName = "/ubyteOverflow";
        writer.uint8().write(unsignedByteOverflowDatasetName, (byte) 1024);
        final String shortDatasetName = "/short";
        writer.int16().write(shortDatasetName, (short) 1000);
        final String intDatasetName = "/int";
        writer.int32().write(intDatasetName, 1000000);
        final String longDatasetName = "/long";
        writer.int64().write(longDatasetName, 10000000000L);
        final String floatDatasetName = "/float";
        writer.float32().write(floatDatasetName, 0.001f);
        final String doubleDatasetName = "/double";
        writer.float64().write(doubleDatasetName, 1.0E100);
        final String stringDatasetName = "/string";
        writer.string().write(stringDatasetName, "some string");
        final String stringWithZeroDatasetName = "/stringWithZero";
        writer.string().write(stringWithZeroDatasetName, "some string\0with zero");
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        assertTrue(reader.readBoolean(booleanDatasetName));
        assertEquals(0, reader.object().getRank(booleanDatasetName));
        assertTrue(Arrays.equals(new long[0], reader.object().getDimensions(booleanDatasetName)));
        assertEquals(17, reader.int8().read(byteDatasetName));
        assertEquals(0, reader.object().getRank(byteDatasetName));
        assertTrue(Arrays.equals(new long[0], reader.object().getDimensions(byteDatasetName)));
        assertTrue(reader.getDataSetInformation(byteDatasetName).isSigned());
        assertEquals(0, reader.int16().read(unsignedByteOverflowDatasetName));
        assertFalse(reader.getDataSetInformation(unsignedByteOverflowDatasetName).isSigned());
        assertEquals(1000, reader.int16().read(shortDatasetName));
        assertEquals(1000000, reader.int32().read(intDatasetName));
        assertEquals(10000000000L, reader.int64().read(longDatasetName));
        assertEquals(0.001f, reader.float32().read(floatDatasetName));
        assertEquals(0, reader.object().getRank(floatDatasetName));
        assertTrue(Arrays.equals(new long[0], reader.object().getDimensions(floatDatasetName)));
        assertEquals(1.0E100, reader.float64().read(doubleDatasetName));
        assertEquals("some string", reader.string().read(stringDatasetName));
        assertEquals("some string", reader.string().read(stringWithZeroDatasetName));
        assertEquals("some string\0with zero", reader.string().readRaw(stringWithZeroDatasetName));
        reader.close();
    }

    @Test
    public void testUnsignedInt8ValuesArray()
    {
        final String byteDatasetName = "/byte";
        final String uintAttrName = "uattr";
        final File datasetFile = new File(workingDirectory, "unsignedInt8Values.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(datasetFile);
        final byte[] valuesWritten = new byte[]
            { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, (byte) 128, (byte) 255 };
        writer.uint8().writeArray(byteDatasetName, valuesWritten);
        writer.uint8().setAttr(byteDatasetName, uintAttrName, (byte) 224);
        final byte[] valuesRead1 = writer.uint8().readArray(byteDatasetName);
        assertTrue(Arrays.equals(valuesWritten, valuesRead1));
        assertEquals(224, UnsignedIntUtils.toUint8(writer.uint8().getAttr(byteDatasetName, uintAttrName)));
        writer.close();
        
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        assertFalse(reader.getDataSetInformation(byteDatasetName).isSigned());
        final byte[] valuesRead2 = reader.uint8().readArray(byteDatasetName);
        assertTrue(Arrays.equals(valuesWritten, valuesRead2));
        assertEquals(224, UnsignedIntUtils.toUint8(reader.uint8().getAttr(byteDatasetName, uintAttrName)));
        // Alternative is to use larger intege number when reading.
        assertEquals(224, reader.uint16().getAttr(byteDatasetName, uintAttrName));
        assertEquals(224, reader.int16().getAttr(byteDatasetName, uintAttrName));
        // The regular int8() reader doesn't read this number correctly. 
        assertEquals(127, UnsignedIntUtils.toUint8(reader.int8().getAttr(byteDatasetName, uintAttrName)));
        assertEquals(127, reader.int8().getAttr(byteDatasetName, uintAttrName));
        reader.close();
    }

    @Test
    public void testUnsignedInt16ValuesArray()
    {
        final String byteDatasetName = "/byte";
        final File datasetFile = new File(workingDirectory, "unsignedInt16Values.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(datasetFile);
        final short[] valuesWritten = new short[]
            { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 256, 1024 };
        writer.uint16().writeArray(byteDatasetName, valuesWritten);
        writer.uint16().setAttr(byteDatasetName, "attr", (short) 60000);
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        assertFalse(reader.getDataSetInformation(byteDatasetName).isSigned());
        final short[] valuesRead = reader.uint16().readArray(byteDatasetName);
        assertTrue(Arrays.equals(valuesWritten, valuesRead));
        assertEquals(60000,
                UnsignedIntUtils.toUint16(reader.uint16().getAttr(byteDatasetName, "attr")));
        reader.close();
    }

    @Test
    public void testReadMDFloatArrayWithSlicing()
    {
        final File datasetFile = new File(workingDirectory, "mdArray.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(datasetFile);
        final String floatDatasetName = "/floatMatrix";
        final MDFloatArray arrayWritten = new MDFloatArray(new int[]
            { 3, 2, 4 });
        int count = 0;
        for (int i = 0; i < arrayWritten.size(0); ++i)
        {
            for (int j = 0; j < arrayWritten.size(1); ++j)
            {
                for (int k = 0; k < arrayWritten.size(2); ++k)
                {
                    arrayWritten.set(++count, new int[]
                        { i, j, k });
                }
            }
        }
        writer.float32().writeMDArray(floatDatasetName, arrayWritten);
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        final MDFloatArray arrayRead = reader.float32().readMDArray(floatDatasetName);
        final MDFloatArray arrayRead2 = reader.float32().readMDArray(floatDatasetName, array());
        assertEquals(arrayWritten, arrayRead);
        assertEquals(arrayWritten, arrayRead2);
        final IndexMap boundIndex1 = new IndexMap().bind(1, 0);
        final long[] boundIndex1Arr = new long[]
            { -1, 0, -1 };
        final MDFloatArray slice1 = new MDFloatArray(new float[]
            { 1f, 2f, 3f, 4f, 9f, 10f, 11f, 12f, 17f, 18f, 19f, 20f }, new int[]
            { 3, 4 });
        final MDFloatArray slice1BlockOfs00 = new MDFloatArray(new float[]
            { 1f, 2f, 9f, 10f }, new int[]
            { 2, 2 });
        final MDFloatArray slice1BlockOfs01 = new MDFloatArray(new float[]
            { 2f, 3f, 10f, 11f }, new int[]
            { 2, 2 });
        final MDFloatArray slice1BlockOfs10 = new MDFloatArray(new float[]
            { 9f, 10f, 17f, 18f }, new int[]
            { 2, 2 });
        final MDFloatArray slice1BlockOfs11 = new MDFloatArray(new float[]
            { 10f, 11f, 18f, 19f }, new int[]
            { 2, 2 });
        final IndexMap boundIndex2 = new IndexMap().bind(2, 3).bind(0, 1);
        final long[] boundIndex2Arr = new long[]
            { 1, -1, 3 };
        final MDFloatArray slice2 = new MDFloatArray(new float[]
            { 12f, 16f }, new int[]
            { 2 });
        final IndexMap boundIndex3 = new IndexMap().bind(2, 3).bind(0, 1).bind(1, 0);
        final long[] boundIndex3Arr = new long[]
            { 1, 0, 3 };
        final MDFloatArray slice3 = new MDFloatArray(new float[]
            { 12f }, new int[]
            { 1 });
        assertEquals(slice1, reader.float32().readMDArraySlice(floatDatasetName, boundIndex1));
        assertEquals(slice1, reader.float32().readMDArray(floatDatasetName, slice(boundIndex1)));
        assertEquals(slice1, reader.float32().readMDArraySlice(floatDatasetName, boundIndex1Arr));
        assertEquals(slice1, reader.float32().readMDArray(floatDatasetName, slice(boundIndex1Arr)));
        assertEquals(slice1BlockOfs00,
                reader.float32().readSlicedMDArrayBlockWithOffset(floatDatasetName, new int[]
                    { 2, 2 }, new long[]
                    { 0, 0 }, boundIndex1));
        assertEquals(slice1BlockOfs00,
                reader.float32().readMDArray(floatDatasetName, block(2, 2).slice(boundIndex1)));
        assertEquals(slice1BlockOfs00,
                reader.float32().readSlicedMDArrayBlockWithOffset(floatDatasetName, new int[]
                    { 2, 2 }, new long[]
                    { 0, 0 }, boundIndex1Arr));
        assertEquals(slice1BlockOfs00,
                reader.float32().readMDArray(floatDatasetName, block(2, 2).slice(boundIndex1Arr)));
        assertEquals(slice1BlockOfs01,
                reader.float32().readSlicedMDArrayBlockWithOffset(floatDatasetName, new int[]
                    { 2, 2 }, new long[]
                    { 0, 1 }, boundIndex1));
        assertEquals(slice1BlockOfs01,
                reader.float32().readMDArray(floatDatasetName, 
                        block(2, 2).offset(0, 1).slice(boundIndex1)));
        assertEquals(slice1BlockOfs01,
                reader.float32().readSlicedMDArrayBlockWithOffset(floatDatasetName, new int[]
                    { 2, 2 }, new long[]
                    { 0, 1 }, boundIndex1Arr));
        assertEquals(slice1BlockOfs01,
                reader.float32().readMDArray(floatDatasetName, 
                        block(2, 2).offset(0, 1).slice(boundIndex1Arr)));
        assertEquals(slice1BlockOfs10,
                reader.float32().readSlicedMDArrayBlockWithOffset(floatDatasetName, new int[]
                    { 2, 2 }, new long[]
                    { 1, 0 }, boundIndex1));
        assertEquals(slice1BlockOfs10,
                reader.float32().readMDArray(floatDatasetName, 
                        block(2, 2).offset(1, 0).slice(boundIndex1)));
        assertEquals(slice1BlockOfs10,
                reader.float32().readSlicedMDArrayBlockWithOffset(floatDatasetName, new int[]
                    { 2, 2 }, new long[]
                    { 1, 0 }, boundIndex1Arr));
        assertEquals(slice1BlockOfs10,
                reader.float32().readMDArray(floatDatasetName, 
                        block(2, 2).offset(1, 0).slice(boundIndex1Arr)));
        assertEquals(slice1BlockOfs11,
                reader.float32().readSlicedMDArrayBlockWithOffset(floatDatasetName, new int[]
                    { 2, 2 }, new long[]
                    { 1, 1 }, boundIndex1));
        assertEquals(slice1BlockOfs11,
                reader.float32().readMDArray(floatDatasetName, 
                        block(2, 2).offset(1, 1).slice(boundIndex1)));
        assertEquals(slice1BlockOfs11,
                reader.float32().readSlicedMDArrayBlockWithOffset(floatDatasetName, new int[]
                    { 2, 2 }, new long[]
                    { 1, 1 }, boundIndex1Arr));
        assertEquals(slice1BlockOfs11,
                reader.float32().readMDArray(floatDatasetName, 
                        block(2, 2).offset(1, 1).slice(boundIndex1Arr)));
        assertEquals(slice2, reader.float32().readMDArraySlice(floatDatasetName, boundIndex2));
        assertEquals(slice2, reader.float32().readMDArray(floatDatasetName, slice(boundIndex2)));
        assertEquals(slice2, reader.float32().readMDArraySlice(floatDatasetName, boundIndex2Arr));
        assertEquals(slice2, reader.float32().readMDArray(floatDatasetName, slice(boundIndex2Arr)));
        assertEquals(slice3, reader.float32().readMDArraySlice(floatDatasetName, boundIndex3));
        assertEquals(slice3, reader.float32().readMDArray(floatDatasetName, slice(boundIndex3)));
        assertEquals(slice3, reader.float32().readMDArraySlice(floatDatasetName, boundIndex3Arr));
        assertEquals(slice3, reader.float32().readMDArray(floatDatasetName, slice(boundIndex3Arr)));
        reader.close();
    }

    @Test
    public void testBooleanArray()
    {
        final File datasetFile = new File(workingDirectory, "booleanArray.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(datasetFile);
        final String booleanDatasetName = "/booleanArray";
        final String longArrayDataSetName = "/longArray";
        final BitSet arrayWritten = new BitSet();
        arrayWritten.set(32);
        writer.writeBitField(booleanDatasetName, arrayWritten);
        writer.int64().writeArray(longArrayDataSetName,
                BitSetConversionUtils.toStorageForm(arrayWritten));
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        final BitSet arrayRead = reader.readBitField(booleanDatasetName);
        try
        {
            reader.readBitField(longArrayDataSetName);
            fail("Failed to detect type mismatch.");
        } catch (HDF5DatatypeInterfaceException ex)
        {
            // Expected, as the types do not match.
        }
        assertEquals(arrayWritten, arrayRead);
        final HDF5DataSetInformation info = reader.getDataSetInformation(booleanDatasetName);
        assertEquals(HDF5DataClass.BITFIELD, info.getTypeInformation().getDataClass());
        assertChunkSizes(info, HDF5Utils.MIN_CHUNK_SIZE);
        reader.close();
    }

    @Test
    public void testBooleanArrayBlock()
    {
        final File datasetFile = new File(workingDirectory, "booleanArrayBlock.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(datasetFile);
        final String booleanDatasetName = "/booleanArray";
        final BitSet arrayWritten = new BitSet();
        writer.bool().createBitField(booleanDatasetName, 4L, 2);
        arrayWritten.set(32);
        arrayWritten.set(40);
        writer.bool().writeBitFieldBlock(booleanDatasetName, arrayWritten, 2, 0);
        arrayWritten.clear();
        arrayWritten.set(0);
        writer.bool().writeBitFieldBlock(booleanDatasetName, arrayWritten, 2, 1);
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        final BitSet arrayBlockRead = reader.bool().readBitFieldBlock(booleanDatasetName, 2, 1);
        assertEquals(1, arrayBlockRead.cardinality());
        assertTrue(arrayBlockRead.get(0));
        assertTrue(reader.bool().isBitSet(booleanDatasetName, 32));
        assertTrue(reader.bool().isBitSet(booleanDatasetName, 40));
        assertTrue(reader.bool().isBitSet(booleanDatasetName, 128));
        assertFalse(reader.bool().isBitSet(booleanDatasetName, 33));
        assertFalse(reader.bool().isBitSet(booleanDatasetName, 64));
        assertFalse(reader.bool().isBitSet(booleanDatasetName, 256));
        assertFalse(reader.bool().isBitSet(booleanDatasetName, 512));
        reader.close();
    }

    @Test
    public void testBitFieldArray()
    {
        final File datasetFile = new File(workingDirectory, "bitFieldArray.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(datasetFile);
        final String booleanDatasetName = "/bitFieldArray";
        final BitSet[] arrayWritten = new BitSet[]
            { new BitSet(), new BitSet(), new BitSet() };
        arrayWritten[0].set(32);
        arrayWritten[1].set(40);
        arrayWritten[2].set(17);
        writer.bool().writeBitFieldArray(booleanDatasetName, arrayWritten, INT_AUTO_SCALING);
        final String bigBooleanDatasetName = "/bigBitFieldArray";
        final BitSet[] bigAarrayWritten = new BitSet[]
            { new BitSet(), new BitSet(), new BitSet() };
        bigAarrayWritten[0].set(32);
        bigAarrayWritten[1].set(126);
        bigAarrayWritten[2].set(17);
        bigAarrayWritten[2].set(190);
        writer.bool().writeBitFieldArray(bigBooleanDatasetName, bigAarrayWritten, INT_AUTO_SCALING);
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        assertTrue(
                Arrays.toString(reader.object().getDataSetInformation(booleanDatasetName)
                        .getDimensions()),
                Arrays.equals(new long[]
                    { 1, 3 }, reader.object().getDataSetInformation(booleanDatasetName)
                        .getDimensions()));
        assertEquals(HDF5DataClass.BITFIELD,
                reader.object().getDataSetInformation(booleanDatasetName).getTypeInformation()
                        .getDataClass());
        final BitSet[] arrayRead = reader.bool().readBitFieldArray(booleanDatasetName);
        assertEquals(3, arrayRead.length);
        assertEquals(1, arrayRead[0].cardinality());
        assertTrue(arrayRead[0].get(32));
        assertEquals(1, arrayRead[1].cardinality());
        assertTrue(arrayRead[1].get(40));
        assertEquals(1, arrayRead[2].cardinality());
        assertTrue(arrayRead[2].get(17));

        assertEquals(HDF5DataClass.BITFIELD,
                reader.object().getDataSetInformation(bigBooleanDatasetName).getTypeInformation()
                        .getDataClass());
        final BitSet[] bigArrayRead = reader.bool().readBitFieldArray(bigBooleanDatasetName);
        assertEquals(3, arrayRead.length);
        assertEquals(1, bigArrayRead[0].cardinality());
        assertTrue(bigArrayRead[0].get(32));
        assertEquals(1, bigArrayRead[1].cardinality());
        assertTrue(bigArrayRead[1].get(126));
        assertEquals(2, bigArrayRead[2].cardinality());
        assertTrue(bigArrayRead[2].get(17));
        assertTrue(bigArrayRead[2].get(190));
        reader.close();
    }

    @Test
    public void testBitFieldArrayBlockWise()
    {
        final File datasetFile = new File(workingDirectory, "bitFieldArrayBlockWise.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(datasetFile);
        final String booleanDatasetName = "/bitFieldArray";
        final BitSet[] arrayWritten = new BitSet[]
            { new BitSet(), new BitSet(), new BitSet(), new BitSet() };
        arrayWritten[0].set(0);
        arrayWritten[1].set(1);
        arrayWritten[2].set(2);
        arrayWritten[3].set(3);
        final int count = 100;
        writer.bool().createBitFieldArray(booleanDatasetName, 4, count * arrayWritten.length,
                INT_AUTO_SCALING);
        for (int i = 0; i < count; ++i)
        {
            writer.bool().writeBitFieldArrayBlock(booleanDatasetName, arrayWritten, i);
        }
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        final HDF5DataSetInformation info =
                reader.object().getDataSetInformation(booleanDatasetName);
        assertEquals(HDF5DataClass.BITFIELD, info.getTypeInformation().getDataClass());
        assertEquals(2, info.getDimensions().length);
        assertEquals(1, info.getDimensions()[0]);
        assertEquals(count * arrayWritten.length, info.getDimensions()[1]);
        for (int i = 0; i < count; ++i)
        {
            assertTrue(
                    "Block " + i,
                    Arrays.equals(arrayWritten,
                            reader.bool().readBitFieldArrayBlock(booleanDatasetName, 4, i)));
        }
        reader.close();
    }

    private void assertChunkSizes(final HDF5DataSetInformation info,
            final long... expectedChunkSize)
    {
        assertEquals(HDF5StorageLayout.CHUNKED, info.getStorageLayout());
        final int[] chunkSize = info.tryGetChunkSizes();
        assertNotNull(chunkSize);
        assertEquals(expectedChunkSize.length, chunkSize.length);
        for (int i = 0; i < expectedChunkSize.length; ++i)
        {
            assertEquals(Integer.toString(i), expectedChunkSize[i], chunkSize[i]);
        }
    }

    @Test
    public void testMDFloatArrayBlockWise()
    {
        final File datasetFile = new File(workingDirectory, "mdArrayBlockWise.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(datasetFile);
        final String floatDatasetName = "/floatMatrix";
        final String floatDatasetName2 = "/floatMatrix2";
        final long[] shape = ldims(10, 10, 10);
        final int[] blockShape = dims(5, 5, 5);
        writer.float32().createMDArray(floatDatasetName, shape, blockShape);
        writer.float32().createMDArray(floatDatasetName2, shape, blockShape);
        final float[] flatArray = new float[MDArray.getLength(blockShape)];
        for (int i = 0; i < flatArray.length; ++i)
        {
            flatArray[i] = i;
        }
        final MDFloatArray arrayBlockWritten = new MDFloatArray(flatArray, blockShape);
        for (int i = 0; i < 2; ++i)
        {
            for (int j = 0; j < 2; ++j)
            {
                for (int k = 0; k < 2; ++k)
                {
                    final long[] blockIndex = new long[]
                        { i, j, k };
                    writer.float32().writeMDArrayBlock(floatDatasetName, arrayBlockWritten,
                            blockIndex);
                    try (HDF5DataSet ds = writer.object().openDataSet(floatDatasetName2))
                    {
                        writer.float32().writeMDArray(ds, arrayBlockWritten, blockIndex(blockIndex));
                    }
                }
            }
        }

        final MDFloatArray arraySliceWritten1 = new MDFloatArray(new float[]
            { 1000f, 2000f, 3000f, 4000f, 5000f }, dims(1, 5));
        final long[] slicedBlock1 = ldims(4, 1);
        final IndexMap imap1 = new IndexMap().bind(1, 7);
        writer.float32().writeSlicedMDArrayBlock(floatDatasetName2, arraySliceWritten1,
                slicedBlock1, imap1);

        final MDFloatArray arraySliceWritten2 = new MDFloatArray(new float[]
            { -1f, -2f, -3f, -4f, -5f, -6f }, dims(3, 2));
        final long[] slicedBlockOffs2 = ldims(2, 6);
        final IndexMap imap2 = new IndexMap().bind(0, 9);
        try (HDF5DataSet ds = writer.object().openDataSet(floatDatasetName2))
        {
            writer.float32().writeMDArray(ds, arraySliceWritten2, slice(imap2).offset(slicedBlockOffs2));
        }
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        for (int i = 0; i < 2; ++i)
        {
            for (int j = 0; j < 2; ++j)
            {
                for (int k = 0; k < 2; ++k)
                {
                    final long[] blockIndex = new long[]
                        { i, j, k };
                    final MDFloatArray arrayRead =
                            reader.float32().readMDArrayBlock(floatDatasetName, blockShape,
                                    blockIndex);
                    assertEquals(Arrays.toString(blockIndex), arrayBlockWritten, arrayRead);
                    final MDFloatArray arrayRead2 =
                            reader.float32().readMDArray(floatDatasetName, 
                                    block(blockShape).index(blockIndex));
                    assertEquals(Arrays.toString(blockIndex), arrayBlockWritten, arrayRead2);
                    // {0, 1, 1} is the first block we overwrote, { 1, 0, 1} the second block.
                    if (false == Arrays.equals(ldims(0, 1, 1), blockIndex) 
                            && false == Arrays.equals(ldims(1, 0, 1), blockIndex))
                    {
                        assertEquals(
                                Arrays.toString(blockIndex),
                                arrayBlockWritten,
                                reader.float32().readMDArrayBlock(floatDatasetName2, blockShape,
                                        blockIndex));
                        assertEquals(
                                Arrays.toString(blockIndex),
                                arrayBlockWritten,
                                reader.float32().readMDArray(floatDatasetName2, 
                                        block(blockShape).index(blockIndex)));
                    }
                }
            }
        }
        try (final HDF5DataSet fds2 = reader.object().openDataSet(floatDatasetName2))
        {
            final MDFloatArray arraySliceRead1 =
                    reader.float32().readSlicedMDArrayBlock(floatDatasetName2,
                            arraySliceWritten1.dimensions(), slicedBlock1, imap1);
            assertEquals(arraySliceWritten1, arraySliceRead1);
            final MDFloatArray arraySliceRead1b =
                    reader.float32().readSlicedMDArrayBlock(fds2,
                            arraySliceWritten1.dimensions(), slicedBlock1, imap1);
            assertEquals(arraySliceWritten1, arraySliceRead1b);
            final MDFloatArray arraySliceRead1c =
                    reader.float32().readMDArray(fds2, 
                            block(arraySliceWritten1.dimensions()).index(slicedBlock1).slice(imap1));
            assertEquals(arraySliceWritten1, arraySliceRead1c);
            final MDFloatArray arraySliceRead2 =
                    reader.float32().readSlicedMDArrayBlockWithOffset(floatDatasetName2,
                            arraySliceWritten2.dimensions(), slicedBlockOffs2, imap2);
            assertEquals(arraySliceWritten2, arraySliceRead2);
            final MDFloatArray arraySliceRead2b =
                    reader.float32().readSlicedMDArrayBlockWithOffset(fds2,
                            arraySliceWritten2.dimensions(), slicedBlockOffs2, imap2);
            assertEquals(arraySliceWritten2, arraySliceRead2b);
            final MDFloatArray arraySliceRead2c =
                    reader.float32().readMDArray(fds2,
                            block(arraySliceWritten2.dimensions()).offset(slicedBlockOffs2).slice(imap2));
            assertEquals(arraySliceWritten2, arraySliceRead2c);
        }
        reader.close();
    }

    @Test
    public void testMDFloatArraySliced()
    {
        final File datasetFile = new File(workingDirectory, "mdArraySliced.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(datasetFile);
        final String floatDatasetName = "/floatMatrix";
        final long[] shape = new long[]
            { 10, 10, 10 };
        final int[] sliceShape = new int[]
            { 10, 10 };
        final int[] sliceBlockShape = new int[]
            { 1, 10, 10 };
        writer.float32().createMDArray(floatDatasetName, shape, sliceBlockShape);
        final float[] baseArray = new float[MDArray.getLength(sliceShape)];
        for (int i = 0; i < baseArray.length; ++i)
        {
            baseArray[i] = i;
        }
        float[] floatArrayWritten = baseArray.clone();
        for (int i = 0; i < 10; ++i)
        {
            writer.float32().writeMDArraySlice(floatDatasetName,
                    new MDFloatArray(timesTwo(floatArrayWritten), sliceShape), new long[]
                        { i, -1, -1 });
        }
        writer.close();

        floatArrayWritten = baseArray.clone();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        for (int i = 0; i < 10; ++i)
        {
            final MDFloatArray arrayRead =
                    reader.float32().readMDArraySlice(floatDatasetName, new long[]
                        { i, -1, -1 });
            assertEquals(Integer.toString(i), new MDFloatArray(timesTwo(floatArrayWritten),
                    sliceShape), arrayRead);
        }
        reader.close();
    }

    @Test
    public void testMDFloatArraySlicedDetachedDataSet()
    {
        final File datasetFile = new File(workingDirectory, "mdArraySlicedDetachedDataSet.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(datasetFile);
        final String floatDatasetName = "/floatMatrix";
        final long[] shape = new long[]
            { 10, 10, 10 };
        final int[] sliceShape = new int[]
            { 10, 10 };
        final int[] sliceBlockShape = new int[]
            { 1, 10, 10 };
        final float[] baseArray = new float[MDArray.getLength(sliceShape)];
        float[] floatArrayWritten;
        try (HDF5DataSet ds = writer.float32().createMDArrayAndOpen(floatDatasetName, shape, sliceBlockShape))
        {
            for (int i = 0; i < baseArray.length; ++i)
            {
                baseArray[i] = i;
            }
            floatArrayWritten = baseArray.clone();
            for (int i = 0; i < 10; ++i)
            {
                writer.float32().writeMDArray(ds,
                        new MDFloatArray(timesTwo(floatArrayWritten), sliceShape), slice(i, -1, -1 ));
            }
        }
        writer.close();

        floatArrayWritten = baseArray.clone();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        try (HDF5DataSet ds = reader.object().openDataSet(floatDatasetName))
        {
            for (int i = 0; i < 10; ++i)
            {
                final MDFloatArray arrayRead =
                        reader.float32().readMDArraySlice(ds, new long[]
                            { i, -1, -1 });
                assertEquals(Integer.toString(i), new MDFloatArray(timesTwo(floatArrayWritten),
                        sliceShape), arrayRead);
            }
        }
        reader.close();
    }

    private static float[] timesTwo(float[] array)
    {
        for (int i = 0; i < array.length; ++i)
        {
            array[i] *= 2;
        }
        return array;
    }

    @Test
    public void testMDFloatArrayBlockWiseWithMemoryOffset()
    {
        final File datasetFile = new File(workingDirectory, "mdArrayBlockWiseWithMemoryOffset.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(datasetFile);
        final String floatDatasetName = "/floatMatrix";
        final long[] shape = new long[]
            { 10, 10 };
        try (HDF5DataSet fds = writer.float32().createMDArrayAndOpen(floatDatasetName, shape, MDArray.toInt(shape)))
        {
            final float[] flatArray = new float[MDArray.getLength(shape)];
            for (int i = 0; i < flatArray.length; ++i)
            {
                flatArray[i] = i;
            }
            final MDFloatArray arrayBlockWritten = new MDFloatArray(flatArray, shape);
            writer.float32().writeMDArrayBlockWithOffset(floatDatasetName, arrayBlockWritten, new int[]
                { 2, 2 }, new long[]
                { 0, 0 }, new int[]
                { 1, 3 });
            writer.float32().writeMDArrayBlockWithOffset(fds, arrayBlockWritten, new int[]
                { 2, 2 }, new long[]
                { 2, 2 }, new int[]
                { 5, 1 });
            writer.float32().writeMDArrayBlockWithOffset(fds, arrayBlockWritten, new int[]
                    { 2, 2 }, new long[]
                    { 4, 4 }, new int[]
                    { 2, 7 });
        }
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        final float[][] matrixRead = reader.float32().readMatrix(floatDatasetName);
        reader.close();
        assertEquals(13f, matrixRead[0][0]);
        assertEquals(14f, matrixRead[0][1]);
        assertEquals(23f, matrixRead[1][0]);
        assertEquals(24f, matrixRead[1][1]);
        assertEquals(51f, matrixRead[2][2]);
        assertEquals(52f, matrixRead[2][3]);
        assertEquals(61f, matrixRead[3][2]);
        assertEquals(62f, matrixRead[3][3]);
        assertEquals(27f, matrixRead[4][4]);
        assertEquals(28f, matrixRead[4][5]);
        assertEquals(37f, matrixRead[5][4]);
        assertEquals(38f, matrixRead[5][5]);
        for (int i = 0; i < 10; ++i)
        {
            for (int j = 0; j < 10; ++j)
            {
                if ((i < 2 && j < 2) || (i > 1 && i < 4 && j > 1 && j < 4) || (i > 3 && i < 6 && j > 3 && j < 6))
                {
                    continue;
                }
                assertEquals("(" + i + "," + j + "}", 0f, matrixRead[i][j]);
            }
        }
    }

    @Test
    public void testStringVariableLength()
    {
        final File datasetFile = new File(workingDirectory, "stringVariableLength.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(datasetFile);
        writer.string().writeVL("a", "");
        writer.string().writeVL("b", "\0");
        writer.string().writeVL("c", "\0ABC\0");
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        assertEquals("", reader.readString("a"));
        assertEquals("\0", reader.readString("b"));
        assertEquals("\0ABC\0", reader.readString("c"));
        reader.close();
    }

    @Test
    public void testFixedLengthStringArray()
    {
        final File datasetFile = new File(workingDirectory, "stringArray.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(datasetFile);
        final String[] s = new String[100];
        for (int i = 0; i < s.length; ++i)
        {
            s[i] = "a";
        }
        writer.string().writeArray("ds", s);
        writer.close();

        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        for (int i = 0; i < 100; ++i)
        {
            final String[] s2 = reader.string().readArray("ds");
            assertEquals(100, s2.length);
            for (int j = 0; j < s2.length; ++j)
            {
                assertEquals("a", s2[j]);
            }
        }
        reader.close();
    }
    
    private String repeatStr(String s, int count)
    {
        StringBuilder b = new StringBuilder();
        for (int i = 0; i < count; ++i)
        {
            b.append(s);
        }
        return b.toString();
    }
    
    @Test
    public void testVLStringCrash()
    {
        final File datasetFile = new File(workingDirectory, "testVLStringCrash.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        IHDF5Writer writer = HDF5Factory.open(datasetFile);

        List<String> memberNames = Arrays.asList("StringA", "StringB");
        List<String> typeValues = Arrays.asList("", "");

        HDF5CompoundMappingHints hints = new HDF5CompoundMappingHints();
        hints.setUseVariableLengthStrings(true);

        HDF5CompoundType<List<?>> hdf5CompoundType = writer.compound().getInferredType("RowData", memberNames, typeValues, hints);

        HDF5GenericStorageFeatures storageFeatures = (HDF5GenericStorageFeatures) HDF5GenericStorageFeatures.build()
            .chunkedStorageLayout()
            .features();

        writer.compound().createArray("SomeReport", hdf5CompoundType, 0L, 1, storageFeatures);

        int index = 0;
        Random random = new Random(12);

        for(int i = 0; i < 100; ++i) {
            int sizeA = random.nextInt(100);
            int sizeB = random.nextInt(100);

            // System.out.println("i = " + i + ".  sizeA = " + sizeA + ", sizeB = " + sizeB + ".");

            List<String> rowData = Arrays.asList(repeatStr("a", sizeA), repeatStr("a", sizeB));
            @SuppressWarnings("unchecked")
            List<String>[] dataSet = new List[1];
            dataSet[0] = rowData;

            writer.compound().writeArrayBlock("SomeReport", hdf5CompoundType, dataSet, index);
            ++index;
        }
        writer.close();
        
    }
    
    @Test
    public void testDataSets()
    {
        final File datasetFile = new File(workingDirectory, "datasets.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(datasetFile);
        final String floatDatasetName = "/Group1/floats";
        final float[] floatDataWritten = new float[]
            { 2.8f, 8.2f, -3.1f, 0.0f, 10000.0f };
        writer.float32().writeArray(floatDatasetName, floatDataWritten);
        final long[] longDataWritten = new long[]
            { 10, -1000000, 1, 0, 100000000000L };
        final String longDatasetName = "/Group2/longs";
        writer.int64().writeArray(longDatasetName, longDataWritten);
        final byte[] byteDataWritten = new byte[]
            { 0, -1, 1, -128, 127 };
        final String byteDatasetName = "/Group2/bytes";
        writer.int8().writeArray(byteDatasetName, byteDataWritten, INT_DEFLATE);
        final short[] shortDataWritten = new short[]
            { 0, -1, 1, -128, 127 };
        final String shortDatasetName = "/Group2/shorts";
        writer.int16().writeArray(shortDatasetName, shortDataWritten, INT_DEFLATE);
        final String intDatasetName1 = "/Group2/ints1";
        final int[] intDataWritten = new int[]
            { 0, 1, 2, 3, 4 };
        final String intDatasetName2 = "/Group2/ints2";
        writer.int32().writeArray(intDatasetName1, intDataWritten, INT_DEFLATE);
        writer.int32().writeArray(intDatasetName2, intDataWritten, INT_SHUFFLE_DEFLATE);
        writer.file().flush();
        final String stringDataWritten1 = "Some Random String";
        final String stringDataWritten2 = "Another Random String";
        final String stringDatasetName = "/Group3/strings";
        final String stringDatasetName2 = "/Group4/strings";
        writer.string().write(stringDatasetName, stringDataWritten1);
        writer.string().writeVL(stringDatasetName2, stringDataWritten1);
        writer.string().writeVL(stringDatasetName2, stringDataWritten2);
        final String stringDatasetName3 = "/Group4/stringArray";
        writer.string().writeArrayVL(stringDatasetName3, new String[]
            { stringDataWritten1, stringDataWritten2 });
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        final float[] floatDataRead = reader.float32().readArray(floatDatasetName);
        assertTrue(Arrays.equals(floatDataWritten, floatDataRead));
        assertEquals(1, reader.object().getRank(floatDatasetName));
        assertTrue(Arrays.equals(new long[]
            { floatDataWritten.length }, reader.object().getDimensions(floatDatasetName)));
        final long[] longDataRead = reader.int64().readArray(longDatasetName);
        assertTrue(Arrays.equals(longDataWritten, longDataRead));
        final byte[] byteDataRead = reader.int8().readArray(byteDatasetName);
        assertTrue(Arrays.equals(byteDataWritten, byteDataRead));
        final short[] shortDataRead = reader.int16().readArray(shortDatasetName);
        assertTrue(Arrays.equals(shortDataWritten, shortDataRead));
        final String stringDataRead1 = reader.string().read(stringDatasetName);
        final int[] intDataRead1 = reader.int32().readArray(intDatasetName1);
        assertTrue(Arrays.equals(intDataWritten, intDataRead1));
        final int[] intDataRead2 = reader.int32().readArray(intDatasetName2);
        assertTrue(Arrays.equals(intDataWritten, intDataRead2));
        assertEquals(stringDataWritten1, stringDataRead1);
        final String stringDataRead2 = reader.string().read(stringDatasetName2);
        assertEquals(stringDataWritten2, stringDataRead2);
        final String[] vlStringArrayRead = reader.string().readArray(stringDatasetName3);
        assertEquals(stringDataWritten1, vlStringArrayRead[0]);
        assertEquals(stringDataWritten2, vlStringArrayRead[1]);
        reader.close();
    }

    @Test
    public void testCopyDataSet()
    {
        final File datasetFile1 = new File(workingDirectory, "testCopyDataSet1.h5");
        datasetFile1.delete();
        assertFalse(datasetFile1.exists());
        datasetFile1.deleteOnExit();
        final File datasetFile2 = new File(workingDirectory, "testCopyDataSet2.h5");
        datasetFile2.delete();
        assertFalse(datasetFile2.exists());
        datasetFile2.deleteOnExit();
        final File datasetFile3 = new File(workingDirectory, "testCopyDataSet3.h5");
        datasetFile3.delete();
        assertFalse(datasetFile3.exists());
        datasetFile3.deleteOnExit();
        final IHDF5Writer writer1 = HDF5FactoryProvider.get().open(datasetFile1);
        final IHDF5Writer writer2 = HDF5FactoryProvider.get().open(datasetFile2);
        final IHDF5Writer writer3 = HDF5FactoryProvider.get().open(datasetFile3);
        final String floatDatasetName = "/Group1/floats";
        final float[] floatDataWritten = new float[]
                { 2.8f, 8.2f, -3.1f, 0.0f, 10000.0f };
        writer1.float32().writeArray(floatDatasetName, floatDataWritten);
        final long[] longDataWritten = new long[]
                { 10, -1000000, 1, 0, 100000000000L };
            final String longDatasetName = "/Group2/longs";
        writer1.int64().writeArray(longDatasetName, longDataWritten);
        final String stringDatasetName = "/Group1/Group3/strings";
        final String stringDataWritten1 = "Some Random String";
        writer1.string().write(stringDatasetName, stringDataWritten1);
        writer1.object().copy(floatDatasetName, writer2, floatDatasetName);
        writer1.object().copyAll(writer3);
        writer1.close();
        writer2.close();
        writer3.close();
        final IHDF5Reader reader2 = HDF5FactoryProvider.get().openForReading(datasetFile2);
        assertTrue(reader2.exists(floatDatasetName));
        assertTrue(Arrays.equals(new long[]
                { floatDataWritten.length }, reader2.object().getDimensions(floatDatasetName)));
        reader2.close();
        final IHDF5Reader reader3 = HDF5FactoryProvider.get().openForReading(datasetFile3);
        assertTrue(reader3.exists(floatDatasetName));
        assertTrue(Arrays.equals(floatDataWritten, reader3.float32().readArray(floatDatasetName)));
        assertTrue(reader3.exists(longDatasetName));
        assertTrue(Arrays.equals(longDataWritten, reader3.int64().readArray(longDatasetName)));
        assertEquals(stringDataWritten1, reader3.string().read(stringDatasetName));
        reader3.close();
    }
    
    @Test
    public void testScaleOffsetFilterInt()
    {
        final File datasetFile = new File(workingDirectory, "scaleoffsetfilterint.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(datasetFile);
        final int[] intWritten = new int[1000000];
        for (int i = 0; i < intWritten.length; ++i)
        {
            intWritten[i] = (i % 4);
        }
        writer.int32().writeArray("ds", intWritten, INT_AUTO_SCALING_DEFLATE);
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        final int[] intRead = reader.int32().readArray("ds");
        assertTrue(Arrays.equals(intRead, intWritten));
        reader.close();
    }

    @Test
    public void testScaleOffsetFilterFloat()
    {
        final File datasetFile = new File(workingDirectory, "scaleoffsetfilterfloat.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(datasetFile);
        final float[] floatWritten = new float[1000000];
        for (int i = 0; i < floatWritten.length; ++i)
        {
            floatWritten[i] = (i % 10) / 10f;
        }
        writer.float32().writeArray("ds", floatWritten, FLOAT_SCALING1_DEFLATE);
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        final float[] floatRead = reader.float32().readArray("ds");
        assertEquals(floatWritten.length, floatRead.length);
        for (int i = 0; i < floatWritten.length; ++i)
        {
            assertEquals(floatWritten[i], floatRead[i], 1.0e-7);
        }
        reader.close();
    }

    @Test
    public void testMaxPathLength()
    {
        final File datasetFile = new File(workingDirectory, "maxpathlength.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(datasetFile);
        final String madnessOverwhelmesUs1 = StringUtils.repeat("a", 16384);
        final String madnessOverwhelmesUs2 = StringUtils.repeat("/b", 8192);
        writer.int32().write(madnessOverwhelmesUs1, 17);
        writer.float32().write(madnessOverwhelmesUs2, 0.0f);
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        assertEquals(17, reader.int32().read(madnessOverwhelmesUs1));
        assertEquals(0.0f, reader.float32().read(madnessOverwhelmesUs2));
        reader.close();
    }

    @Test
    public void testExceedMaxPathLength()
    {
        final File datasetFile = new File(workingDirectory, "exceedmaxpathlength.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(datasetFile);
        final String madnessOverwhelmesUs = StringUtils.repeat("a", 16385);
        try
        {
            writer.int32().write(madnessOverwhelmesUs, 17);
            fail("path overflow not detected");
        } catch (HDF5JavaException ex)
        {
            assertEquals(0, ex.getMessage().indexOf("Path too long"));
        } finally
        {
            writer.close();
        }
    }

    @Test
    public void testAccessClosedReaderWriter()
    {
        final File datasetFile = new File(workingDirectory, "datasetsNonExtendable.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(datasetFile);
        writer.close();
        try
        {
            writer.writeBoolean("dataSet", true);
        } catch (HDF5JavaException ex)
        {
            assertEquals(String.format("HDF5 file '%s' is closed.", datasetFile.getAbsolutePath()),
                    ex.getMessage());
        }
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        reader.close();
        try
        {
            reader.readBoolean("dataSet");
        } catch (HDF5JavaException ex)
        {
            assertEquals(String.format("HDF5 file '%s' is closed.", datasetFile.getAbsolutePath()),
                    ex.getMessage());
        }
    }

    @Test
    public void testDataSetsNonExtendable()
    {
        final File datasetFile = new File(workingDirectory, "datasetsNonExtendable.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        final IHDF5Writer writer =
                HDF5FactoryProvider.get().configure(datasetFile).dontUseExtendableDataTypes()
                        .syncMode(SyncMode.SYNC_BLOCK).writer();
        final String floatDatasetName = "/Group1/floats";
        final float[] floatDataWritten = new float[]
            { 2.8f, 8.2f, -3.1f, 0.0f, 10000.0f };
        writer.float32().writeArray(floatDatasetName, floatDataWritten);
        final String compressedFloatDatasetName = "/Group1/floatsCompressed";
        writer.float32().writeArray(compressedFloatDatasetName, floatDataWritten, FLOAT_DEFLATE);
        final long[] longDataWritten = new long[]
            { 10, -1000000, 1, 0, 100000000000L };
        final String longDatasetName = "/Group2/longs";
        writer.int64().writeArray(longDatasetName, longDataWritten);
        final long[] longDataWrittenAboveCompactThreshold = new long[128];
        for (int i = 0; i < longDataWrittenAboveCompactThreshold.length; ++i)
        {
            longDataWrittenAboveCompactThreshold[i] = i;
        }
        final String longDatasetNameAboveCompactThreshold = "/Group2/longsContiguous";
        writer.int64().writeArray(longDatasetNameAboveCompactThreshold,
                longDataWrittenAboveCompactThreshold);
        final String longDatasetNameAboveCompactThresholdCompress = "/Group2/longsChunked";
        writer.int64().writeArray(longDatasetNameAboveCompactThresholdCompress,
                longDataWrittenAboveCompactThreshold, INT_DEFLATE);
        final byte[] byteDataWritten = new byte[]
            { 0, -1, 1, -128, 127 };
        final String byteDatasetName = "/Group2/bytes";
        writer.int8().writeArray(byteDatasetName, byteDataWritten, INT_DEFLATE);
        final String stringDataWritten = "Some Random String";
        final String stringDatasetName = "/Group3/strings";
        final String stringDatasetName2 = "/Group4/strings";
        writer.string().write(stringDatasetName, stringDataWritten);
        writer.string().writeVL(stringDatasetName2, stringDataWritten);
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        final float[] floatDataRead = reader.float32().readArray(floatDatasetName);
        HDF5DataSetInformation info = reader.getDataSetInformation(floatDatasetName);
        assertEquals(HDF5StorageLayout.COMPACT, info.getStorageLayout());
        assertNull(info.tryGetChunkSizes());
        assertTrue(info.isSigned());
        assertTrue(Arrays.equals(floatDataWritten, floatDataRead));
        final long[] compressedLongDataRead =
                reader.int64().readArray(longDatasetNameAboveCompactThresholdCompress);
        info = reader.getDataSetInformation(longDatasetNameAboveCompactThresholdCompress);
        assertChunkSizes(info, longDataWrittenAboveCompactThreshold.length);
        assertTrue(Arrays.equals(longDataWrittenAboveCompactThreshold, compressedLongDataRead));
        final long[] longDataRead = reader.int64().readArray(longDatasetName);
        info = reader.getDataSetInformation(longDatasetName);
        assertEquals(HDF5StorageLayout.COMPACT, info.getStorageLayout());
        assertNull(info.tryGetChunkSizes());
        assertTrue(Arrays.equals(longDataWritten, longDataRead));
        final long[] longDataReadAboveCompactThreshold =
                reader.int64().readArray(longDatasetNameAboveCompactThreshold);
        info = reader.getDataSetInformation(longDatasetNameAboveCompactThreshold);
        assertEquals(HDF5StorageLayout.CONTIGUOUS, info.getStorageLayout());
        assertNull(info.tryGetChunkSizes());
        assertTrue(Arrays.equals(longDataWrittenAboveCompactThreshold,
                longDataReadAboveCompactThreshold));
        final byte[] byteDataRead = reader.int8().readArray(byteDatasetName);
        assertTrue(Arrays.equals(byteDataWritten, byteDataRead));
        final String stringDataRead = reader.readString(stringDatasetName);
        assertEquals(stringDataWritten, stringDataRead);
        reader.close();
    }

    @Test
    public void testOverwriteContiguousDataSet()
    {
        // Test for a bug in 1.8.1 and 1.8.2 when overwriting contiguous data sets and thereby
        // changing its size.
        // We have some workaround code in IHDF5Writer.getDataSetId(), this is why this test runs
        // green. As new versions of HDF5 become available, one can try to comment out the
        // workaround code and see whether this test still runs red.
        final File datasetFile = new File(workingDirectory, "overwriteContiguousDataSet.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        final String dsName = "longArray";
        IHDF5Writer writer =
                HDF5FactoryProvider.get().configure(datasetFile).dontUseExtendableDataTypes()
                        .writer();
        // Creating the group is part of the "bug magic".
        writer.object().createGroup("group");
        final long[] arrayWritten1 = new long[1000];
        for (int i = 0; i < arrayWritten1.length; ++i)
        {
            arrayWritten1[i] = i;
        }
        writer.int64().writeArray(dsName, arrayWritten1);
        writer.close();
        writer = HDF5FactoryProvider.get().open(datasetFile);
        final long[] arrayWritten2 = new long[5];
        for (int i = 0; i < arrayWritten1.length; ++i)
        {
            arrayWritten1[i] = i * i;
        }
        writer.int64().writeArray(dsName, arrayWritten2);
        writer.close();
        IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        final long[] arrayRead = reader.int64().readArray(dsName);
        assertTrue(Arrays.equals(arrayWritten2, arrayRead));
        reader.close();
    }

    @Test
    public void testCompactDataset()
    {
        final File datasetFile = new File(workingDirectory, "compactDS.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        final String dsName = "ds";
        long[] data = new long[]
            { 1, 2, 3 };
        IHDF5Writer writer = HDF5FactoryProvider.get().open(datasetFile);
        writer.int64().writeArray(dsName, data, HDF5IntStorageFeatures.INT_COMPACT);
        assertEquals(HDF5StorageLayout.COMPACT, writer.getDataSetInformation(dsName)
                .getStorageLayout());
        writer.close();
        IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        assertTrue(Arrays.equals(data, reader.int64().readArray(dsName)));
        reader.close();
    }

    @Test
    public void testCreateEmptyFixedSizeDataSets()
    {
        final File datasetFile = new File(workingDirectory, "createEmptyFixedSizeDataSets.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        IHDF5Writer writer = HDF5Factory.open(datasetFile);
        writer.int64().createArray("longArr", 5, HDF5IntStorageFeatures.INT_COMPACT);
        writer.int64().createMDArray("longMDArr", new int[]
            { 5, 5 }, HDF5IntStorageFeatures.INT_COMPACT);
        writer.bool()
                .createBitFieldArray("bitfieldArr", 128, 5, HDF5IntStorageFeatures.INT_COMPACT);
        writer.enumeration().createArray("enumArr", writer.enumeration().getAnonType(new String[]
            { "a", "b", "c" }), 5, HDF5IntStorageFeatures.INT_COMPACT);
        writer.enumeration().createMDArray("enumMDArr",
                writer.enumeration().getAnonType(new String[]
                    { "a", "b", "c" }), new int[]
                    { 5, 5 }, HDF5IntStorageFeatures.INT_COMPACT);
        writer.close();
        IHDF5Reader reader = HDF5Factory.openForReading(datasetFile);
        HDF5DataSetInformation info = reader.getDataSetInformation("longArr");
        assertTrue(Arrays.equals(new long[]
            { 5 }, info.getDimensions()));
        assertNull(info.tryGetChunkSizes());
        assertEquals(HDF5StorageLayout.COMPACT, info.getStorageLayout());
        info = reader.getDataSetInformation("longMDArr");
        assertTrue(Arrays.equals(new long[]
            { 5, 5 }, info.getDimensions()));
        assertNull(info.tryGetChunkSizes());
        assertEquals(HDF5StorageLayout.COMPACT, info.getStorageLayout());
        info = reader.getDataSetInformation("enumArr");
        assertTrue(Arrays.equals(new long[]
            { 5 }, info.getDimensions()));
        assertNull(info.tryGetChunkSizes());
        assertEquals(HDF5StorageLayout.COMPACT, info.getStorageLayout());
        info = reader.getDataSetInformation("enumMDArr");
        assertTrue(Arrays.equals(new long[]
            { 5, 5 }, info.getDimensions()));
        assertNull(info.tryGetChunkSizes());
        assertEquals(HDF5StorageLayout.COMPACT, info.getStorageLayout());
        info = reader.getDataSetInformation("bitfieldArr");
        assertTrue(Arrays.equals(new long[]
            { 2, 5 }, info.getDimensions()));
        assertNull(info.tryGetChunkSizes());
        assertEquals(HDF5StorageLayout.COMPACT, info.getStorageLayout());
        reader.close();
    }

    @Test
    public void testCreateEmptyGrowableDataSets()
    {
        final File datasetFile = new File(workingDirectory, "createEmptyGrowableDataSets.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        IHDF5Writer writer = HDF5Factory.open(datasetFile);
        writer.int64().createArray("longArr", 5);
        writer.int64().createMDArray("longMDArr", new int[]
            { 5, 5 });
        writer.bool().createBitFieldArray("bitfieldArr", 128, 5);
        writer.enumeration().createArray("enumArr", writer.enumeration().getAnonType(new String[]
            { "a", "b", "c" }), 5);
        writer.enumeration().createMDArray("enumMDArr",
                writer.enumeration().getAnonType(new String[]
                    { "a", "b", "c" }), new int[]
                    { 5, 5 });
        writer.close();
        IHDF5Reader reader = HDF5Factory.openForReading(datasetFile);
        HDF5DataSetInformation info = reader.object().getDataSetInformation("longArr");
        assertTrue(Arrays.equals(new long[]
            { 5 }, info.getDimensions()));
        assertTrue(Arrays.equals(new int[]
            { 5 }, info.tryGetChunkSizes()));
        assertEquals(HDF5StorageLayout.CHUNKED, info.getStorageLayout());
        info = reader.object().getDataSetInformation("longMDArr");
        assertTrue(Arrays.equals(new long[]
            { 5, 5 }, info.getDimensions()));
        assertTrue(Arrays.equals(new int[]
            { 5, 5 }, info.tryGetChunkSizes()));
        assertEquals(HDF5StorageLayout.CHUNKED, info.getStorageLayout());
        info = reader.object().getDataSetInformation("enumArr");
        assertTrue(Arrays.equals(new long[]
            { 5 }, info.getDimensions()));
        assertTrue(Arrays.equals(new int[]
            { 5 }, info.tryGetChunkSizes()));
        assertEquals(HDF5StorageLayout.CHUNKED, info.getStorageLayout());
        info = reader.object().getDataSetInformation("enumMDArr");
        assertTrue(Arrays.equals(new long[]
            { 5, 5 }, info.getDimensions()));
        assertTrue(Arrays.equals(new int[]
            { 5, 5 }, info.tryGetChunkSizes()));
        assertEquals(HDF5StorageLayout.CHUNKED, info.getStorageLayout());
        info = reader.object().getDataSetInformation("bitfieldArr");
        assertTrue(Arrays.equals(new long[]
            { 2, 5 }, info.getDimensions()));
        assertTrue(Arrays.equals(new int[]
            { 2, 5 }, info.tryGetChunkSizes()));
        assertEquals(HDF5StorageLayout.CHUNKED, info.getStorageLayout());
        reader.close();
    }

    @Test
    public void testCreateZeroLengthGrowableDataSets()
    {
        final File datasetFile = new File(workingDirectory, "createZeroLengthGrowableDataSets.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        IHDF5Writer writer = HDF5Factory.open(datasetFile);
        writer.int64().createArray("longArr", 5, INT_CHUNKED);
        writer.int64().createMDArray("longMDArr", new int[]
            { 5, 5 }, INT_CHUNKED);
        writer.bool().createBitFieldArray("bitfieldArr", 128, 5, INT_CHUNKED);
        writer.enumeration().createArray("enumArr", writer.enumeration().getAnonType(new String[]
            { "a", "b", "c" }), 5, INT_CHUNKED);
        writer.enumeration().createMDArray("enumMDArr",
                writer.enumeration().getAnonType(new String[]
                    { "a", "b", "c" }), new int[]
                    { 5, 5 }, INT_CHUNKED);
        writer.close();
        IHDF5Reader reader = HDF5Factory.openForReading(datasetFile);
        HDF5DataSetInformation info = reader.object().getDataSetInformation("longArr");
        assertTrue(Arrays.equals(new long[]
            { 0 }, info.getDimensions()));
        assertTrue(Arrays.equals(new int[]
            { 5 }, info.tryGetChunkSizes()));
        assertEquals(HDF5StorageLayout.CHUNKED, info.getStorageLayout());
        info = reader.object().getDataSetInformation("longMDArr");
        assertTrue(Arrays.equals(new long[]
            { 0, 0 }, info.getDimensions()));
        assertTrue(Arrays.equals(new int[]
            { 5, 5 }, info.tryGetChunkSizes()));
        assertEquals(HDF5StorageLayout.CHUNKED, info.getStorageLayout());
        info = reader.object().getDataSetInformation("enumArr");
        assertTrue(Arrays.equals(new long[]
            { 0 }, info.getDimensions()));
        assertTrue(Arrays.equals(new int[]
            { 5 }, info.tryGetChunkSizes()));
        assertEquals(HDF5StorageLayout.CHUNKED, info.getStorageLayout());
        info = reader.object().getDataSetInformation("enumMDArr");
        assertTrue(Arrays.equals(new long[]
            { 0, 0 }, info.getDimensions()));
        assertTrue(Arrays.equals(new int[]
            { 5, 5 }, info.tryGetChunkSizes()));
        assertEquals(HDF5StorageLayout.CHUNKED, info.getStorageLayout());
        info = reader.object().getDataSetInformation("bitfieldArr");
        assertTrue(Arrays.equals(new long[]
            { 2, 0 }, info.getDimensions()));
        assertTrue(Arrays.equals(new int[]
            { 2, 5 }, info.tryGetChunkSizes()));
        assertEquals(HDF5StorageLayout.CHUNKED, info.getStorageLayout());
        reader.close();
    }

    @Test
    public void testCreateEmptyDefaultFixedSizeDataSets()
    {
        final File datasetFile =
                new File(workingDirectory, "createEmptyDefaultFixedSizeDataSets.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        IHDF5Writer writer =
                HDF5Factory.configure(datasetFile).dontUseExtendableDataTypes().writer();
        writer.int64().createArray("longArr", 5);
        writer.int64().createMDArray("longMDArr", new int[]
            { 5, 5 });
        writer.bool().createBitFieldArray("bitfieldArr", 128, 5);
        writer.enumeration().createArray("enumArr", writer.enumeration().getAnonType(new String[]
            { "a", "b", "c" }), 5);
        writer.close();
        IHDF5Reader reader = HDF5Factory.openForReading(datasetFile);
        HDF5DataSetInformation info = reader.getDataSetInformation("longArr");
        assertTrue(Arrays.equals(new long[]
            { 5 }, info.getDimensions()));
        assertEquals(HDF5StorageLayout.COMPACT, info.getStorageLayout());
        info = reader.getDataSetInformation("longMDArr");
        assertTrue(Arrays.equals(new long[]
            { 5, 5 }, info.getDimensions()));
        assertEquals(HDF5StorageLayout.COMPACT, info.getStorageLayout());
        info = reader.getDataSetInformation("enumArr");
        assertTrue(Arrays.equals(new long[]
            { 5 }, info.getDimensions()));
        assertEquals(HDF5StorageLayout.COMPACT, info.getStorageLayout());
        info = reader.getDataSetInformation("bitfieldArr");
        assertTrue(Arrays.equals(new long[]
            { 2, 5 }, info.getDimensions()));
        assertEquals(HDF5StorageLayout.COMPACT, info.getStorageLayout());
        reader.close();
    }

    @Test
    public void testExtendChunkedDataset()
    {
        final File datasetFile = new File(workingDirectory, "extendChunked.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        final String dsName = "ds";
        long[] data = new long[]
            { 1, 2, 3, 4 };
        IHDF5Writer writer = HDF5FactoryProvider.get().open(datasetFile);
        writer.int64().createArray(dsName, 5, 3);
        writer.int64().writeArray(dsName, data, HDF5IntStorageFeatures.INT_NO_COMPRESSION_KEEP);
        writer.close();
        IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        long[] dataRead = reader.int64().readArray(dsName);
        assertTrue(Arrays.equals(data, dataRead));
        reader.close();
        // Now write a larger data set and see whether the data set is correctly extended.
        writer = HDF5FactoryProvider.get().open(datasetFile);
        data = new long[]
            { 17, 42, 1, 2, 3, 101, -5 };
        writer.int64().writeArray(dsName, data);
        writer.close();
        reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        dataRead = reader.int64().readArray(dsName);
        assertTrue(Arrays.equals(data, dataRead));
        reader.close();
    }

    @Test
    public void testExtendContiguousDataset()
    {
        final File datasetFile = new File(workingDirectory, "extendContiguous.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        final String dsName = "ds";
        long[] longArrayWritten = new long[]
            { 1, 2, 3 };
        IHDF5Writer writer =
                HDF5FactoryProvider.get().configure(datasetFile).dontUseExtendableDataTypes()
                        .writer();
        // Set maxdims such that COMPACT_LAYOUT_THRESHOLD (int bytes!) is exceeded so that we get a
        // contiguous data set.
        writer.int64().createArray(dsName, 128, 1);
        writer.int64().writeArray(dsName, longArrayWritten);
        writer.close();
        IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        final long[] longArrayRead = reader.int64().readArray(dsName);
        assertTrue(Arrays.equals(longArrayWritten, longArrayRead));
        reader.close();
        // Now write a larger data set and see whether the data set is correctly extended.
        writer = HDF5FactoryProvider.get().open(datasetFile);
        longArrayWritten = new long[]
            { 17, 42, 1, 2, 3 };
        writer.int64().writeArray(dsName, longArrayWritten);
        writer.close();
        reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        assertTrue(Arrays.equals(longArrayWritten, reader.int64().readArray(dsName)));
        reader.close();
    }

    @Test
    public void testAutomaticDeletionOfDataSetOnWrite()
    {
        final File datasetFile = new File(workingDirectory, "automaticDeletionOfDataSetOnWrite.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        final IHDF5Writer writer = new HDF5WriterConfigurator(datasetFile).writer();
        writer.float32().createArray("f", 12, HDF5FloatStorageFeatures.FLOAT_COMPACT);
        writer.float32().writeArray("f", new float[]
            { 1f, 2f, 3f, 4f, 5f });
        writer.close();
        final IHDF5Reader reader = new HDF5ReaderConfigurator(datasetFile).reader();
        HDF5DataSetInformation info = reader.getDataSetInformation("f");
        assertEquals(HDF5StorageLayout.CHUNKED, info.getStorageLayout());
        assertEquals(5, info.tryGetChunkSizes()[0]);
        reader.close();
    }

    @Test
    public void testAutomaticDeletionOfDataSetOnCreate()
    {
        final File datasetFile =
                new File(workingDirectory, "automaticDeletionOfDataSetOnCreate.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        final IHDF5Writer writer = new HDF5WriterConfigurator(datasetFile).writer();
        writer.float32().createArray("f", 12, 6, HDF5FloatStorageFeatures.FLOAT_COMPACT);
        writer.float32().createArray("f", 10, HDF5FloatStorageFeatures.FLOAT_CONTIGUOUS);
        // This won't overwrite the data set as it is a block write command.
        writer.float32().writeArrayBlock("f", new float[]
            { 1f, 2f, 3f, 4f, 5f }, 0);
        writer.close();
        final IHDF5Reader reader = new HDF5ReaderConfigurator(datasetFile).reader();
        HDF5DataSetInformation info = reader.getDataSetInformation("f");
        assertEquals(HDF5StorageLayout.CONTIGUOUS, info.getStorageLayout());
        assertEquals(10, info.getDimensions()[0]);
        assertNull(info.tryGetChunkSizes());
        reader.close();
    }

    @Test
    public void testSpacesInDataSetName()
    {
        final File datasetFile = new File(workingDirectory, "datasetsWithSpaces.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(datasetFile);
        final String floatDatasetName = "Float Dataset";
        final float[] floatDataWritten = new float[]
            { 2.8f, 8.2f, -3.1f, 0.0f, 10000.0f };
        writer.float32().writeArray(floatDatasetName, floatDataWritten);
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        final float[] floatDataRead = reader.float32().readArray(floatDatasetName);
        assertTrue(Arrays.equals(floatDataWritten, floatDataRead));
        reader.close();
    }

    @Test
    public void testFloatArrayTypeDataSet()
    {
        final File datasetFile = new File(workingDirectory, "floatArrayTypeDataSet.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(datasetFile);
        final HDF5ArrayTypeFloatWriter efWriter = new HDF5ArrayTypeFloatWriter((HDF5Writer) writer);
        final float[] floatDataWritten = new float[]
            { 2.8f, 8.2f, -3.1f, 0.0f, 10000.0f };
        efWriter.writeFloatArrayArrayType("f", floatDataWritten);
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        assertEquals("FLOAT(4, #5):{}", reader.getDataSetInformation("f").toString());
        final float[] floatDataRead = reader.float32().readArray("f");
        assertTrue(Arrays.equals(floatDataWritten, floatDataRead));
        reader.close();
    }

    @Test
    public void testDoubleArrayAsByteArray()
    {
        final File datasetFile = new File(workingDirectory, "doubleArrayTypeDataSetAsByteArray.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(datasetFile);
        final HDF5ArrayTypeFloatWriter efWriter = new HDF5ArrayTypeFloatWriter((HDF5Writer) writer);
        writer.float64().createArray("f", 6, 3);
        final double[] floatDataWritten = new double[]
            { 2.8, 8.2, -3.1, 0.0, 10000.0 };
        efWriter.writeDoubleArrayBigEndian("f", floatDataWritten,
                HDF5FloatStorageFeatures.FLOAT_NO_COMPRESSION_KEEP);
        final double[] floatDataWritten2 = new double[]
            { 2.8, 8.2, -3.1, 0.0 };
        writer.float64().writeMDArray("f2", new MDDoubleArray(floatDataWritten2, new int[]
            { 2, 2 }));
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        assertEquals("FLOAT(8):{5}", reader.getDataSetInformation("f").toString());
        final byte[] byteDataRead = reader.readAsByteArray("f");
        final double[] floatDataRead = NativeData.byteToDouble(byteDataRead, ByteOrder.NATIVE);
        assertTrue(Arrays.equals(floatDataWritten, floatDataRead));
        final byte[] byteDataRead2 = reader.readAsByteArray("f2");
        final double[] floatDataRead2 = NativeData.byteToDouble(byteDataRead2, ByteOrder.NATIVE);
        assertTrue(Arrays.equals(floatDataWritten2, floatDataRead2));
        byte[] byteDataBlockRead = reader.opaque().readArrayBlock("f", 2, 1);
        assertEquals(16, byteDataBlockRead.length);
        assertEquals(floatDataWritten[2],
                NativeData.byteToDouble(byteDataBlockRead, ByteOrder.NATIVE, 0, 1)[0]);
        assertEquals(floatDataWritten[3],
                NativeData.byteToDouble(byteDataBlockRead, ByteOrder.NATIVE, 8, 1)[0]);

        byteDataBlockRead = reader.opaque().readArrayBlockWithOffset("f", 2, 1);
        assertEquals(16, byteDataBlockRead.length);
        assertEquals(floatDataWritten[1],
                NativeData.byteToDouble(byteDataBlockRead, ByteOrder.NATIVE, 0, 1)[0]);
        assertEquals(floatDataWritten[2],
                NativeData.byteToDouble(byteDataBlockRead, ByteOrder.NATIVE, 8, 1)[0]);
        final double[][] values =
            {
                { 2.8, 8.2, -3.1 },
                { 0.0, 10000.0 } };
        int i = 0;
        for (HDF5DataBlock<byte[]> block : reader.opaque().getArrayNaturalBlocks("f"))
        {
            assertEquals(i, block.getIndex());
            assertEquals(i * 3, block.getOffset());
            assertTrue(Arrays.equals(values[i],
                    NativeData.byteToDouble(block.getData(), ByteOrder.NATIVE)));
            ++i;
        }
        reader.close();
    }

    @Test
    public void testFloatArrayTypeDataSetOverwrite()
    {
        final File datasetFile = new File(workingDirectory, "floatArrayTypeDataSetOverwrite.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(datasetFile);
        final HDF5ArrayTypeFloatWriter efWriter = new HDF5ArrayTypeFloatWriter((HDF5Writer) writer);
        final float[] floatDataWritten = new float[]
            { 2.8f, 8.2f, -3.1f, 0.0f, 10000.0f };
        efWriter.writeFloatArrayArrayType("f", floatDataWritten);
        final float[] floatDataWritten2 = new float[]
            { 0.1f, 8.2f, -3.1f, 0.0f, 20000.0f };
        writer.float32().writeArray("f", floatDataWritten2);
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        assertEquals("FLOAT(4):{5}", reader.getDataSetInformation("f").toString());
        final float[] floatDataRead = reader.float32().readArray("f");
        assertTrue(Arrays.equals(floatDataWritten2, floatDataRead));
        reader.close();
    }

    @Test
    public void testFloatMDArrayTypeDataSet()
    {
        final File datasetFile = new File(workingDirectory, "floatMDArrayTypeDataSet.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(datasetFile);
        final HDF5ArrayTypeFloatWriter efWriter = new HDF5ArrayTypeFloatWriter((HDF5Writer) writer);
        final MDFloatArray floatDataWritten = new MDFloatArray(new float[]
            { 2.8f, 8.2f, -3.1f, -0.1f, 10000.0f, 1.111f }, new int[]
            { 3, 2 });
        efWriter.writeFloatArrayArrayType("fa", floatDataWritten);
        efWriter.writeFloat2DArrayArrayType1DSpace1d("fas", floatDataWritten);
        writer.float32().writeMDArray("f", floatDataWritten);
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        assertEquals("FLOAT(4, [3,2]):{}", reader.getDataSetInformation("fa").toString());
        final MDFloatArray floatDataReadFa = reader.float32().readMDArray("fa");
        assertEquals(floatDataWritten, floatDataReadFa);
        final MDFloatArray floatDataReadFas = reader.float32().readMDArray("fas");
        assertEquals(floatDataWritten, floatDataReadFas);
        final MDFloatArray floatBlock = new MDFloatArray(new float[]
            { -3.1f, -0.1f }, new int[]
            { 1, 2 });
        assertEquals(floatBlock, reader.float32().readMDArrayBlock("f", new int[]
            { 1, -1 }, new long[]
            { 1, 0 }));
        assertEquals(floatBlock, reader.float32().readMDArrayBlock("fas", new int[]
            { 1, -1 }, new long[]
            { 1, 0 }));
        try
        {
            reader.float32().readMDArrayBlock("fa", new int[]
                { 1, -1 }, new long[]
                { 1, 0 });
            fail("Illegal block-wise reading of array-type not detected.");
        } catch (HDF5JavaException ex)
        {
            // Expected
        }
        assertEquals(2, reader.object().getRank("f"));
        assertTrue(Arrays.equals(new long[]
            { 3, 2 }, reader.object().getDimensions("f")));
        assertEquals(2, reader.object().getRank("fa"));
        assertTrue(Arrays.equals(new long[]
            { 3, 2 }, reader.object().getDimensions("fa")));
        assertEquals(2, reader.object().getRank("fas"));
        assertTrue(Arrays.equals(new long[]
            { 3, 2 }, reader.object().getDimensions("fas")));
        reader.close();
    }

    @Test
    public void testFloatArrayCreateCompactOverwriteBlock()
    {
        final File datasetFile =
                new File(workingDirectory, "testFloatArrayCreateCompactOverwroteBlock.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(datasetFile);
        writer.float32().writeArray("f", new float[]
            { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }, HDF5FloatStorageFeatures.FLOAT_COMPACT);
        writer.float32().writeArrayBlockWithOffset("f", new float[]
            { 400, 500, 600 }, 3, 3);
        float[] arrayWritten = new float[]
            { 1, 2, 3, 400, 500, 600, 7, 8, 9, 10 };
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        assertTrue(Arrays.equals(arrayWritten, reader.float32().readArray("f")));
        reader.close();
    }

    @Test
    public void testReadFloatMatrixDataSetBlockWise()
    {
        final File datasetFile = new File(workingDirectory, "readFloatMatrixBlockWise.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(datasetFile);
        final String dsName = "ds";
        final float[][] floatMatrix = new float[10][10];
        for (int i = 0; i < floatMatrix.length; ++i)
        {
            for (int j = 0; j < floatMatrix[i].length; ++j)
            {
                floatMatrix[i][j] = i * j;
            }
        }
        writer.float32().writeMatrix(dsName, floatMatrix);
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        final int blockSize = 5;
        for (int i = 0; i < 2; ++i)
        {
            for (int j = 0; j < 2; ++j)
            {
                final float[][] floatMatrixBlockRead =
                        reader.float32().readMatrixBlock(dsName, blockSize, blockSize, i, j);
                assertEquals(blockSize, floatMatrixBlockRead.length);
                assertEquals(blockSize, floatMatrixBlockRead[0].length);
                final float[][] floatMatrixBlockExpected = new float[blockSize][];
                for (int k = 0; k < blockSize; ++k)
                {
                    final float[] rowExpected = new float[blockSize];
                    System.arraycopy(floatMatrix[i * blockSize + k], blockSize * j, rowExpected, 0,
                            blockSize);
                    floatMatrixBlockExpected[k] = rowExpected;
                }
                assertMatrixEquals(floatMatrixBlockExpected, floatMatrixBlockRead);
            }
        }
        reader.close();
    }

    @Test
    public void testMDIntArrayDifferentSizesElementType()
    {
        final File datasetFile =
                new File(workingDirectory, "testMDIntArrayDifferentSizesElementType.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(datasetFile);
        final MDIntArray arr = new MDIntArray(new int[]
            { 2, 2 });
        arr.set(1, 0, 0);
        arr.set(2, 0, 1);
        arr.set(3, 1, 0);
        arr.set(4, 1, 1);
        arr.incNumberOfHyperRows(1);
        arr.set(5, 2, 0);
        arr.set(6, 2, 1);
        writer.int16().createMDArray("array", new int[]
            { 3, 2 });
        writer.int32().writeMDArrayBlock("array", arr, new long[]
            { 0, 0 });
        writer.close();

        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        assertEquals(arr, reader.int32().readMDArray("array"));
        reader.close();
    }

    @Test
    public void testMDIntArrayDifferentSizesElementTypeUnsignedByte()
    {
        final File datasetFile =
                new File(workingDirectory, "testMDIntArrayDifferentSizesElementTypeUnsignedByte.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(datasetFile);
        final MDIntArray arr = new MDIntArray(new int[]
            { 2, 2 });
        arr.set(1, 0, 0);
        arr.set(2, 0, 1);
        arr.set(3, 1, 0);
        arr.set(4, 1, 1);
        arr.incNumberOfHyperRows(1);
        arr.set(5, 2, 0);
        arr.set(255, 2, 1);
        writer.uint8().createMDArray("array", new int[]
            { 3, 2 });
        writer.int32().writeMDArrayBlock("array", arr, new long[]
            { 0, 0 });
        writer.close();

        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        assertEquals(arr, reader.int32().readMDArray("array"));
        reader.close();
    }

    @Test
    public void testSetExtentBug()
    {
        final File datasetFile = new File(workingDirectory, "setExtentBug.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(datasetFile);
        final String dsName = "ds";
        final float[][] floatMatrixBlockWritten = new float[][]
            {
                { 1, 2 },
                { 3, 4 } };
        final int blockSize = 2;
        writer.float32().createMatrix(dsName, 0, 0, blockSize, blockSize);
        writer.float32().writeMatrixBlock(dsName, floatMatrixBlockWritten, 0, 0);
        writer.float32().writeMatrixBlock(dsName, floatMatrixBlockWritten, 0, 1);
        // The next line will make the the block (0,1) disappear if the bug is present.
        writer.float32().writeMatrixBlock(dsName, floatMatrixBlockWritten, 1, 0);
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        final float[][] floatMatrixBlockRead =
                reader.float32().readMatrixBlock(dsName, blockSize, blockSize, 0, 1);
        assertMatrixEquals(floatMatrixBlockWritten, floatMatrixBlockRead);
        reader.close();
    }

    @Test
    public void testWriteFloatMatrixDataSetBlockWise()
    {
        final File datasetFile = new File(workingDirectory, "writeFloatMatrixBlockWise.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(datasetFile);
        final String dsName = "ds";
        final float[][] floatMatrixBlockWritten = new float[5][5];
        for (int i = 0; i < floatMatrixBlockWritten.length; ++i)
        {
            for (int j = 0; j < floatMatrixBlockWritten[i].length; ++j)
            {
                floatMatrixBlockWritten[i][j] = i * j;
            }
        }
        final int blockSize = 5;
        writer.float32().createMatrix(dsName, 2 * blockSize, 2 * blockSize, blockSize, blockSize);
        for (int i = 0; i < 2; ++i)
        {
            for (int j = 0; j < 2; ++j)
            {
                writer.float32().writeMatrixBlock(dsName, floatMatrixBlockWritten, i, j);
            }
        }
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        for (int i = 0; i < 2; ++i)
        {
            for (int j = 0; j < 2; ++j)
            {
                final float[][] floatMatrixBlockRead =
                        reader.float32().readMatrixBlock(dsName, blockSize, blockSize, i, j);
                assertMatrixEquals(floatMatrixBlockWritten, floatMatrixBlockRead);
            }
        }
        reader.close();
    }

    @Test
    public void testWriteFloatMatrixDataSetBlockWiseWithOffset()
    {
        final File datasetFile =
                new File(workingDirectory, "writeFloatMatrixBlockWiseWithOffset.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(datasetFile);
        final String dsName = "ds";
        final float[][] floatMatrixBlockWritten = new float[5][5];
        int count = 0;
        for (int i = 0; i < floatMatrixBlockWritten.length; ++i)
        {
            for (int j = 0; j < floatMatrixBlockWritten[i].length; ++j)
            {
                floatMatrixBlockWritten[i][j] = ++count;
            }
        }
        final int blockSize = 5;
        final int offsetX = 2;
        final int offsetY = 3;
        writer.float32().createMatrix(dsName, 2 * blockSize, 2 * blockSize, blockSize, blockSize);
        writer.float32().writeMatrixBlockWithOffset(dsName, floatMatrixBlockWritten, 5, 5, offsetX,
                offsetY);
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        final float[][] floatMatrixBlockRead =
                reader.float32().readMatrixBlockWithOffset(dsName, blockSize, blockSize, offsetX,
                        offsetY);
        assertMatrixEquals(floatMatrixBlockWritten, floatMatrixBlockRead);
        final float[][] floatMatrixRead = reader.float32().readMatrix(dsName);
        // Subtract the non-zero block.
        for (int i = 0; i < floatMatrixBlockWritten.length; ++i)
        {
            for (int j = 0; j < floatMatrixBlockWritten[i].length; ++j)
            {
                floatMatrixRead[offsetX + i][offsetY + j] -= floatMatrixBlockWritten[i][j];
            }
        }
        for (int i = 0; i < floatMatrixRead.length; ++i)
        {
            for (int j = 0; j < floatMatrixRead[i].length; ++j)
            {
                assertEquals(i + ":" + j, 0.0f, floatMatrixRead[i][j]);
            }
        }
        reader.close();
    }

    @Test
    public void testReadMDFloatArrayAsByteArray()
    {
        final File datasetFile = new File(workingDirectory, "readMDFloatArrayAsByteArray.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(datasetFile);
        writer.float32().writeMatrix("fm", new float[][]
            {
                { 1f, 2f },
                { 3f, 4f } });
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        final byte[] arr = reader.readAsByteArray("fm");
        assertEquals(1f, NativeData.byteToFloat(arr, ByteOrder.NATIVE, 0, 1)[0]);
        assertEquals(2f, NativeData.byteToFloat(arr, ByteOrder.NATIVE, 4, 1)[0]);
        assertEquals(3f, NativeData.byteToFloat(arr, ByteOrder.NATIVE, 8, 1)[0]);
        assertEquals(4f, NativeData.byteToFloat(arr, ByteOrder.NATIVE, 12, 1)[0]);
        try
        {
            reader.opaque().readArrayBlock("fm", 2, 0);
            fail("readAsByteArrayBlock() is expected to fail on datasets of rank > 1");
        } catch (HDF5JavaException ex)
        {
            assertEquals("Data Set is expected to be of rank 1 (rank=2)", ex.getMessage());
        }
        reader.close();
    }

    @Test
    public void testReadByteArrayDataSetBlockWise()
    {
        final File datasetFile = new File(workingDirectory, "readByteArrayBlockWise.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        assertEquals(0, HDF5Factory.getOpenHDF5FileCount());
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(datasetFile);
        assertEquals(1, HDF5Factory.getOpenHDF5FileCount());
        final String dsName = "ds";
        final byte[] byteArray = new byte[100];
        for (int i = 0; i < byteArray.length; ++i)
        {
            byteArray[i] = (byte) (100 + i);
        }
        writer.int8().writeArray(dsName, byteArray);
        writer.close();
        assertEquals(0, HDF5Factory.getOpenHDF5FileCount());
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        assertEquals(1, HDF5Factory.getOpenHDF5FileCount());
        final int blockSize = 10;
        for (int i = 0; i < 10; ++i)
        {
            final byte[] byteArrayBlockRead = reader.int8().readArrayBlock(dsName, blockSize, i);
            assertEquals(blockSize, byteArrayBlockRead.length);
            final byte[] byteArrayBlockExpected = new byte[blockSize];
            System.arraycopy(byteArray, blockSize * i, byteArrayBlockExpected, 0, blockSize);
            assertTrue("Block " + i, Arrays.equals(byteArrayBlockExpected, byteArrayBlockRead));
        }
        reader.close();
        assertEquals(0, HDF5Factory.getOpenHDF5FileCount());
    }

    @Test
    public void testWriteByteArrayDataSetBlockWise()
    {
        final File datasetFile = new File(workingDirectory, "writeByteArrayBlockWise.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(datasetFile);
        final String dsName = "ds";
        final int size = 100;
        final int blockSize = 10;
        final int numberOfBlocks = 10;
        writer.int8().createArray(dsName, size, blockSize, INT_DEFLATE);
        final byte[] block = new byte[blockSize];
        for (int i = 0; i < numberOfBlocks; ++i)
        {
            Arrays.fill(block, (byte) i);
            writer.int8().writeArrayBlock(dsName, block, i);
        }
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        final byte[] byteArrayRead = reader.readAsByteArray(dsName);
        reader.close();
        assertEquals(size, byteArrayRead.length);
        for (int i = 0; i < byteArrayRead.length; ++i)
        {
            assertEquals("Byte " + i, (i / blockSize), byteArrayRead[i]);
        }
    }

    @Test
    public void testCreateByteArrayDataSetBlockSize0()
    {
        final File datasetFile = new File(workingDirectory, "testCreateByteArrayDataSetBlockSize0");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(datasetFile);
        final String dsName = "ds";
        final int size = 100;
        final int blockSize = 10;
        final int numberOfBlocks = 10;
        final int nominalBlockSize = 0;
        writer.int8().createArray(dsName, size, nominalBlockSize, INT_DEFLATE);
        final byte[] block = new byte[blockSize];
        for (int i = 0; i < numberOfBlocks; ++i)
        {
            Arrays.fill(block, (byte) i);
            writer.int8().writeArrayBlock(dsName, block, i);
        }
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        final byte[] byteArrayRead = reader.readAsByteArray(dsName);
        reader.close();
        assertEquals(size, byteArrayRead.length);
        for (int i = 0; i < byteArrayRead.length; ++i)
        {
            assertEquals("Byte " + i, (i / blockSize), byteArrayRead[i]);
        }
    }

    @Test
    public void testCreateFloatArrayWithDifferentStorageLayouts()
    {
        final File datasetFile =
                new File(workingDirectory, "testCreateFloatArrayWithDifferentStorageLayouts");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(datasetFile);
        final String dsName1 = "ds1";
        final String dsName2 = "ds2";
        final int size = 100;
        writer.float32().createArray(dsName1, size, HDF5FloatStorageFeatures.FLOAT_CONTIGUOUS);
        writer.float32().createArray(dsName2, size, HDF5FloatStorageFeatures.FLOAT_CHUNKED);
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        final HDF5DataSetInformation info1 = reader.getDataSetInformation(dsName1);
        final HDF5DataSetInformation info2 = reader.getDataSetInformation(dsName2);
        reader.close();
        assertEquals(HDF5StorageLayout.CONTIGUOUS, info1.getStorageLayout());
        assertEquals(size, info1.getDimensions()[0]);
        assertNull(info1.tryGetChunkSizes());
        assertEquals(HDF5StorageLayout.CHUNKED, info2.getStorageLayout());
        assertEquals(0, info2.getDimensions()[0]);
        assertEquals(size, info2.tryGetChunkSizes()[0]);
    }

    @Test
    public void testWriteByteArrayDataSetBlockWiseExtend()
    {
        final File datasetFile = new File(workingDirectory, "writeByteArrayBlockWiseExtend.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(datasetFile);
        final String dsName = "ds";
        final int size = 100;
        final int blockSize = 10;
        final int numberOfBlocks = 10;
        writer.int8().createArray(dsName, 0, blockSize, INT_DEFLATE);
        final byte[] block = new byte[blockSize];
        for (int i = 0; i < numberOfBlocks; ++i)
        {
            Arrays.fill(block, (byte) i);
            writer.int8().writeArrayBlock(dsName, block, i);
        }
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        final byte[] byteArrayRead = reader.readAsByteArray(dsName);
        reader.close();
        assertEquals(size, byteArrayRead.length);
        for (int i = 0; i < byteArrayRead.length; ++i)
        {
            assertEquals("Byte " + i, (i / blockSize), byteArrayRead[i]);
        }
    }

    @Test
    public void testWriteByteArrayDataSetBlockWiseMismatch()
    {
        final File datasetFile = new File(workingDirectory, "writeByteArrayBlockWise.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(datasetFile);
        final String dsName = "ds";
        final int size = 99;
        final int blockSize = 10;
        final int numberOfBlocks = 10;
        writer.int8().createArray(dsName, size, blockSize, INT_DEFLATE);
        final byte[] block = new byte[blockSize];
        for (int i = 0; i < numberOfBlocks; ++i)
        {
            Arrays.fill(block, (byte) i);
            if (blockSize * (i + 1) > size)
            {
                final int ofs = blockSize * i;
                writer.int8().writeArrayBlockWithOffset(dsName, block, size - ofs, ofs);
            } else
            {
                writer.int8().writeArrayBlock(dsName, block, i);
            }
        }
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        final byte[] byteArrayRead = reader.int8().readArray(dsName);
        reader.close();
        assertEquals(size, byteArrayRead.length);
        for (int i = 0; i < byteArrayRead.length; ++i)
        {
            assertEquals("Byte " + i, (i / blockSize), byteArrayRead[i]);
        }
    }

    @Test
    public void testWriteOpaqueByteArrayDataSetBlockWise()
    {
        final File datasetFile = new File(workingDirectory, "writeOpaqueByteArrayBlockWise.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(datasetFile);
        final String dsName = "ds";
        final int size = 100;
        final int blockSize = 10;
        final int numberOfBlocks = 10;
        final HDF5OpaqueType opaqueDataType =
                writer.opaque()
                        .createArray(dsName, "TAG", size / 2, blockSize, GENERIC_DEFLATE_MAX);
        final byte[] block = new byte[blockSize];
        for (int i = 0; i < numberOfBlocks; ++i)
        {
            Arrays.fill(block, (byte) i);
            writer.opaque().writeArrayBlock(dsName, opaqueDataType, block, i);
        }
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        final byte[] byteArrayRead = reader.readAsByteArray(dsName);
        reader.close();
        assertEquals(size, byteArrayRead.length);
        for (int i = 0; i < byteArrayRead.length; ++i)
        {
            assertEquals("Byte " + i, (i / blockSize), byteArrayRead[i]);
        }
    }

    @Test
    public void testWriteOpaqueByteArrayDataSetBlockWiseMismatch()
    {
        final File datasetFile = new File(workingDirectory, "writeOpaqueByteArrayBlockWise.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(datasetFile);
        final String dsName = "ds";
        final int size = 99;
        final int blockSize = 10;
        final int numberOfBlocks = 10;
        final HDF5OpaqueType opaqueDataType =
                writer.opaque().createArray(dsName, "TAG", size, blockSize, GENERIC_DEFLATE);
        final byte[] block = new byte[blockSize];
        for (int i = 0; i < numberOfBlocks; ++i)
        {
            Arrays.fill(block, (byte) i);
            if (blockSize * (i + 1) > size)
            {
                final int ofs = blockSize * i;
                writer.opaque().writeArrayBlockWithOffset(dsName, opaqueDataType, block,
                        size - ofs, ofs);
            } else
            {
                writer.opaque().writeArrayBlock(dsName, opaqueDataType, block, i);
            }
        }
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        final byte[] byteArrayRead = reader.readAsByteArray(dsName);
        reader.close();
        assertEquals(size, byteArrayRead.length);
        for (int i = 0; i < byteArrayRead.length; ++i)
        {
            assertEquals("Byte " + i, (i / blockSize), byteArrayRead[i]);
        }
    }

    @Test
    public void testWriteByteMatrixDataSetBlockWise()
    {
        final File datasetFile = new File(workingDirectory, "writeByteMatrixBlockWise.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(datasetFile);
        final String dsName = "ds";
        final int sizeX = 100;
        final int sizeY = 10;
        final int blockSizeX = 10;
        final int blockSizeY = 5;
        final int numberOfBlocksX = 10;
        final int numberOfBlocksY = 2;
        writer.int8().createMatrix(dsName, sizeX, sizeY, blockSizeX, blockSizeY, INT_DEFLATE);
        final byte[][] block = new byte[blockSizeX][blockSizeY];
        for (int i = 0; i < numberOfBlocksX; ++i)
        {
            for (int j = 0; j < numberOfBlocksY; ++j)
            {
                for (int k = 0; k < blockSizeX; ++k)
                {
                    Arrays.fill(block[k], (byte) (i + j));
                }
                writer.int8().writeMatrixBlock(dsName, block, i, j);
            }
        }
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        final byte[][] byteMatrixRead = reader.int8().readMatrix(dsName);
        reader.close();
        assertEquals(sizeX, byteMatrixRead.length);
        for (int i = 0; i < byteMatrixRead.length; ++i)
        {
            for (int j = 0; j < byteMatrixRead[i].length; ++j)
            {
                assertEquals("Byte (" + i + "," + j + ")", (i / blockSizeX + j / blockSizeY),
                        byteMatrixRead[i][j]);
            }
        }
    }

    @Test
    public void testWriteByteMatrixDataSetBlockWiseMismatch()
    {
        final File datasetFile = new File(workingDirectory, "writeByteMatrixBlockWiseMismatch.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(datasetFile);
        final String dsName = "ds";
        final int sizeX = 99;
        final int sizeY = 12;
        final int blockSizeX = 10;
        final int blockSizeY = 5;
        final int numberOfBlocksX = 10;
        final int numberOfBlocksY = 3;
        writer.int8().createMatrix(dsName, sizeX, sizeY, blockSizeX, blockSizeY, INT_DEFLATE);
        final byte[][] block = new byte[blockSizeX][blockSizeY];
        for (int i = 0; i < numberOfBlocksX; ++i)
        {
            for (int j = 0; j < numberOfBlocksY; ++j)
            {
                for (int k = 0; k < blockSizeX; ++k)
                {
                    Arrays.fill(block[k], (byte) (i + j));
                }
                writer.int8().writeMatrixBlockWithOffset(dsName, block,
                        Math.min(blockSizeX, sizeX - i * blockSizeX),
                        Math.min(blockSizeY, sizeY - j * blockSizeY), i * blockSizeX,
                        j * blockSizeY);
            }
        }
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        final byte[][] byteMatrixRead = reader.int8().readMatrix(dsName);
        reader.close();
        assertEquals(sizeX, byteMatrixRead.length);
        for (int i = 0; i < byteMatrixRead.length; ++i)
        {
            for (int j = 0; j < byteMatrixRead[i].length; ++j)
            {
                assertEquals("Byte (" + i + "," + j + ")", (i / blockSizeX + j / blockSizeY),
                        byteMatrixRead[i][j]);
            }
        }
        HDF5Factory.garbageCollect();
    }

    @Test
    public void testReadToFloatMDArray()
    {
        final File datasetFile = new File(workingDirectory, "readToFloatMDArray.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(datasetFile);
        final String dsName = "ds";
        final MDFloatArray arrayWritten = new MDFloatArray(new float[]
            { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, new int[]
            { 3, 3 });
        writer.float32().writeMDArray(dsName, arrayWritten);
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        final MDFloatArray arrayRead = new MDFloatArray(new int[]
            { 10, 10 });
        final MDFloatArray arrayRead2 = new MDFloatArray(arrayRead.dimensions());
        final int memOfsX = 2;
        final int memOfsY = 3;
        reader.float32().readToMDArrayWithOffset(dsName, arrayRead, new int[]
            { memOfsX, memOfsY });
        try (HDF5DataSet ds = reader.object().openDataSet(dsName))
        {
            reader.float32().readToMDArrayWithOffset(ds, arrayRead2, new int[]
                    { memOfsX, memOfsY });
        }
        reader.close();
        final boolean[][] isSet = new boolean[10][10];
        for (int i = 0; i < arrayWritten.size(0); ++i)
        {
            for (int j = 0; j < arrayWritten.size(1); ++j)
            {
                isSet[memOfsX + i][memOfsY + j] = true;
                assertEquals("(" + i + "," + j + ")", arrayWritten.get(i, j),
                        arrayRead.get(memOfsX + i, memOfsY + j));
                assertEquals("(" + i + "," + j + ")", arrayWritten.get(i, j),
                        arrayRead2.get(memOfsX + i, memOfsY + j));
            }
        }
        for (int i = 0; i < arrayRead.size(0); ++i)
        {
            for (int j = 0; j < arrayRead.size(1); ++j)
            {
                if (isSet[i][j] == false)
                {
                    assertEquals("(" + i + "," + j + ")", 0f, arrayRead.get(i, j));
                    assertEquals("(" + i + "," + j + ")", 0f, arrayRead2.get(i, j));
                }
            }
        }
    }

    @DataProvider
    private Object[][] provideSizes()
    {
        return new Object[][]
            {
                { 10, 99 },
                { 10, 100 },
                { 10, 101 } };
    }

    @Test(dataProvider = "provideSizes")
    public void testIterateOverFloatArrayInNaturalBlocks(int blockSize, int dataSetSize)
    {
        final File datasetFile =
                new File(workingDirectory, "iterateOverFloatArrayInNaturalBlocks.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(datasetFile);
        final String dsName = "ds";
        final float[] arrayWritten = new float[dataSetSize];
        for (int i = 0; i < dataSetSize; ++i)
        {
            arrayWritten[i] = i;
        }
        writer.float32().createArray(dsName, dataSetSize, blockSize);
        writer.float32().writeArrayBlock(dsName, arrayWritten, 0);
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        int i = 0;
        for (HDF5DataBlock<float[]> block : reader.float32().getArrayNaturalBlocks(dsName))
        {
            assertEquals(i, block.getIndex());
            assertEquals(blockSize * i, block.getOffset());
            final float[] arrayReadBlock = block.getData();
            if (blockSize * (i + 1) > dataSetSize)
            {
                assertEquals(dataSetSize - i * blockSize, arrayReadBlock.length);
            } else
            {
                assertEquals(blockSize, arrayReadBlock.length);
            }
            final float[] arrayWrittenBlock = new float[arrayReadBlock.length];
            System.arraycopy(arrayWritten, (int) block.getOffset(), arrayWrittenBlock, 0,
                    arrayWrittenBlock.length);
            assertTrue(Arrays.equals(arrayWrittenBlock, arrayReadBlock));
            ++i;
        }
        assertEquals(dataSetSize / blockSize + (dataSetSize % blockSize != 0 ? 1 : 0), i);
        reader.close();
    }

    @Test
    public void testReadToFloatMDArrayBlockWithOffset()
    {
        final File datasetFile = new File(workingDirectory, "readToFloatMDArrayBlockWithOffset.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(datasetFile);
        final String dsName = "ds";
        final MDFloatArray arrayWritten = new MDFloatArray(new float[]
            { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, new int[]
            { 3, 3 });
        writer.float32().writeMDArray(dsName, arrayWritten);
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        final MDFloatArray arrayRead = new MDFloatArray(new int[]
            { 10, 10 });
        final MDFloatArray arrayReadb = new MDFloatArray(arrayRead.dimensions());
        final int memOfsX = 2;
        final int memOfsY = 3;
        final int diskOfsX = 1;
        final int diskOfsY = 0;
        final int blockSizeX = 3;
        final int blockSizeY = 2;
        final int[] effectiveDimensions =
                reader.float32().readToMDArrayBlockWithOffset(dsName, arrayRead, new int[]
                    { blockSizeX, blockSizeY }, new long[]
                    { diskOfsX, diskOfsY }, new int[]
                    { memOfsX, memOfsY });
        final int[] effectiveDimensionsb;
        try (HDF5DataSet ds = reader.object().openDataSet(dsName))
        {
            effectiveDimensionsb =
                    reader.float32().readToMDArrayBlockWithOffset(ds, arrayReadb, new int[]
                        { blockSizeX, blockSizeY }, new long[]
                        { diskOfsX, diskOfsY }, new int[]
                        { memOfsX, memOfsY });
        }
        reader.close();
        assertEquals(blockSizeX - 1, effectiveDimensions[0]);
        assertEquals(blockSizeY, effectiveDimensions[1]);
        assertEquals(blockSizeX - 1, effectiveDimensionsb[0]);
        assertEquals(blockSizeY, effectiveDimensionsb[1]);
        final boolean[][] isSet = new boolean[10][10];
        for (int i = 0; i < effectiveDimensions[0]; ++i)
        {
            for (int j = 0; j < effectiveDimensions[1]; ++j)
            {
                isSet[memOfsX + i][memOfsY + j] = true;
                assertEquals("(" + i + "," + j + ")", arrayWritten.get(diskOfsX + i, diskOfsY + j),
                        arrayRead.get(memOfsX + i, memOfsY + j));
                assertEquals("(" + i + "," + j + ")", arrayWritten.get(diskOfsX + i, diskOfsY + j),
                        arrayReadb.get(memOfsX + i, memOfsY + j));
            }
        }
        for (int i = 0; i < arrayRead.size(0); ++i)
        {
            for (int j = 0; j < arrayRead.size(1); ++j)
            {
                if (isSet[i][j] == false)
                {
                    assertEquals("(" + i + "," + j + ")", 0f, arrayRead.get(i, j));
                    assertEquals("(" + i + "," + j + ")", 0f, arrayReadb.get(i, j));
                }
            }
        }
    }

    @Test
    public void testReadToTimeDurationMDArrayBlockWithOffset()
    {
        final File datasetFile =
                new File(workingDirectory, "readToTimeDurationMDArrayBlockWithOffset.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(datasetFile);
        final String dsName = "ds";
        final HDF5TimeDurationMDArray arrayWritten = new HDF5TimeDurationMDArray(new long[]
            { 1, 1, 1, 1, 1, 1, 1, 1, 1 }, new int[]
            { 3, 3 }, HDF5TimeUnit.MINUTES);
        writer.duration().writeMDArray(dsName, arrayWritten);
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        final HDF5TimeDurationMDArray arrayRead = new HDF5TimeDurationMDArray(new int[]
            { 10, 10 }, HDF5TimeUnit.SECONDS);
        final int memOfsX = 2;
        final int memOfsY = 3;
        final int diskOfsX = 1;
        final int diskOfsY = 0;
        final int blockSizeX = 3;
        final int blockSizeY = 2;
        final int[] effectiveDimensions =
                reader.duration().readToMDArrayBlockWithOffset(dsName, arrayRead, new int[]
                    { blockSizeX, blockSizeY }, new long[]
                    { diskOfsX, diskOfsY }, new int[]
                    { memOfsX, memOfsY });
        reader.close();
        assertEquals(blockSizeX - 1, effectiveDimensions[0]);
        assertEquals(blockSizeY, effectiveDimensions[1]);
        final boolean[][] isSet = new boolean[10][10];
        for (int i = 0; i < effectiveDimensions[0]; ++i)
        {
            for (int j = 0; j < effectiveDimensions[1]; ++j)
            {
                isSet[memOfsX + i][memOfsY + j] = true;
                assertEquals("(" + i + "," + j + ")",
                        60 * arrayWritten.get(diskOfsX + i, diskOfsY + j),
                        arrayRead.get(memOfsX + i, memOfsY + j));
            }
        }
        for (int i = 0; i < arrayRead.size(0); ++i)
        {
            for (int j = 0; j < arrayRead.size(1); ++j)
            {
                if (isSet[i][j] == false)
                {
                    assertEquals("(" + i + "," + j + ")", 0, arrayRead.get(i, j));
                }
            }
        }
    }

    @Test(dataProvider = "provideSizes")
    public void testIterateOverStringArrayInNaturalBlocks(int blockSize, int dataSetSize)
    {
        final File datasetFile =
                new File(workingDirectory, "testIterateOverStringArrayInNaturalBlocks.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(datasetFile);
        final String dsName = "ds";
        final String[] arrayWritten = new String[dataSetSize];
        for (int i = 0; i < dataSetSize; ++i)
        {
            arrayWritten[i] = "" + i;
        }
        writer.string().createArray(dsName, dataSetSize, blockSize);
        writer.string().writeArrayBlock(dsName, arrayWritten, 0);
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        int i = 0;
        for (HDF5DataBlock<String[]> block : reader.string().getArrayNaturalBlocks(dsName))
        {
            assertEquals(i, block.getIndex());
            assertEquals(blockSize * i, block.getOffset());
            final String[] arrayReadBlock = block.getData();
            if (blockSize * (i + 1) > dataSetSize)
            {
                assertEquals(dataSetSize - i * blockSize, arrayReadBlock.length);
            } else
            {
                assertEquals(blockSize, arrayReadBlock.length);
            }
            final String[] arrayWrittenBlock = new String[arrayReadBlock.length];
            System.arraycopy(arrayWritten, (int) block.getOffset(), arrayWrittenBlock, 0,
                    arrayWrittenBlock.length);
            assertTrue(Arrays.equals(arrayWrittenBlock, arrayReadBlock));
            ++i;
        }
        assertEquals(dataSetSize / blockSize + (dataSetSize % blockSize != 0 ? 1 : 0), i);
        reader.close();
    }

    @DataProvider
    private Object[][] provideMDSizes()
    {
        return new Object[][]
            {
                { new int[]
                    { 2, 2 }, new long[]
                    { 4, 3 }, new float[]
                    { 0f, 2f, 6f, 8f }, new int[][]
                    {
                        { 2, 2 },
                        { 2, 1 },
                        { 2, 2 },
                        { 2, 1 } } },
                { new int[]
                    { 2, 2 }, new long[]
                    { 4, 4 }, new float[]
                    { 0f, 2f, 8f, 10f }, new int[][]
                    {
                        { 2, 2 },
                        { 2, 2 },
                        { 2, 2 },
                        { 2, 2 } } },
                { new int[]
                    { 2, 2 }, new long[]
                    { 4, 5 }, new float[]
                    { 0f, 2f, 4f, 10f, 12f, 14f }, new int[][]
                    {
                        { 2, 2 },
                        { 2, 2 },
                        { 2, 1 },
                        { 2, 2 },
                        { 2, 2 },
                        { 2, 1 } } },
                { new int[]
                    { 3, 2 }, new long[]
                    { 5, 4 }, new float[]
                    { 0f, 2f, 12f, 14f }, new int[][]
                    {
                        { 3, 2 },
                        { 3, 2 },
                        { 2, 2 },
                        { 2, 2 } } },
                { new int[]
                    { 2, 2 }, new long[]
                    { 5, 4 }, new float[]
                    { 0f, 2f, 8f, 10f, 16f, 18f }, new int[][]
                    {
                        { 2, 2 },
                        { 2, 2 },
                        { 2, 2 },
                        { 2, 2 },
                        { 1, 2 },
                        { 1, 2 } } }, };
    }

    @Test(dataProvider = "provideMDSizes")
    public void testIterateOverMDFloatArrayInNaturalBlocks(int[] blockSize, long[] dataSetSize,
            float[] firstNumberPerIteration, int[][] blockSizePerIteration)
    {
        final File datasetFile =
                new File(workingDirectory, "iterateOverMDFloatArrayInNaturalBlocks.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(datasetFile);
        final String dsName = "ds";
        final float[] flattenedArray = new float[getNumberOfElements(dataSetSize)];
        for (int i = 0; i < flattenedArray.length; ++i)
        {
            flattenedArray[i] = i;
        }
        final MDFloatArray arrayWritten = new MDFloatArray(flattenedArray, dataSetSize);
        writer.float32().createMDArray(dsName, dataSetSize, blockSize);
        writer.float32().writeMDArrayBlock(dsName, arrayWritten, new long[blockSize.length]);
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        int i = 0;
        for (HDF5MDDataBlock<MDFloatArray> block : reader.float32().getMDArrayNaturalBlocks(dsName))
        {
            assertEquals(firstNumberPerIteration[i], block.getData().get(0, 0));
            assertTrue(Arrays.equals(block.getData().dimensions(), blockSizePerIteration[i]));
            ++i;
        }
        assertEquals(firstNumberPerIteration.length, i);
        reader.close();
    }

    private static int getNumberOfElements(long[] size)
    {
        int elements = 1;
        for (long dim : size)
        {
            elements *= dim;
        }
        return elements;
    }

    @Test
    public void testStringArray()
    {
        final File stringArrayFile = new File(workingDirectory, "stringArray.h5");
        stringArrayFile.delete();
        assertFalse(stringArrayFile.exists());
        stringArrayFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(stringArrayFile);
        final String[] data = new String[]
            { "abc", "ABCxxx", "xyz" };
        final String dataSetName = "/aStringArray";
        writer.writeStringArray(dataSetName, data);
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(stringArrayFile);
        final String[] dataStored = reader.readStringArray(dataSetName);
        assertTrue(Arrays.equals(data, dataStored));
        reader.close();
    }

    @Test
    public void testStringArrayUTF8()
    {
        final File stringArrayFile = new File(workingDirectory, "stringArrayUTF8.h5");
        stringArrayFile.delete();
        assertFalse(stringArrayFile.exists());
        stringArrayFile.deleteOnExit();
        final IHDF5Writer writer =
                HDF5FactoryProvider.get().configure(stringArrayFile).useUTF8CharacterEncoding()
                        .writer();
        final String[] data = new String[]
            { "abc", "ABCxxx", "\u00b6\u00bc\u09ab" };
        final String dataSetName = "/aStringArray";
        writer.writeStringArray(dataSetName, data);
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(stringArrayFile);
        final String[] dataStored = reader.readStringArray(dataSetName);
        assertTrue(Arrays.equals(data, dataStored));
        reader.close();
    }

    @Test
    public void testStringArrayUTF8WithZeroChar()
    {
        final File stringArrayFile = new File(workingDirectory, "stringArrayUTF8WithZeroChar.h5");
        stringArrayFile.delete();
        assertFalse(stringArrayFile.exists());
        stringArrayFile.deleteOnExit();
        final IHDF5Writer writer =
                HDF5FactoryProvider.get().configure(stringArrayFile).useUTF8CharacterEncoding()
                        .writer();
        final String[] data = new String[]
            { "abc", "ABCxxx", "\u00b6\000\u00bc\u09ab" };
        final String dataSetName = "/aStringArray";
        writer.writeStringArray(dataSetName, data);
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(stringArrayFile);
        final String[] dataStored = reader.string().readArrayRaw(dataSetName);
        assertEquals(3, dataStored.length);
        assertEquals(StringUtils.rightPad("abc", 8, "\0"), dataStored[0]);
        assertEquals(StringUtils.rightPad("ABCxxx", 8, "\0"), dataStored[1]);
        assertEquals("\u00b6\000\u00bc\u09ab", dataStored[2]);
        reader.close();
    }

    private static void fillArray(float mult, float[] data)
    {
        for (int i = 0; i < data.length; ++i)
        {
            data[i] = mult * i;
        }
    }

    private static void fillArray(short mult, short[] data)
    {
        for (short i = 0; i < data.length; ++i)
        {
            data[i] = (short) (mult * i);
        }
    }

    private static void fillArray(long mult, long[] data)
    {
        for (int i = 0; i < data.length; ++i)
        {
            data[i] = mult * i;
        }
    }

    @Test
    public void testFloatArrayBlockWithPreopenedDataSet()
    {
        final File floatArrayFile = new File(workingDirectory, "testFloatArrayBlockWithPreopenedDataSet.h5");
        floatArrayFile.delete();
        assertFalse(floatArrayFile.exists());
        floatArrayFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(floatArrayFile);
        final float[] dataWritten = new float[128];
        writer.int32().createArray("ds", dataWritten.length);
        try (final HDF5DataSet ds = writer.int32().createArrayAndOpen("ds", dataWritten.length))
        {
            for (long bx = 0; bx < 8; ++bx)
            {
                fillArray(bx + 1, dataWritten);
                writer.float32().writeArrayBlock(ds, dataWritten, bx);
            }
        }
        writer.close();

        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(floatArrayFile);
        try (final HDF5DataSet ds = reader.object().openDataSet("ds"))
        {
            for (long bx = 0; bx < 8; ++bx)
            {
                fillArray(bx + 1, dataWritten);
                final float[] dataRead =
                        reader.float32().readArrayBlock(ds, dataWritten.length, bx);
                assertTrue("" + bx, Arrays.equals(dataWritten, dataRead));
            }
        }
        reader.close();
    }

    @Test
    public void testFloatArraysFromTemplates()
    {
        final File floatArrayFile = new File(workingDirectory, "floatArraysFromTemplates.h5");
        floatArrayFile.delete();
        assertFalse(floatArrayFile.exists());
        floatArrayFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(floatArrayFile);
        final float[] dataWritten = new float[128];
        try (final HDF5DataSetTemplate tmpl =
                writer.float32().createArrayTemplate(dataWritten.length, dataWritten.length,
                        HDF5FloatStorageFeatures.FLOAT_CONTIGUOUS))
        {
            for (long bx = 0; bx < 8; ++bx)
                try (final HDF5DataSet ds = writer.float32().createArrayAndOpen("ds" + bx, tmpl)) 
                {
                    fillArray(bx + 1, dataWritten);
                    writer.float32().writeArrayBlock(ds, dataWritten, 0);
                }
        }
        writer.close();

        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(floatArrayFile);
        for (long bx = 0; bx < 8; ++bx)
        {
            fillArray(bx + 1, dataWritten);
            final double[] dataRead = reader.float64().readArray("ds" + bx);
            final double[] dataWrittenDouble = new double[dataWritten.length];
            for (int i = 0; i < dataWritten.length; ++i)
            {
                dataWrittenDouble[i] = dataWritten[i];
            }
            assertTrue("" + bx, Arrays.equals(dataWrittenDouble, dataRead));
        }
        reader.close();
    }

    @Test(expectedExceptions = { HDF5SpaceRankMismatch.class })
    public void testMDShortArrayRankMismatch()
    {
        assertEquals(0, HDF5Factory.getOpenHDF5FileCount());
        final File shortArrayFile = new File(workingDirectory, "MDShortArrayRankMismatch.h5");
        shortArrayFile.delete();
        assertFalse(shortArrayFile.exists());
        shortArrayFile.deleteOnExit();
        try (final IHDF5Writer writer = HDF5FactoryProvider.get().open(shortArrayFile))
        {
            final MDShortArray dataWritten = new MDShortArray(new int[] { 2, 2, 2 });
            writer.uint16().createMDArray("ds", new int[] { 4, 2 });
            fillArray((short) 7, dataWritten.getAsFlatArray());
            // Will fail with an HDF5SpaceRankMismatch as the data set is of rank 3, but the data are of rank 2.
            writer.uint16().writeMDArrayBlock("ds", dataWritten, dataWritten.longDimensions());
        }
    }

    @Test(expectedExceptions = { HDF5SpaceRankMismatch.class })
    public void testMDShortArrayRankMismatchDetached()
    {
        assertEquals(0, HDF5Factory.getOpenHDF5FileCount());
        final File shortArrayFile = new File(workingDirectory, "MDShortArrayRankMismatch.h5");
        shortArrayFile.delete();
        assertFalse(shortArrayFile.exists());
        shortArrayFile.deleteOnExit();
        try (final IHDF5Writer writer = HDF5FactoryProvider.get().open(shortArrayFile))
        {
            final MDShortArray dataWritten = new MDShortArray(new int[] { 2, 2, 2 });
            try (final HDF5DataSet ds = writer.uint16().createMDArrayAndOpen("ds", new int[] { 4, 2 })) 
            {
                fillArray((short) 7, dataWritten.getAsFlatArray());
                // Will fail with an HDF5SpaceRankException as the data set is of rank 3, but the data are of rank 2.
                writer.uint16().writeMDArrayBlock(ds, dataWritten, dataWritten.longDimensions());
            }
        }
    }

    @Test(expectedExceptions = { HDF5JavaException.class })
    public void testMDLongArrayArrayIndexOutOfBounds()
    {
        assertEquals(0, HDF5Factory.getOpenHDF5FileCount());
        final File longArrayFile = new File(workingDirectory, "MDLongArrayArrayIndexOutOfBounds.h5");
        longArrayFile.delete();
        assertFalse(longArrayFile.exists());
        longArrayFile.deleteOnExit();
        try (final IHDF5Writer writer = HDF5FactoryProvider.get().open(longArrayFile))
        {
            final MDLongArray dataWritten = new MDLongArray(new int[] { 2, 2, 2 });
            try (final HDF5DataSet ds = writer.int64().createMDArrayAndOpen("ds", new int[] { 2, 2, 2 }, HDF5IntStorageFeatures.INT_COMPACT)) 
            {
                fillArray(7, dataWritten.getAsFlatArray());
                // Will fail with an HDF5JavaException as the block number is out of bounds.
                writer.int64().writeMDArrayBlock(ds, dataWritten, new long[] { 0, 1, 0 });
            }
        }
    }
    
    @Test(expectedExceptions = { HDF5JavaException.class })
    public void testMDLongArrayOnReadArrayIndexOutOfBounds()
    {
        assertEquals(0, HDF5Factory.getOpenHDF5FileCount());
        final File longArrayFile = new File(workingDirectory, "MDLongArrayOnReadArrayIndexOutOfBounds.h5");
        longArrayFile.delete();
        assertFalse(longArrayFile.exists());
        longArrayFile.deleteOnExit();
        final int[] dimensions = new int[] { 2, 2, 2 };
        final MDLongArray dataWritten = new MDLongArray(dimensions);
        try (final IHDF5Writer writer = HDF5FactoryProvider.get().open(longArrayFile))
        {
            try (final HDF5DataSet ds = writer.int64().createMDArrayAndOpen("ds", dimensions, HDF5IntStorageFeatures.INT_COMPACT)) 
            {
                fillArray(7, dataWritten.getAsFlatArray());
                writer.int64().writeMDArrayBlock(ds, dataWritten, new long[] { 0, 0, 0 });
            }
        }
        try (final IHDF5Reader reader = HDF5FactoryProvider.get().open(longArrayFile))
        {
            try (final HDF5DataSet ds = reader.object().openDataSet("ds")) 
            {
                fillArray(7, dataWritten.getAsFlatArray());
                // Will fail with an HDF5JavaException as the block number is out of bounds.
                reader.int64().readMDArrayBlock(ds, dimensions, new long[] { 0, 1, 0 });
            }
            
        }
    }
    
    @Test
    public void testFloatMDArraysFromTemplates()
    {
        final File floatArrayFile = new File(workingDirectory, "floatMDArraysFromTemplates.h5");
        floatArrayFile.delete();
        assertFalse(floatArrayFile.exists());
        floatArrayFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(floatArrayFile);
        final MDFloatArray dataWritten = new MDFloatArray(new int[] { 2, 2 });
        try (final HDF5DataSetTemplate tmpl =
                writer.float32().createMDArrayTemplate(new long[] { 0, 0 }, new int[] { 2, 2 },
                        HDF5FloatStorageFeatures.FLOAT_CHUNKED))
        {
            for (long bx = 0; bx < 8; ++bx)
                try (final HDF5DataSet ds = writer.float32().createMDArrayAndOpen("ds" + bx, tmpl)) 
                {
                    fillArray(bx + 1, dataWritten.getAsFlatArray());
                    writer.float32().writeMDArrayBlock(ds, dataWritten, new long[] { 0, 0} );
                }
        }
        writer.close();

        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(floatArrayFile);
        for (long bx = 0; bx < 8; ++bx)
            try (final HDF5DataSet ds = reader.object().openDataSet("ds" + bx)) {
                fillArray(bx + 1, dataWritten.getAsFlatArray());
                final MDFloatArray dataRead = reader.float32().readMDArrayBlock(ds, 
                        new int[] { 2, 2 }, new long[] {0, 0 });
                assertEquals("" + bx, dataWritten, dataRead);
            }
        reader.close();
    }

    @Test
    public void testFloatMDArraysDetachedDataSets()
    {
        final File floatArrayFile = new File(workingDirectory, "testFloatMDArraysDetachedDataSets.h5");
        floatArrayFile.delete();
        assertFalse(floatArrayFile.exists());
        floatArrayFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(floatArrayFile);
        final MDFloatArray dataWritten = new MDFloatArray(new int[] { 5, 3 });
        try (HDF5DataSet ds = writer.float32().createMDArrayAndOpen("ds", new int[] { 5, 3 }, HDF5FloatStorageFeatures.FLOAT_CHUNKED))
        {
            for (int i = 0; i < 4; ++i)
            {
                for (int j = 0; j < 4; ++j)
                {
                    fillArray(i + j + 17, dataWritten.getAsFlatArray());
                    writer.float32().writeMDArrayBlock(ds, dataWritten, new long[] { i, j });
                }
            }
        }
        writer.close();

        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(floatArrayFile);
        try (HDF5DataSet ds = reader.object().openDataSet("ds"))
        {
            for (int i = 0; i < 4; ++i)
            {
                for (int j = 0; j < 4; ++j)
                {
                    fillArray(i + j + 17, dataWritten.getAsFlatArray());
                    final MDFloatArray dataRead = reader.float32().readMDArrayBlock(ds, new int[] { 5, 3 }, new long[] {i, j});
                    assertEquals(i + "," + j, dataWritten, dataRead);
                }
            }
        }
        reader.close();
    }

    @Test
    public void testStringArrayBlock()
    {
        final File stringArrayFile = new File(workingDirectory, "stringArrayBlock.h5");
        stringArrayFile.delete();
        assertFalse(stringArrayFile.exists());
        stringArrayFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(stringArrayFile);
        final String[] data = new String[]
            { "abc\0A", "ABCxxx\0" + "1", "xyz\0;" };
        final String[] dataZeroTerm = zeroTerm(data);
        final String[] dataPadded = pad(data, 8);
        final String emptyPadded = StringUtils.rightPad("", 8, '\0');
        final String dataSetName = "/aStringArray";
        writer.string().createArray(dataSetName, 8, 5, 3);
        writer.string().writeArrayBlock(dataSetName, data, 1);
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(stringArrayFile);
        String[] dataStored = reader.string().readArray(dataSetName);
        assertTrue(Arrays.equals(new String[]
            { "", "", "", dataZeroTerm[0], dataZeroTerm[1], dataZeroTerm[2] }, dataStored));

        dataStored = reader.string().readArrayRaw(dataSetName);
        assertTrue(Arrays.equals(new String[]
            { emptyPadded, emptyPadded, emptyPadded, dataPadded[0], dataPadded[1], dataPadded[2] },
                dataStored));

        dataStored = reader.string().readArrayBlock(dataSetName, 3, 0);
        assertTrue(Arrays.equals(new String[]
            { "", "", "" }, dataStored));
        dataStored = reader.string().readArrayBlockRaw(dataSetName, 3, 0);
        assertTrue(Arrays.equals(new String[]
            { emptyPadded, emptyPadded, emptyPadded }, dataStored));
        dataStored = reader.string().readArrayBlock(dataSetName, 3, 1);
        assertTrue(Arrays.equals(dataZeroTerm, dataStored));
        dataStored = reader.string().readArrayBlockRaw(dataSetName, 3, 1);
        assertTrue(Arrays.equals(dataPadded, dataStored));
        dataStored = reader.string().readArrayBlockWithOffset(dataSetName, 3, 2);
        assertTrue(Arrays.equals(new String[]
            { "", dataZeroTerm[0], dataZeroTerm[1] }, dataStored));
        dataStored = reader.string().readArrayBlockWithOffsetRaw(dataSetName, 3, 2);
        assertTrue(Arrays.equals(new String[]
            { emptyPadded, dataPadded[0], dataPadded[1] }, dataStored));
        reader.close();
    }

    @Test
    public void testStringArrayBlockCompact()
    {
        final File stringArrayFile = new File(workingDirectory, "stringArrayBlockCompact.h5");
        stringArrayFile.delete();
        assertFalse(stringArrayFile.exists());
        stringArrayFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(stringArrayFile);
        final String[] data = new String[]
            { "abc", "ABCxxx", "xyz" };
        final String dataSetName = "/aStringArray";
        writer.string().createArray(dataSetName, 6, 6, HDF5GenericStorageFeatures.GENERIC_COMPACT);
        writer.string().writeArrayBlock(dataSetName, data, 1);
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(stringArrayFile);
        String[] dataStored = reader.readStringArray(dataSetName);
        assertTrue(Arrays.equals(new String[]
            { "", "", "", data[0], data[1], data[2] }, dataStored));
        dataStored = reader.string().readArrayBlock(dataSetName, 3, 0);
        assertTrue(Arrays.equals(new String[]
            { "", "", "" }, dataStored));
        dataStored = reader.string().readArrayBlock(dataSetName, 3, 1);
        assertTrue(Arrays.equals(data, dataStored));
        dataStored = reader.string().readArrayBlockWithOffset(dataSetName, 3, 2);
        assertTrue(Arrays.equals(new String[]
            { "", data[0], data[1] }, dataStored));
        reader.close();
    }

    @Test
    public void testStringArrayBlockVL()
    {
        final File stringArrayFile = new File(workingDirectory, "stringArrayBlockVL.h5");
        stringArrayFile.delete();
        assertFalse(stringArrayFile.exists());
        stringArrayFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(stringArrayFile);
        final String[] data1 = new String[]
            { "abc", "ABCxxx", "xyz" };
        final String[] data2 = new String[]
            { "abd", "ABDxxx", "xyw" };
        final String[] data = new String[]
            { "", "", "", "abc", "ABCxxx", "xyz", "abd", "ABDxxx", "xyw" };
        final String dataSetName = "/aStringArray";
        writer.string().createArrayVL(dataSetName, 0, 5);
        writer.string().writeArrayBlock(dataSetName, data1, 1);
        writer.string().writeArrayBlock(dataSetName, data2, 2);
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(stringArrayFile);
        String[] dataRead = reader.readStringArray(dataSetName);
        assertTrue(Arrays.equals(data, dataRead));
        dataRead = reader.string().readArrayBlock(dataSetName, 3, 1);
        assertTrue(Arrays.equals(data1, dataRead));
        dataRead = reader.string().readArrayBlock(dataSetName, 3, 2);
        assertTrue(Arrays.equals(data2, dataRead));
        dataRead = reader.string().readArrayBlockWithOffset(dataSetName, 3, 5);
        assertTrue(Arrays.equals(new String[]
            { "xyz", "abd", "ABDxxx" }, dataRead));
        reader.close();
    }

    @Test
    public void testStringArrayMD()
    {
        final File stringArrayFile = new File(workingDirectory, "stringMDArray.h5");
        stringArrayFile.delete();
        assertFalse(stringArrayFile.exists());
        stringArrayFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(stringArrayFile);
        final MDArray<String> data = new MDArray<String>(new String[]
            { "abc", "ABCxxx", "xyz", "DEF" }, new long[]
            { 2, 2 });
        final String dataSetName = "/aStringArray";
        writer.string().writeMDArray(dataSetName, data);
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(stringArrayFile);
        final MDArray<String> dataStored = reader.string().readMDArray(dataSetName);
        assertTrue(Arrays.equals(data.getAsFlatArray(), dataStored.getAsFlatArray()));
        assertTrue(Arrays.equals(data.dimensions(), dataStored.dimensions()));
        reader.close();
    }

    @Test
    public void testStringArrayMDBlocks()
    {
        final File stringArrayFile = new File(workingDirectory, "stringMDArrayBlocks.h5");
        stringArrayFile.delete();
        assertFalse(stringArrayFile.exists());
        stringArrayFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(stringArrayFile);
        final String dataSetName = "/aStringArray";
        writer.string().createMDArray(dataSetName, 8, new long[]
            { 4, 4 }, new int[]
            { 2, 2 });
        final MDArray<String> data = new MDArray<String>(new String[]
            { "abc", "ABCxxx\0" + 1, "xyz\0;", "DEF\0" + 8 }, new long[]
            { 2, 2 });
        final MDArray<String> dataZeroTerm =
                new MDArray<String>(zeroTerm(data.getAsFlatArray()), data.dimensions());
        final MDArray<String> dataPadded =
                new MDArray<String>(pad(data.getAsFlatArray(), 8), data.dimensions());
        for (int i = 0; i < 2; ++i)
        {
            for (int j = 0; j < 2; ++j)
            {
                writer.string().writeMDArrayBlock(dataSetName, data, new long[]
                    { i, j });
            }
        }
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(stringArrayFile);
        int i = 0;
        int j = 0;
        for (HDF5MDDataBlock<MDArray<String>> block : reader.string().getMDArrayNaturalBlocks(
                dataSetName))
        {
            assertTrue(Arrays.equals(dataZeroTerm.getAsFlatArray(), block.getData()
                    .getAsFlatArray()));
            assertTrue(Arrays.equals(dataZeroTerm.dimensions(), block.getData().dimensions()));
            assertTrue(Arrays.equals(new long[]
                { i, j }, block.getIndex()));
            if (++j > 1)
            {
                j = 0;
                ++i;
            }
        }

        i = 0;
        j = 0;
        for (HDF5MDDataBlock<MDArray<String>> block : reader.string().getMDArrayNaturalBlocksRaw(
                dataSetName))
        {
            assertTrue(Arrays.equals(dataPadded.getAsFlatArray(), block.getData().getAsFlatArray()));
            assertTrue(Arrays.equals(dataPadded.dimensions(), block.getData().dimensions()));
            assertTrue(Arrays.equals(new long[]
                { i, j }, block.getIndex()));
            if (++j > 1)
            {
                j = 0;
                ++i;
            }
        }
        reader.close();
    }

    private String[] pad(String[] data, int len)
    {
        final String[] result = new String[data.length];
        for (int i = 0; i < result.length; ++i)
        {
            result[i] = StringUtils.rightPad(data[i], len, '\0');
        }
        return result;
    }

    private String[] zeroTerm(String[] data)
    {
        final String[] result = new String[data.length];
        for (int i = 0; i < result.length; ++i)
        {
            result[i] = zeroTerm(data[i]);
        }
        return result;
    }

    private String zeroTerm(String s)
    {
        int idx = s.indexOf('\0');
        if (idx < 0)
        {
            return s;
        } else
        {
            return s.substring(0, idx);
        }
    }

    @Test
    public void testStringMDArrayVL()
    {
        final File stringArrayFile = new File(workingDirectory, "stringMDArrayVL.h5");
        stringArrayFile.delete();
        assertFalse(stringArrayFile.exists());
        stringArrayFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(stringArrayFile);
        final MDArray<String> data = new MDArray<String>(new String[]
            { "abc", "ABCxxx", "xyz", "DEF" }, new long[]
            { 2, 2 });
        final String dataSetName = "/aStringArray";
        writer.string().writeMDArrayVL(dataSetName, data);
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(stringArrayFile);
        final MDArray<String> dataStored = reader.string().readMDArray(dataSetName);
        assertTrue(Arrays.equals(data.getAsFlatArray(), dataStored.getAsFlatArray()));
        assertTrue(Arrays.equals(data.dimensions(), dataStored.dimensions()));
        final HDF5DataSetInformation info = reader.getDataSetInformation(dataSetName);
        reader.close();
        assertTrue(info.getTypeInformation().isVariableLengthString());
        assertEquals("STRING(-1)", info.getTypeInformation().toString());
        assertEquals(HDF5StorageLayout.CHUNKED, info.getStorageLayout());
        assertTrue(Arrays.toString(info.getDimensions()), Arrays.equals(new long[]
            { 2, 2 }, info.getDimensions()));
        assertTrue(Arrays.toString(info.getMaxDimensions()), Arrays.equals(new long[]
            { -1, -1 }, info.getMaxDimensions()));
        assertTrue(Arrays.toString(info.tryGetChunkSizes()),
                Arrays.equals(MDArray.toInt(info.getDimensions()), info.tryGetChunkSizes()));
    }

    @Test
    public void testStringMDArrayVLBlocks()
    {
        final File stringArrayFile = new File(workingDirectory, "stringMDArrayVLBlocks.h5");
        stringArrayFile.delete();
        assertFalse(stringArrayFile.exists());
        stringArrayFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(stringArrayFile);
        final long[] dims = new long[]
            { 8, 8 };
        final int[] blockSize = new int[]
            { 2, 2 };
        final MDArray<String> data = new MDArray<String>(new String[]
            { "abc", "ABCxxx", "xyz", "DEF" }, blockSize);
        final String dataSetName = "/aStringArray";
        writer.string().createMDArrayVL(dataSetName, dims, blockSize);
        writer.string().writeMDArrayBlock(dataSetName, data, new long[]
            { 1, 1 });
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(stringArrayFile);
        final MDArray<String> dataStored =
                reader.string().readMDArrayBlock(dataSetName, blockSize, new long[]
                    { 1, 1 });
        assertTrue(Arrays.equals(data.getAsFlatArray(), dataStored.getAsFlatArray()));
        assertTrue(Arrays.equals(data.dimensions(), dataStored.dimensions()));
        assertTrue(Arrays.equals(new String[]
            { "", "", "", "" }, reader.string().readMDArrayBlock(dataSetName, blockSize, new long[]
            { 1, 0 }).getAsFlatArray()));
        assertTrue(Arrays.equals(new String[]
            { "", "", "", "" }, reader.string().readMDArrayBlock(dataSetName, blockSize, new long[]
            { 0, 1 }).getAsFlatArray()));
        assertTrue(Arrays.equals(new String[]
            { "", "", "", "" }, reader.string().readMDArrayBlock(dataSetName, blockSize, new long[]
            { 2, 2 }).getAsFlatArray()));
        final HDF5DataSetInformation info = reader.getDataSetInformation(dataSetName);
        reader.close();
        assertTrue(info.getTypeInformation().isVariableLengthString());
        assertEquals("STRING(-1)", info.getTypeInformation().toString());
        assertEquals(HDF5StorageLayout.CHUNKED, info.getStorageLayout());
        assertTrue(Arrays.equals(dims, info.getDimensions()));
        assertTrue(Arrays.equals(new long[]
            { -1, -1 }, info.getMaxDimensions()));
        assertTrue(Arrays.equals(blockSize, info.tryGetChunkSizes()));
    }

    @Test
    public void testOverwriteString()
    {
        final File stringOverwriteFile = new File(workingDirectory, "overwriteString.h5");
        stringOverwriteFile.delete();
        assertFalse(stringOverwriteFile.exists());
        stringOverwriteFile.deleteOnExit();
        IHDF5Writer writer =
                HDF5FactoryProvider.get().configure(stringOverwriteFile)
                        .dontUseExtendableDataTypes().writer();
        final String largeData = StringUtils.repeat("a", 12);
        final String smallData = "abc1234";
        final String dataSetName = "/aString";
        writer.string().write(dataSetName, smallData);
        writer.string().write(dataSetName, largeData);
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(stringOverwriteFile);
        final String dataRead = reader.readString(dataSetName);
        assertEquals(largeData, dataRead);
        reader.close();
    }

    @Test
    public void testOverwriteStringWithLarge()
    {
        final File stringOverwriteFile = new File(workingDirectory, "overwriteStringWithLarge.h5");
        stringOverwriteFile.delete();
        assertFalse(stringOverwriteFile.exists());
        stringOverwriteFile.deleteOnExit();
        IHDF5Writer writer = HDF5FactoryProvider.get().configure(stringOverwriteFile).writer();
        final String largeData = StringUtils.repeat("a", 64 * 1024);
        final String smallData = "abc1234";
        final String dataSetName = "/aString";
        writer.string().write(dataSetName, smallData);
        writer.string().write(dataSetName, largeData);
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(stringOverwriteFile);
        final String dataRead = reader.readString(dataSetName);
        assertEquals(largeData, dataRead);
        reader.close();
    }

    @Test
    public void testOverwriteStringWithLargeKeepCompact()
    {
        final File stringOverwriteFile =
                new File(workingDirectory, "overwriteStringWithLargeKeepCompact.h5");
        stringOverwriteFile.delete();
        assertFalse(stringOverwriteFile.exists());
        stringOverwriteFile.deleteOnExit();
        IHDF5Writer writer = HDF5FactoryProvider.get().configure(stringOverwriteFile).writer();
        final String largeData = StringUtils.repeat("a", 64 * 1024);
        final String smallData = "abc1234";
        final String dataSetName = "/aString";
        writer.string().write(dataSetName, smallData);
        writer.string().write(dataSetName, largeData,
                HDF5GenericStorageFeatures.GENERIC_CONTIGUOUS_KEEP);
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(stringOverwriteFile);
        final String dataRead = reader.readString(dataSetName);
        assertEquals(largeData.substring(0, smallData.length()), dataRead);
        assertEquals(HDF5StorageLayout.COMPACT, reader.getDataSetInformation(dataSetName)
                .getStorageLayout());
        reader.close();
    }

    @Test
    public void testSmallString()
    {
        final File smallStringFile = new File(workingDirectory, "smallString.h5");
        smallStringFile.delete();
        assertFalse(smallStringFile.exists());
        smallStringFile.deleteOnExit();
        IHDF5Writer writer = HDF5FactoryProvider.get().configure(smallStringFile).writer();
        final String dataSetName = "/aString";
        writer.string().write(dataSetName, "abc");
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(smallStringFile);
        final String dataRead = reader.readString(dataSetName);
        assertEquals("abc", dataRead);
        assertEquals(HDF5StorageLayout.COMPACT, reader.getDataSetInformation(dataSetName)
                .getStorageLayout());
        reader.close();
    }

    @Test
    public void testVeryLargeString()
    {
        assertEquals(0, HDF5Factory.getOpenHDF5FileCount());
        final File veryLargeStringFile = new File(workingDirectory, "veryLargeString.h5");
        veryLargeStringFile.delete();
        assertFalse(veryLargeStringFile.exists());
        veryLargeStringFile.deleteOnExit();
        IHDF5Writer writer = HDF5FactoryProvider.get().configure(veryLargeStringFile).writer();
        final String largeData = StringUtils.repeat("a", 64 * 1024);
        final String dataSetName = "/aString";
        writer.string().write(dataSetName, largeData);
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(veryLargeStringFile);
        final String dataRead = reader.readString(dataSetName);
        assertEquals(largeData, dataRead);
        assertEquals(HDF5StorageLayout.CONTIGUOUS, reader.getDataSetInformation(dataSetName)
                .getStorageLayout());
        reader.close();
    }

    @Test
    public void testReadStringAsByteArray()
    {
        final File file = new File(workingDirectory, "readStringAsByteArray.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer w = HDF5Factory.open(file);
        w.string().write("a", "abc");
        w.close();
        final IHDF5Reader r = HDF5Factory.openForReading(file);
        final byte[] b = r.readAsByteArray("a");
        assertEquals("abc", new String(b));
        r.close();
    }

    @Test
    public void testReadStringVLAsByteArray()
    {
        final File file = new File(workingDirectory, "readStringVLAsByteArray.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer w = HDF5Factory.open(file);
        w.string().writeVL("a", "abc");
        w.close();
        final IHDF5Reader r = HDF5Factory.openForReading(file);
        final byte[] b = r.readAsByteArray("a");
        assertEquals("abc", new String(b));
        r.close();
    }

    @Test
    public void testReadStringAttributeAsByteArray()
    {
        final File file = new File(workingDirectory, "readStringAttributeAsByteArray.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer w = HDF5Factory.open(file);
        w.string().setAttr("/", "a", "abc");
        w.close();
        final IHDF5Reader r = HDF5Factory.openForReading(file);
        final byte[] b = r.opaque().getArrayAttr("/", "a");
        assertEquals("abc", new String(b));
        r.close();
    }

    @Test
    public void testStringAttributeFixedLength()
    {
        final File file = new File(workingDirectory, "stringAttributeFixedLength.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer w = HDF5Factory.open(file);
        w.string().setAttr("/", "a", "a\0c");
        w.close();
        final IHDF5Reader r = HDF5Factory.openForReading(file);
        final String b = r.string().getAttrRaw("/", "a");
        assertEquals("a\0c", b);
        r.close();
    }

    @Test
    public void testStringAttributeLength0()
    {
        final File file = new File(workingDirectory, "stringAttributeLength0.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer w = HDF5Factory.open(file);
        w.string().setAttr("/", "a", "");
        w.close();
        final IHDF5Reader r = HDF5Factory.openForReading(file);
        final String b = r.string().getAttr("/", "a");
        assertEquals("", b);
        r.close();
    }

    @Test
    public void testStringAttributeFixedLengthExplicitlySaveLength()
    {
        final File file =
                new File(workingDirectory, "stringAttributeFixedLengthExplicitlySaveLength.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer w = HDF5Factory.open(file);
        w.string().setAttr("/", "a", "a\0c");
        w.close();
        final IHDF5Reader r = HDF5Factory.openForReading(file);
        assertEquals("a\0c", r.string().getAttrRaw("/", "a"));
        assertEquals("a", r.string().getAttr("/", "a"));
        r.close();
    }

    @Test
    public void testStringAttributeFixedLengthOverwriteWithShorter()
    {
        final File file =
                new File(workingDirectory, "stringAttributeFixedLengthOverwriteWithShorter.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer w = HDF5Factory.open(file);
        w.string().setAttr("/", "a", "abcdef");
        // This will delete the old attribute and write a new one with length 3.
        w.string().setAttr("/", "a", "ghi");
        w.string().setAttr("/", "b", "abcdef", 6);
        // This will keep the old attribute (of length 6) and just overwrite its value.
        w.string().setAttr("/", "b", "jkl", 6);
        w.close();
        final IHDF5Reader r = HDF5Factory.openForReading(file);
        assertEquals("ghi", r.string().getAttrRaw("/", "a"));
        assertEquals("jkl\0\0\0", r.string().getAttrRaw("/", "b"));
        r.close();
    }

    @Test
    public void testStringAttributeUTF8FixedLength()
    {
        final File file = new File(workingDirectory, "stringAttributeUTF8FixedLength.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer w = HDF5Factory.configure(file).useUTF8CharacterEncoding().writer();
        w.string().setAttr("/", "a", "a\0c");
        w.close();
        final IHDF5Reader r = HDF5Factory.openForReading(file);
        assertEquals("a\0c", r.string().getAttrRaw("/", "a"));
        r.close();
    }

    @Test
    public void testStringArrayAttributeLengthFitsValue()
    {
        final File file = new File(workingDirectory, "stringArrayAttributeLengthFitsValue.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer w = HDF5Factory.open(file);
        w.string().setArrayAttr("/", "a", new String[]
            { "12", "a\0c", "QWERTY" });
        w.close();
        final IHDF5Reader r = HDF5Factory.openForReading(file);
        final String[] s0Term = r.string().getArrayAttr("/", "a");
        final String[] sFixed = r.string().getArrayAttrRaw("/", "a");
        assertTrue(Arrays.equals(new String[]
            { "12", "a", "QWERTY" }, s0Term));
        assertTrue(Arrays.equals(new String[]
            { "12\0\0\0\0", "a\0c\0\0\0", "QWERTY" }, sFixed));
        r.close();
    }

    @Test
    public void testStringArrayAttributeFixedLength()
    {
        final File file = new File(workingDirectory, "stringArrayAttributeFixedLength.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer w = HDF5Factory.open(file);
        w.string().setArrayAttr("/", "a", new String[]
            { "12", "a\0c", "QWERTY" }, 7);
        w.close();
        final IHDF5Reader r = HDF5Factory.openForReading(file);
        final String[] s0Term = r.string().getArrayAttr("/", "a");
        final String[] sFixed = r.string().getArrayAttrRaw("/", "a");
        assertTrue(Arrays.equals(new String[]
            { "12", "a", "QWERTY" }, s0Term));
        assertTrue(Arrays.equals(new String[]
            { "12\0\0\0\0\0", "a\0c\0\0\0\0", "QWERTY\0" }, sFixed));
        r.close();
    }

    @Test
    public void testStringArrayAttributeUTF8LengthFitsValue()
    {
        final File file = new File(workingDirectory, "stringArrayAttributeUTF8LengthFitsValue.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer w = HDF5Factory.configure(file).useUTF8CharacterEncoding().writer();
        w.string().setArrayAttr("/", "a", new String[]
            { "12", "a\0c", "QWERTY" });
        w.close();
        final IHDF5Reader r = HDF5Factory.openForReading(file);
        final String[] b = r.string().getArrayAttrRaw("/", "a");
        assertTrue(Arrays.equals(new String[]
            { "12\0\0\0\0", "a\0c\0\0\0", "QWERTY" }, b));
        r.close();
    }

    @Test
    public void testStringArrayAttributeUTF8FixedLength()
    {
        final File file = new File(workingDirectory, "stringArrayAttributeUTF8FixedLength.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer w = HDF5Factory.configure(file).useUTF8CharacterEncoding().writer();
        w.string().setArrayAttr("/", "a", new String[]
            { "12", "a\0c", "QWERTY" }, 7);
        w.close();
        final IHDF5Reader r = HDF5Factory.openForReading(file);
        final String[] sFixed = r.string().getArrayAttrRaw("/", "a");
        final String[] s0Term = r.string().getArrayAttr("/", "a");
        assertTrue(Arrays.equals(new String[]
            { StringUtils.rightPad("12", 7 * 4, '\0'), StringUtils.rightPad("a\0c", 7 * 4, '\0'),
                    StringUtils.rightPad("QWERTY", 7 * 4, '\0') }, sFixed));
        assertTrue(Arrays.equals(new String[]
            { "12", "a", "QWERTY" }, s0Term));
        r.close();
    }

    @Test
    public void testStringMDArrayAttributeFixedLength()
    {
        final File file = new File(workingDirectory, "stringMDArrayAttributeFixedLength.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer w = HDF5Factory.open(file);
        final MDArray<String> array = new MDArray<String>(new String[]
            { "12", "a\0c", "QWERTY", "" }, new int[]
            { 2, 2 });
        w.string().setMDArrayAttr("/", "a", array, 7);
        w.close();
        final IHDF5Reader r = HDF5Factory.openForReading(file);
        final MDArray<String> s0Term = r.string().getMDArrayAttr("/", "a");
        final MDArray<String> sFixed = r.string().getMDArrayAttrRaw("/", "a");
        assertEquals(new MDArray<String>(new String[]
            { "12", "a", "QWERTY", "" }, new int[]
            { 2, 2 }), s0Term);
        assertEquals(new MDArray<String>(new String[]
            { "12\0\0\0\0\0", "a\0c\0\0\0\0", "QWERTY\0", "\0\0\0\0\0\0\0" }, new int[]
            { 2, 2 }), sFixed);
        r.close();
    }

    @Test
    public void testStringMDArrayAttributeUTF8LengthFitsValue()
    {
        final File file =
                new File(workingDirectory, "stringMDArrayAttributeUTF8LengthFitsValue.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer w = HDF5Factory.configure(file).useUTF8CharacterEncoding().writer();
        final MDArray<String> array = new MDArray<String>(new String[]
            { "\u00b6\u00bc\u09ab", "a\0c", "QWERTY", "" }, new int[]
            { 2, 2 });
        w.string().setMDArrayAttr("/", "a", array);
        w.close();
        final IHDF5Reader r = HDF5Factory.openForReading(file);
        final MDArray<String> b1 = r.string().getMDArrayAttr("/", "a");
        assertEquals(new MDArray<String>(new String[]
            { "\u00b6\u00bc\u09ab", "a", "QWERTY", "" }, new int[]
            { 2, 2 }), b1);
        final MDArray<String> b2 = r.string().getMDArrayAttrRaw("/", "a");
        assertEquals(new MDArray<String>(new String[]
            { "\u00b6\u00bc\u09ab", "a\0c\0\0\0\0", "QWERTY\0", "\0\0\0\0\0\0\0" }, new int[]
            { 2, 2 }), b2);
        r.close();
    }

    @Test
    public void testStringMDArrayAttributeUTF8FixedLength()
    {
        final File file = new File(workingDirectory, "stringMDArrayAttributeUTF8FixedLength.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer w = HDF5Factory.configure(file).useUTF8CharacterEncoding().writer();
        final MDArray<String> array = new MDArray<String>(new String[]
            { "\u00b6\u00bc\u09ab", "a\0c", "QWERTY", "" }, new int[]
            { 2, 2 });
        w.string().setMDArrayAttr("/", "a", array, 7);
        w.close();
        final IHDF5Reader r = HDF5Factory.openForReading(file);
        final MDArray<String> b1 = r.string().getMDArrayAttr("/", "a");
        assertEquals(new MDArray<String>(new String[]
            { "\u00b6\u00bc\u09ab", "a", "QWERTY", "" }, new int[]
            { 2, 2 }), b1);
        final MDArray<String> b2 = r.string().getMDArrayAttrRaw("/", "a");
        // Note: the first string contains 28 bytes, but uses 7 bytes to encode 3 characters, thus
        // it has only 28 - (7-3) = 24 characters.
        assertEquals(
                new MDArray<String>(new String[]
                    { StringUtils.rightPad("\u00b6\u00bc\u09ab", 7 * 4 - 4, '\0'),
                            StringUtils.rightPad("a\0c", 7 * 4, '\0'),
                            StringUtils.rightPad("QWERTY", 7 * 4, '\0'),
                            StringUtils.rightPad("", 7 * 4, '\0') }, new int[]
                    { 2, 2 }), b2);
        r.close();
    }

    @Test
    public void testStringCompact()
    {
        final File stringCompactFile = new File(workingDirectory, "stringCompact.h5");
        stringCompactFile.delete();
        assertFalse(stringCompactFile.exists());
        stringCompactFile.deleteOnExit();
        IHDF5Writer writer = HDF5FactoryProvider.get().configure(stringCompactFile).writer();
        final String smallData = "abc1234";
        final String dataSetName1 = "/aString";
        writer.string().write(dataSetName1, smallData, HDF5GenericStorageFeatures.GENERIC_COMPACT);
        final String dataSetName2 = "/anotherString";
        final String largeData = StringUtils.repeat("a", 64 * 1024 - 13);
        writer.string().write(dataSetName2, largeData, HDF5GenericStorageFeatures.GENERIC_COMPACT);
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(stringCompactFile);
        final String dataRead1 = reader.readString(dataSetName1);
        assertEquals(HDF5StorageLayout.COMPACT, reader.getDataSetInformation(dataSetName1)
                .getStorageLayout());
        assertEquals(smallData, dataRead1);
        final String dataRead2 = reader.readString(dataSetName2);
        assertEquals(HDF5StorageLayout.COMPACT, reader.getDataSetInformation(dataSetName2)
                .getStorageLayout());
        assertEquals(largeData, dataRead2);
        reader.close();
    }

    @Test
    public void testStringContiguous()
    {
        final File stringCompactFile = new File(workingDirectory, "stringContiguous.h5");
        stringCompactFile.delete();
        assertFalse(stringCompactFile.exists());
        stringCompactFile.deleteOnExit();
        IHDF5Writer writer = HDF5FactoryProvider.get().configure(stringCompactFile).writer();
        final String smallData = "abc1234";
        final String dataSetName1 = "/aString";
        writer.string().write(dataSetName1, smallData,
                HDF5GenericStorageFeatures.GENERIC_CONTIGUOUS);
        final String dataSetName2 = "/anotherString";
        final String largeData = StringUtils.repeat("a", 64 * 1024 - 13);
        writer.string().write(dataSetName2, largeData,
                HDF5GenericStorageFeatures.GENERIC_CONTIGUOUS);
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(stringCompactFile);
        final String dataRead1 = reader.readString(dataSetName1);
        assertEquals(HDF5StorageLayout.CONTIGUOUS, reader.getDataSetInformation(dataSetName1)
                .getStorageLayout());
        assertEquals(smallData, dataRead1);
        final String dataRead2 = reader.readString(dataSetName2);
        assertEquals(HDF5StorageLayout.CONTIGUOUS, reader.getDataSetInformation(dataSetName2)
                .getStorageLayout());
        assertEquals(largeData, dataRead2);
        reader.close();
    }

    @Test
    public void testStringUnicode() throws Exception
    {
        final File stringUnicodeFile = new File(workingDirectory, "stringUnicode.h5");
        stringUnicodeFile.delete();
        assertFalse(stringUnicodeFile.exists());
        stringUnicodeFile.deleteOnExit();
        IHDF5Writer writer =
                HDF5FactoryProvider.get().configure(stringUnicodeFile).dontUseExtendableDataTypes()
                        .useUTF8CharacterEncoding().writer();
        final String uniCodeData = "\u00b6\u00bc\u09ab";
        final String dataSetName = "/aString";
        final String attributeName = "attr1";
        final String uniCodeAttributeData = "\u09bb";
        writer.string().write(dataSetName, uniCodeData);
        writer.string().setAttr(dataSetName, attributeName, uniCodeAttributeData);
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(stringUnicodeFile);
        final String dataRead = reader.readString(dataSetName);
        final String attributeDataRead = reader.string().getAttr(dataSetName, attributeName);
        assertEquals(uniCodeData, dataRead);
        assertEquals(uniCodeAttributeData, attributeDataRead);
        reader.close();
    }

    @Test
    public void testStringArrayCompact()
    {
        final File stringArrayFile = new File(workingDirectory, "stringArrayCompact.h5");
        stringArrayFile.delete();
        assertFalse(stringArrayFile.exists());
        stringArrayFile.deleteOnExit();
        IHDF5Writer writer =
                HDF5FactoryProvider.get().configure(stringArrayFile).dontUseExtendableDataTypes()
                        .writer();
        final String[] data = new String[]
            { "abc1234", "ABCxxxX", "xyzUVWX" };
        final String dataSetName = "/aStringArray";
        writer.writeStringArray(dataSetName, data);
        writer.close();
        writer = HDF5FactoryProvider.get().open(stringArrayFile);
        writer.writeStringArray(dataSetName, new String[]
            { data[0], data[1] });
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(stringArrayFile);
        String[] dataStored = reader.readStringArray(dataSetName);
        assertTrue(Arrays.equals(new String[]
            { data[0], data[1] }, dataStored));
        reader.close();
    }

    @Test
    public void testStringCompression()
    {
        final File compressedStringFile = new File(workingDirectory, "compressedStrings.h5");
        compressedStringFile.delete();
        assertFalse(compressedStringFile.exists());
        compressedStringFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(compressedStringFile);
        final int size = 100000;
        final String dataSetName = "/hopefullyCompressedString";
        final String longMonotonousString = StringUtils.repeat("a", size);
        writer.string().write(dataSetName, longMonotonousString, GENERIC_DEFLATE);
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(compressedStringFile);
        final String longMonotonousStringStored = reader.readString(dataSetName);
        assertEquals(longMonotonousString, longMonotonousStringStored);
        reader.close();
        assertTrue(Long.toString(compressedStringFile.length()),
                compressedStringFile.length() < size / 10);
    }

    @Test
    public void testStringArrayCompression()
    {
        final File compressedStringArrayFile =
                new File(workingDirectory, "compressedStringArray.h5");
        compressedStringArrayFile.delete();
        assertFalse(compressedStringArrayFile.exists());
        compressedStringArrayFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(compressedStringArrayFile);
        final int size = 100000;
        final String longMonotonousString = StringUtils.repeat("a", size);
        final String[] data = new String[]
            { longMonotonousString, longMonotonousString, longMonotonousString };
        final String dataSetName = "/aHopeFullyCompressedStringArray";
        writer.string().writeArray(dataSetName, data, size, GENERIC_DEFLATE);
        writer.close();
        final IHDF5Reader reader =
                HDF5FactoryProvider.get().openForReading(compressedStringArrayFile);
        final String[] dataStored = reader.readStringArray(dataSetName);
        assertTrue(Arrays.equals(data, dataStored));
        reader.close();
        assertTrue(Long.toString(compressedStringArrayFile.length()),
                compressedStringArrayFile.length() < 3 * size / 10);
    }

    @Test
    public void testStringVLArray()
    {
        final File compressedStringArrayFile = new File(workingDirectory, "StringVLArray.h5");
        compressedStringArrayFile.delete();
        assertFalse(compressedStringArrayFile.exists());
        compressedStringArrayFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(compressedStringArrayFile);
        final int size = 100000;
        final String longMonotonousString = StringUtils.repeat("a", size);
        final String[] data = new String[]
            { longMonotonousString, longMonotonousString, longMonotonousString };
        final String dataSetName = "/aHopeFullyCompressedStringArray";
        writer.string().writeArrayVL(dataSetName, data);
        writer.close();
        final IHDF5Reader reader =
                HDF5FactoryProvider.get().openForReading(compressedStringArrayFile);
        final String[] dataStored = reader.readStringArray(dataSetName);
        assertTrue(Arrays.equals(data, dataStored));
        assertTrue(reader.getDataSetInformation(dataSetName).getTypeInformation()
                .isVariableLengthString());
        reader.close();
    }

    private void assertMatrixEquals(final float[][] floatMatrixWritten,
            final float[][] floatMatrixRead)
    {
        assertEquals(floatMatrixWritten.length, floatMatrixRead.length);
        for (int i = 0; i < floatMatrixWritten.length; ++i)
        {
            assertEquals(floatMatrixWritten[i].length, floatMatrixRead[i].length);
            for (int j = 0; j < floatMatrixWritten[i].length; ++j)
            {
                assertEquals(i + ":" + j, floatMatrixWritten[i][j], floatMatrixRead[i][j]);
            }
        }
    }

    @Test
    public void testCompressedDataSet()
    {
        final File datasetFile = new File(workingDirectory, "compressed.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(datasetFile);
        final String stringDatasetName = "/compressed";
        final StringBuilder b = new StringBuilder();
        for (int i = 0; i < 10000; ++i)
        {
            b.append("easyToCompress");
        }
        writer.int8().writeArray(stringDatasetName, b.toString().getBytes(), INT_DEFLATE);
        writer.close();
    }

    @Test
    public void testCreateEmptyFloatMatrix()
    {
        final File datasetFile = new File(workingDirectory, "initiallyEmptyFloatMatrix.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        IHDF5Writer writer = HDF5FactoryProvider.get().open(datasetFile);
        final String floatDatasetName = "/emptyMatrix";
        writer.float32().createMatrix(floatDatasetName, 2, 2, FLOAT_CHUNKED);
        writer.close();
        writer = HDF5FactoryProvider.get().open(datasetFile);
        float[][] floatMatrixRead = writer.float32().readMatrix(floatDatasetName);
        assertEquals(0, floatMatrixRead.length);

        // No write a non-empty matrix
        float[][] floatMatrixWritten = new float[][]
            {
                { 1f, 2f, 3f },
                { 4f, 5f, 6f },
                { 7f, 8f, 9f } };
        writer.float32().writeMatrix(floatDatasetName, floatMatrixWritten);
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        floatMatrixRead = reader.float32().readMatrix(floatDatasetName);
        assertTrue(equals(floatMatrixWritten, floatMatrixRead));
        reader.close();
    }

    @Test
    public void testFloatVectorLength1()
    {
        final File datasetFile = new File(workingDirectory, "singleFloatVector.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(datasetFile);
        final String floatDatasetName = "/singleFloat";
        final float[] floatDataWritten = new float[]
            { 1.0f };
        writer.float32().writeArray(floatDatasetName, floatDataWritten);
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        reader.object().hasAttribute(floatDatasetName, "flag");
        final float[] floatDataRead = reader.float32().readArray(floatDatasetName);
        assertTrue(Arrays.equals(floatDataWritten, floatDataRead));
        reader.close();
    }

    @Test
    public void testFloatMatrixLength1()
    {
        final File datasetFile = new File(workingDirectory, "singleFloatMatrix.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(datasetFile);
        final String floatDatasetName = "/singleFloat";
        final float[][] floatDataWritten = new float[][]
            {
                { 1.0f } };
        writer.float32().writeMatrix(floatDatasetName, floatDataWritten);
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        final float[][] floatDataRead = reader.float32().readMatrix(floatDatasetName);
        assertTrue(equals(floatDataWritten, floatDataRead));
        reader.close();
    }

    @Test
    public void testOneRowFloatMatrix()
    {
        final File datasetFile = new File(workingDirectory, "oneRowFloatMatrix.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(datasetFile);
        final String floatDatasetName = "/singleFloat";
        final float[][] floatDataWritten = new float[][]
            {
                { 1.0f, 2.0f } };
        writer.float32().writeMatrix(floatDatasetName, floatDataWritten);
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        final float[][] floatDataRead = reader.float32().readMatrix(floatDatasetName);
        assertTrue(equals(floatDataWritten, floatDataRead));
        reader.close();
    }

    private static boolean equals(float[][] a, float[][] a2)
    {
        if (a == a2)
        {
            return true;
        }
        if (a == null || a2 == null)
        {
            return false;
        }

        int rows = a.length;
        if (a2.length != rows)
        {
            return false;
        }

        for (int i = 0; i < rows; i++)
        {
            int columns = a[i].length;
            if (a2[i].length != columns)
            {
                return false;
            }
            for (int j = 0; j < columns; j++)
            {
                if (Float.floatToIntBits(a[i][j]) != Float.floatToIntBits(a2[i][j]))
                {
                    return false;
                }
            }
        }

        return true;
    }

    @Test
    public void testEmptyVectorDataSets()
    {
        final File datasetFile = new File(workingDirectory, "emptyVectorDatasets.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(datasetFile);
        final String floatDatasetName = "/float";
        writer.float32().writeArray(floatDatasetName, new float[0]);
        final String doubleDatasetName = "/double";
        writer.float64().writeArray(doubleDatasetName, new double[0]);
        final String byteDatasetName = "byte";
        writer.int8().writeArray(byteDatasetName, new byte[0]);
        final String shortDatasetName = "/short";
        writer.int16().writeArray(shortDatasetName, new short[0]);
        final String intDatasetName = "/int";
        writer.int32().writeArray(intDatasetName, new int[0]);
        final String longDatasetName = "/long";
        writer.int64().writeArray(longDatasetName, new long[0]);
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        assertEquals(HDF5ObjectType.DATASET, reader.object().getObjectType(floatDatasetName));
        assertTrue(reader.float32().readArray(floatDatasetName).length == 0);
        assertTrue(reader.float64().readArray(doubleDatasetName).length == 0);
        assertTrue(reader.int8().readArray(byteDatasetName).length == 0);
        assertTrue(reader.int16().readArray(shortDatasetName).length == 0);
        assertTrue(reader.int32().readArray(intDatasetName).length == 0);
        assertTrue(reader.int64().readArray(longDatasetName).length == 0);
        reader.close();
    }

    @Test
    public void testEmptyVectorDataSetsContiguous()
    {
        final File datasetFile = new File(workingDirectory, "emptyVectorDatasetsContiguous.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        final IHDF5Writer writer =
                HDF5FactoryProvider.get().configure(datasetFile).dontUseExtendableDataTypes()
                        .writer();
        final String floatDatasetName = "/float";
        writer.float32().writeArray(floatDatasetName, new float[0]);
        final String doubleDatasetName = "/double";
        writer.float64().writeArray(doubleDatasetName, new double[0]);
        final String byteDatasetName = "byte";
        writer.int8().writeArray(byteDatasetName, new byte[0]);
        final String shortDatasetName = "/short";
        writer.int16().writeArray(shortDatasetName, new short[0]);
        final String intDatasetName = "/int";
        writer.int32().writeArray(intDatasetName, new int[0]);
        final String longDatasetName = "/long";
        writer.int64().writeArray(longDatasetName, new long[0]);
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        assertEquals(HDF5ObjectType.DATASET, reader.object().getObjectType(floatDatasetName));
        assertTrue(reader.float32().readArray(floatDatasetName).length == 0);
        assertTrue(reader.float64().readArray(doubleDatasetName).length == 0);
        assertTrue(reader.int8().readArray(byteDatasetName).length == 0);
        assertTrue(reader.int16().readArray(shortDatasetName).length == 0);
        assertTrue(reader.int32().readArray(intDatasetName).length == 0);
        assertTrue(reader.int64().readArray(longDatasetName).length == 0);
        reader.close();
    }

    @Test
    public void testEmptyVectorDataSetsCompact()
    {
        final File datasetFile = new File(workingDirectory, "emptyVectorDatasetsCompact.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(datasetFile);
        final String floatDatasetName = "/float";
        writer.float32().writeArray(floatDatasetName, new float[0],
                HDF5FloatStorageFeatures.FLOAT_COMPACT);
        final String doubleDatasetName = "/double";
        writer.float64().writeArray(doubleDatasetName, new double[0],
                HDF5FloatStorageFeatures.FLOAT_COMPACT);
        final String byteDatasetName = "byte";
        writer.int8().writeArray(byteDatasetName, new byte[0], HDF5IntStorageFeatures.INT_COMPACT);
        final String shortDatasetName = "/short";
        writer.int16().writeArray(shortDatasetName, new short[0],
                HDF5IntStorageFeatures.INT_COMPACT);
        final String intDatasetName = "/int";
        writer.int32().writeArray(intDatasetName, new int[0], HDF5IntStorageFeatures.INT_COMPACT);
        final String longDatasetName = "/long";
        writer.int64().writeArray(longDatasetName, new long[0], HDF5IntStorageFeatures.INT_COMPACT);
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        assertEquals(HDF5ObjectType.DATASET, reader.object().getObjectType(floatDatasetName));
        assertTrue(reader.float32().readArray(floatDatasetName).length == 0);
        assertTrue(reader.float64().readArray(doubleDatasetName).length == 0);
        assertTrue(reader.int8().readArray(byteDatasetName).length == 0);
        assertTrue(reader.int16().readArray(shortDatasetName).length == 0);
        assertTrue(reader.int32().readArray(intDatasetName).length == 0);
        assertTrue(reader.int64().readArray(longDatasetName).length == 0);
        reader.close();
    }

    @Test
    public void testEmptyMatrixDataSets()
    {
        final File datasetFile = new File(workingDirectory, "emptyMatrixDatasets.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(datasetFile);
        final String floatDatasetName = "/float";
        writer.float32().writeMatrix(floatDatasetName, new float[0][0]);
        final String doubleDatasetName = "/double";
        writer.float64().writeMatrix(doubleDatasetName, new double[1][0]);
        final String byteDatasetName = "byte";
        writer.int8().writeMatrix(byteDatasetName, new byte[2][0]);
        final String shortDatasetName = "/short";
        writer.int16().writeMatrix(shortDatasetName, new short[3][0]);
        final String intDatasetName = "/int";
        writer.int32().writeMatrix(intDatasetName, new int[4][0]);
        final String longDatasetName = "/long";
        writer.int64().writeMatrix(longDatasetName, new long[5][0]);
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        assertTrue(isEmpty(reader.float32().readMatrix(floatDatasetName)));
        assertTrue(isEmpty(reader.float64().readMatrix(doubleDatasetName)));
        assertTrue(isEmpty(reader.int8().readMatrix(byteDatasetName)));
        assertTrue(isEmpty(reader.int16().readMatrix(shortDatasetName)));
        assertTrue(isEmpty(reader.int32().readMatrix(intDatasetName)));
        assertTrue(isEmpty(reader.int64().readMatrix(longDatasetName)));
        reader.close();
    }

    @Test
    public void testEmptyMatrixDataSetsContiguous()
    {
        final File datasetFile = new File(workingDirectory, "emptyMatrixDatasetsContiguous.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        final IHDF5Writer writer =
                HDF5FactoryProvider.get().configure(datasetFile).dontUseExtendableDataTypes()
                        .writer();
        final String floatDatasetName = "/float";
        writer.float32().writeMatrix(floatDatasetName, new float[0][0]);
        final String doubleDatasetName = "/double";
        writer.float64().writeMatrix(doubleDatasetName, new double[1][0]);
        final String byteDatasetName = "byte";
        writer.int8().writeMatrix(byteDatasetName, new byte[2][0]);
        final String shortDatasetName = "/short";
        writer.int16().writeMatrix(shortDatasetName, new short[3][0]);
        final String intDatasetName = "/int";
        writer.int32().writeMatrix(intDatasetName, new int[4][0]);
        final String longDatasetName = "/long";
        writer.int64().writeMatrix(longDatasetName, new long[5][0]);
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        assertTrue(isEmpty(reader.float32().readMatrix(floatDatasetName)));
        assertTrue(isEmpty(reader.float64().readMatrix(doubleDatasetName)));
        assertTrue(isEmpty(reader.int8().readMatrix(byteDatasetName)));
        assertTrue(isEmpty(reader.int16().readMatrix(shortDatasetName)));
        assertTrue(isEmpty(reader.int32().readMatrix(intDatasetName)));
        assertTrue(isEmpty(reader.int64().readMatrix(longDatasetName)));
        reader.close();
    }

    @Test
    public void testOverwriteVectorIncreaseSize()
    {
        final File datasetFile = new File(workingDirectory, "resizableVector.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        IHDF5Writer writer = HDF5FactoryProvider.get().open(datasetFile);
        final String dsName = "/vector";
        final float[] firstVector = new float[]
            { 1f, 2f, 3f };
        writer.float32().writeArray(dsName, firstVector);
        writer.close();
        writer = HDF5FactoryProvider.get().open(datasetFile);
        final float[] secondVector = new float[]
            { 1f, 2f, 3f, 4f };
        writer.float32().writeArray(dsName, secondVector);
        writer.close();
        IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        final float[] vectorRead = reader.float32().readArray(dsName);
        reader.close();
        assertTrue(Arrays.equals(secondVector, vectorRead));
    }

    @Test
    public void testOverwriteWithEmptyVector()
    {
        final File datasetFile = new File(workingDirectory, "overwriteVector1.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        IHDF5Writer writer = HDF5FactoryProvider.get().open(datasetFile);
        final String dsName = "/vector";
        final byte[] firstVector = new byte[]
            { 1, 2, 3 };
        writer.int8().writeArray(dsName, firstVector);
        writer.close();
        writer = HDF5FactoryProvider.get().open(datasetFile);
        final byte[] emptyVector = new byte[0];
        writer.int8().writeArray(dsName, emptyVector);
        writer.close();
        IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        final byte[] vectorRead = reader.int8().readArray(dsName);
        reader.close();
        assertTrue(Arrays.equals(emptyVector, vectorRead));
    }

    @Test
    public void testOverwriteEmptyVectorWithNonEmptyVector()
    {
        final File datasetFile = new File(workingDirectory, "overwriteVector2.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        IHDF5Writer writer = HDF5FactoryProvider.get().open(datasetFile);
        final String dsName = "/vector";
        final byte[] emptyVector = new byte[0];
        writer.int8().writeArray(dsName, emptyVector);
        writer.close();
        writer = HDF5FactoryProvider.get().open(datasetFile);
        final byte[] nonEmptyVector = new byte[]
            { 1 };
        writer.int8().writeArray(dsName, nonEmptyVector);
        writer.close();
        IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        final byte[] vectorRead = reader.int8().readArray(dsName);
        reader.close();
        assertTrue(Arrays.equals(nonEmptyVector, vectorRead));
    }

    @Test
    public void testDeleteVector()
    {
        final File datasetFile = new File(workingDirectory, "deleteVector.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        IHDF5Writer writer = HDF5FactoryProvider.get().open(datasetFile);
        try
        {
            final String dsName = "/vector";
            final byte[] firstVector = new byte[]
                { 1, 2, 3 };
            writer.int8().writeArray(dsName, firstVector);
            writer.close();
            writer = HDF5FactoryProvider.get().open(datasetFile);
            writer.delete(dsName.substring(1));
        } finally
        {
            writer.close();
        }
        IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        try
        {
            final List<String> members = reader.object().getAllGroupMembers("/");
            assertEquals(1, members.size());
            assertEquals(HDF5Utils.getDataTypeGroup("").substring(1), members.get(0));
        } finally
        {
            reader.close();
        }
    }

    @Test
    public void testDeleteGroup()
    {
        final File datasetFile = new File(workingDirectory, "deleteGroup.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        IHDF5Writer writer = HDF5FactoryProvider.get().open(datasetFile);
        try
        {
            final String groupName = "/group";
            final String dsName = groupName + "/vector";
            final byte[] firstVector = new byte[]
                { 1, 2, 3 };
            writer.int8().writeArray(dsName, firstVector);
            writer.close();
            writer = HDF5FactoryProvider.get().open(datasetFile);
            writer.delete(groupName);
        } finally
        {
            writer.close();
        }
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        try
        {
            final List<String> members = reader.object().getAllGroupMembers("/");
            assertEquals(1, members.size());
            assertEquals(HDF5Utils.getDataTypeGroup("").substring(1), members.get(0));
            assertEquals(0, reader.getGroupMembers("/").size());
        } finally
        {
            reader.close();
        }
    }

    @Test
    public void testRenameLink()
    {
        final File file = new File(workingDirectory, "renameLink.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(file);
        writer.writeBoolean("/some/boolean/value", true);
        writer.object().move("/some/boolean/value", "/a/new/home");
        assertFalse(writer.exists("/home/boolean/value"));
        assertTrue(writer.exists("/a/new/home"));
        writer.close();
    }

    @Test(expectedExceptions = HDF5LibraryException.class)
    public void testRenameLinkOverwriteFails()
    {
        final File file = new File(workingDirectory, "renameLinkOverwriteFails.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        try (final IHDF5Writer writer = HDF5FactoryProvider.get().open(file))
        {
            writer.writeBoolean("/some/boolean/value", true);
            writer.int32().write("/a/new/home", 4);
            // This will fail.
            writer.object().move("/some/boolean/value", "/a/new/home");
        }
    }

    @Test(expectedExceptions = HDF5SymbolTableException.class)
    public void testRenameLinkSrcNonExistentFails()
    {
        final File file = new File(workingDirectory, "renameLinkSrcNonExistentFails.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        try (final IHDF5Writer writer = HDF5FactoryProvider.get().open(file))
        {
            writer.object().move("/some/boolean/value", "/a/new/home");
        }
    }

    @Test
    public void testOverwriteKeepWithEmptyString()
    {
        final File datasetFile = new File(workingDirectory, "overwriteWithEmtpyString.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        IHDF5Writer writer = HDF5FactoryProvider.get().open(datasetFile);
        final String dsName = "/string";
        writer.string().write(dsName, "non-empty");
        writer.close();
        writer =
                HDF5FactoryProvider.get().configure(datasetFile).keepDataSetsIfTheyExist().writer();
        writer.string().write(dsName, "");
        writer.close();
        IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        final String stringRead = reader.readString(dsName);
        reader.close();
        assertEquals("", stringRead);
    }

    @Test
    public void testOverwriteKeepWithShorterString()
    {
        final File datasetFile = new File(workingDirectory, "overwriteWithShorterString.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        IHDF5Writer writer = HDF5FactoryProvider.get().open(datasetFile);
        final String dsName = "/string";
        writer.string().write(dsName, "non-empty");
        writer.close();
        writer =
                HDF5FactoryProvider.get().configure(datasetFile).keepDataSetsIfTheyExist().writer();
        writer.string().write(dsName, "non");
        writer.close();
        IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        final String stringRead = reader.readString(dsName);
        reader.close();
        assertEquals("non", stringRead);
    }

    @Test
    public void testOverwriteKeepWithLongerString()
    {
        final File datasetFile = new File(workingDirectory, "overwriteWithLongerString.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        IHDF5Writer writer = HDF5FactoryProvider.get().open(datasetFile);
        final String dsName = "/string";
        writer.string().write(dsName, "non-empty");
        writer.close();
        writer =
                HDF5FactoryProvider.get().configure(datasetFile).keepDataSetsIfTheyExist().writer();
        writer.string().write(dsName, "0123456789");
        writer.close();
        IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        final String stringRead = reader.readString(dsName);
        reader.close();
        assertEquals("012345678", stringRead);
    }

    @Test
    public void testReplaceWithLongerString()
    {
        final File datasetFile = new File(workingDirectory, "replaceWithLongerString.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        IHDF5Writer writer = HDF5FactoryProvider.get().open(datasetFile);
        final String dsName = "/string";
        writer.string().write(dsName, "non-empty");
        writer.close();
        writer = HDF5FactoryProvider.get().open(datasetFile);
        writer.string().write(dsName, "0123456789");
        writer.close();
        IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        final String stringRead = reader.readString(dsName);
        reader.close();
        assertEquals("0123456789", stringRead);
    }

    @Test
    public void testOverwriteMatrixIncreaseSize()
    {
        final File datasetFile = new File(workingDirectory, "resizableMatrix.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        IHDF5Writer writer = HDF5FactoryProvider.get().open(datasetFile);
        final String dsName = "/matrix";
        final float[][] firstMatrix = new float[][]
            {
                { 1f, 2f, 3f },
                { 4f, 5f, 6f } };
        writer.float32().writeMatrix(dsName, firstMatrix);
        writer.close();
        writer = HDF5FactoryProvider.get().open(datasetFile);
        final float[][] secondMatrix = new float[][]
            {
                { 1f, 2f, 3f, 4f },
                { 5f, 6f, 7f, 8f },
                { 9f, 10f, 11f, 12f } };
        writer.float32().writeMatrix(dsName, secondMatrix);
        writer.close();
        IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        final float[][] matrixRead = reader.float32().readMatrix(dsName);
        reader.close();
        assertMatrixEquals(secondMatrix, matrixRead);
    }

    @Test
    public void testOverwriteStringVectorDecreaseSize()
    {
        final File datasetFile = new File(workingDirectory, "resizableStringVector.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        IHDF5Writer writer = HDF5FactoryProvider.get().open(datasetFile);
        final String dsName = "/vector";
        final String[] firstVector = new String[]
            { "a", "b", "c" };
        writer.writeStringArray(dsName, firstVector);
        writer.close();
        writer = HDF5FactoryProvider.get().open(datasetFile);
        final String[] secondVector = new String[]
            { "a", "b" };
        writer.writeStringArray(dsName, secondVector);
        writer.close();
        IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        final String[] vectorRead = reader.readStringArray(dsName);
        reader.close();
        assertTrue(Arrays.equals(secondVector, vectorRead));
    }

    private static boolean isEmpty(Object matrix)
    {
        Object maybeAnArray = matrix;
        do
        {
            if (Array.getLength(maybeAnArray) == 0)
            {
                return true;
            }
            maybeAnArray = Array.get(maybeAnArray, 0);
        } while (maybeAnArray.getClass().isArray());
        return false;
    }

    @Test
    public void testStringArrayWithNullStrings()
    {
        final File datasetFile = new File(workingDirectory, "stringArrayWithNullStrings.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        IHDF5Writer writer = HDF5FactoryProvider.get().open(datasetFile);
        final String dsName = "/vector";
        final String[] array = new String[]
            { "a\0c", "b", "" };
        writer.writeStringArray(dsName, array);
        writer.close();
        IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        final String[] arrayRead = reader.string().readArrayRaw(dsName);
        reader.close();
        assertEquals(array.length, arrayRead.length);
        assertEquals("a\0c", arrayRead[0]);
        assertEquals(StringUtils.rightPad("b", 3, '\0'), arrayRead[1]);
        assertEquals(StringUtils.rightPad("", 3, '\0'), arrayRead[2]);
    }

    @Test
    public void testStringMDArrayWithNullStrings()
    {
        final File datasetFile = new File(workingDirectory, "stringMDArrayWithNullStrings.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        IHDF5Writer writer = HDF5FactoryProvider.get().open(datasetFile);
        final String dsName = "/vector";
        final String[] array = new String[]
            { "a\0c", "b", "", "\000123456" };
        final MDArray<String> mdArray = new MDArray<String>(array, new int[]
            { 2, 2 });
        writer.string().writeMDArray(dsName, mdArray);
        writer.close();
        IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        final MDArray<String> arrayRead = reader.string().readMDArrayRaw(dsName);
        assertTrue(Arrays.equals(mdArray.dimensions(), arrayRead.dimensions()));
        assertEquals(StringUtils.rightPad(mdArray.get(0, 0), 7, '\0'), arrayRead.get(0, 0));
        assertEquals(StringUtils.rightPad(mdArray.get(0, 1), 7, '\0'), arrayRead.get(0, 1));
        assertEquals(StringUtils.rightPad(mdArray.get(1, 0), 7, '\0'), arrayRead.get(1, 0));
        assertEquals(StringUtils.rightPad(mdArray.get(1, 1), 7, '\0'), arrayRead.get(1, 1));
        assertEquals(2, reader.object().getRank(dsName));
        assertTrue(Arrays.equals(new long[]
            { 2, 2 }, reader.object().getDimensions(dsName)));
        reader.close();
    }

    @Test
    public void testTimestamps()
    {
        final File datasetFile = new File(workingDirectory, "timestamps.h5");
        final String timeStampDS = "prehistoric";
        final long timestampValue = 10000L;
        final String noTimestampDS = "notatimestamp";
        final long someLong = 173756123L;
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(datasetFile);
        writer.time().write(timeStampDS, timestampValue);
        writer.int64().write(noTimestampDS, someLong);
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        assertEquals(HDF5DataTypeVariant.TIMESTAMP_MILLISECONDS_SINCE_START_OF_THE_EPOCH, reader
                .object().tryGetTypeVariant(timeStampDS));
        final HDF5DataSetInformation info = reader.getDataSetInformation(timeStampDS);
        assertTrue(info.isScalar());
        assertEquals(HDF5StorageLayout.COMPACT, info.getStorageLayout());
        assertNull(info.tryGetChunkSizes());
        assertEquals(HDF5DataClass.INTEGER, info.getTypeInformation().getDataClass());
        assertTrue(info.isTimeStamp());
        assertFalse(info.isTimeDuration());
        assertEquals(HDF5DataTypeVariant.TIMESTAMP_MILLISECONDS_SINCE_START_OF_THE_EPOCH,
                info.tryGetTypeVariant());
        assertEquals(HDF5DataTypeVariant.TIMESTAMP_MILLISECONDS_SINCE_START_OF_THE_EPOCH, info
                .getTypeInformation().tryGetTypeVariant());
        assertEquals(timestampValue, reader.time().readTimeStamp(timeStampDS));
        assertEquals(timestampValue, reader.time().readDate(timeStampDS).getTime());
        try
        {
            reader.time().readTimeStamp(noTimestampDS);
            fail("Failed to detect non-timestamp value.");
        } catch (HDF5JavaException ex)
        {
            if (ex.getMessage().contains("not a time stamp") == false)
            {
                throw ex;
            }
            // That is what we expect.
        }
        reader.close();
    }

    @Test
    public void testTimestampArray()
    {
        final File datasetFile = new File(workingDirectory, "timestampArray.h5");
        final String timeSeriesDS = "/some/timeseries";
        final long[] timeSeries = new long[10];
        for (int i = 0; i < timeSeries.length; ++i)
        {
            timeSeries[i] = i * 10000L;
        }
        final long[] notATimeSeries = new long[100];
        final String noTimeseriesDS = "nota/timeseries";
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(datasetFile);
        writer.time().writeArray(timeSeriesDS, timeSeries);
        writer.int64().writeArray(noTimeseriesDS, notATimeSeries);
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        final HDF5DataSetInformation info = reader.getDataSetInformation(timeSeriesDS);
        assertEquals(HDF5DataTypeVariant.TIMESTAMP_MILLISECONDS_SINCE_START_OF_THE_EPOCH,
                info.tryGetTypeVariant());
        assertChunkSizes(info, 10);
        assertTrue(Arrays.equals(timeSeries, reader.time().readTimeStampArray(timeSeriesDS)));
        final Date[] datesRead = reader.readDateArray(timeSeriesDS);
        final long[] timeStampsRead = new long[datesRead.length];
        for (int i = 0; i < timeStampsRead.length; ++i)
        {
            timeStampsRead[i] = datesRead[i].getTime();
        }
        assertTrue(Arrays.equals(timeSeries, timeStampsRead));
        try
        {
            reader.time().readTimeStampArray(noTimeseriesDS);
            fail("Failed to detect non-timestamp array.");
        } catch (HDF5JavaException ex)
        {
            if (ex.getMessage().contains("not a time stamp") == false)
            {
                throw ex;
            }
            // That is what we expect.
        }
        reader.close();
    }

    @Test
    public void testTimestampArrayChunked()
    {
        final File datasetFile = new File(workingDirectory, "timestampArrayChunked.h5");
        final String timeSeriesDS = "/some/timeseries";
        final long[] timeSeries = new long[10];
        for (int i = 0; i < timeSeries.length; ++i)
        {
            timeSeries[i] = i * 10000L;
        }
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(datasetFile);
        writer.time().createArray(timeSeriesDS, 0, 10, GENERIC_DEFLATE);
        for (int i = 0; i < 10; ++i)
        {
            writer.time().writeArrayBlock(timeSeriesDS, timeSeries, i);
        }
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        final HDF5DataSetInformation info = reader.getDataSetInformation(timeSeriesDS);
        assertEquals(HDF5DataTypeVariant.TIMESTAMP_MILLISECONDS_SINCE_START_OF_THE_EPOCH,
                info.tryGetTypeVariant());
        assertChunkSizes(info, 10);
        for (int i = 0; i < 10; ++i)
        {
            assertTrue(Arrays.equals(timeSeries,
                    reader.time().readTimeStampArrayBlock(timeSeriesDS, 10, i)));
        }
        reader.close();
    }

    @Test
    public void testTimeDurations()
    {
        final File datasetFile = new File(workingDirectory, "timedurations.h5");
        final String timeDurationDS = "someDuration";
        final String timeDurationDS2 = "someOtherDuration";
        final long timeDurationInSeconds = 10000L;
        final long timeDurationInMilliSeconds = 10000L * 1000L;
        final long timeDurationInHoursRounded = 3L;
        final String noTimestampDS = "notatimeduration";
        final long someLong = 173756123L;
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(datasetFile);
        writer.duration().write(timeDurationDS, timeDurationInSeconds, HDF5TimeUnit.SECONDS);
        final HDF5TimeDuration timeDurationWithUnit =
                new HDF5TimeDuration(timeDurationInHoursRounded, HDF5TimeUnit.HOURS);
        writer.writeTimeDuration(timeDurationDS2, timeDurationWithUnit);
        writer.int64().write(noTimestampDS, someLong);
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        final HDF5DataSetInformation info = reader.getDataSetInformation(timeDurationDS);
        assertTrue(info.isScalar());
        assertEquals(HDF5StorageLayout.COMPACT, info.getStorageLayout());
        assertNull(info.tryGetChunkSizes());
        assertEquals(HDF5DataClass.INTEGER, info.getTypeInformation().getDataClass());
        assertTrue(info.isTimeDuration());
        assertFalse(info.isTimeStamp());
        assertEquals(HDF5TimeUnit.SECONDS, reader.duration().tryGetTimeUnit(timeDurationDS));
        assertEquals(HDF5DataTypeVariant.TIME_DURATION_SECONDS, info.tryGetTypeVariant());
        assertEquals(HDF5TimeUnit.SECONDS, info.tryGetTimeUnit());
        assertEquals(timeDurationInSeconds,
                HDF5TimeUnit.SECONDS.convert(reader.readTimeDuration(timeDurationDS)));
        assertEquals(timeDurationInMilliSeconds,
                HDF5TimeUnit.MILLISECONDS.convert(reader.readTimeDuration(timeDurationDS)));
        assertEquals(timeDurationInHoursRounded,
                HDF5TimeUnit.HOURS.convert(reader.readTimeDuration(timeDurationDS)));
        assertEquals(new HDF5TimeDuration(timeDurationInSeconds, HDF5TimeUnit.SECONDS),
                reader.readTimeDuration(timeDurationDS));
        assertEquals(timeDurationWithUnit, reader.readTimeDuration(timeDurationDS2));
        try
        {
            reader.readTimeDuration(noTimestampDS);
            fail("Failed to detect non-timeduration value.");
        } catch (HDF5JavaException ex)
        {
            if (ex.getMessage().contains("not a time duration") == false)
            {
                throw ex;
            }
            // That is what we expect.
        }
        reader.close();
    }

    @Test
    public void testSmallTimeDurations()
    {
        final File datasetFile = new File(workingDirectory, "smalltimedurations.h5");
        final String timeDurationDS = "someDuration";
        final short timeDurationInSeconds = 10000;
        final long timeDurationInMilliSeconds = 10000L * 1000L;
        final long timeDurationInHoursRounded = 3L;
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(datasetFile);
        writer.int16().write(timeDurationDS, timeDurationInSeconds);
        writer.object().setTypeVariant(timeDurationDS, HDF5TimeUnit.SECONDS.getTypeVariant());
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        final HDF5DataSetInformation info = reader.getDataSetInformation(timeDurationDS);
        assertTrue(info.isScalar());
        assertEquals(HDF5StorageLayout.COMPACT, info.getStorageLayout());
        assertNull(info.tryGetChunkSizes());
        assertEquals(HDF5DataClass.INTEGER, info.getTypeInformation().getDataClass());
        assertEquals(NativeData.SHORT_SIZE, info.getTypeInformation().getElementSize());
        assertTrue(info.isTimeDuration());
        assertFalse(info.isTimeStamp());
        assertEquals(HDF5TimeUnit.SECONDS, reader.duration().tryGetTimeUnit(timeDurationDS));
        assertEquals(HDF5DataTypeVariant.TIME_DURATION_SECONDS, info.tryGetTypeVariant());
        assertEquals(HDF5TimeUnit.SECONDS, info.tryGetTimeUnit());
        assertEquals(timeDurationInSeconds,
                HDF5TimeUnit.SECONDS.convert(reader.duration().read(timeDurationDS)));
        assertEquals(timeDurationInMilliSeconds,
                HDF5TimeUnit.MILLISECONDS.convert(reader.duration().read(timeDurationDS)));
        assertEquals(timeDurationInHoursRounded,
                HDF5TimeUnit.HOURS.convert(reader.readTimeDuration(timeDurationDS)));
        reader.close();
    }

    @Test
    public void testTimeDurationArray()
    {
        final File datasetFile = new File(workingDirectory, "timedurationarray.h5");
        final String timeDurationDS = "someDuration";
        final HDF5TimeDuration[] durationsWritten =
                new HDF5TimeDuration[]
                    { new HDF5TimeDuration(2, HDF5TimeUnit.SECONDS),
                            new HDF5TimeDuration(5, HDF5TimeUnit.HOURS),
                            new HDF5TimeDuration(1, HDF5TimeUnit.DAYS) };
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(datasetFile);
        writer.writeTimeDurationArray(timeDurationDS,
                HDF5TimeDurationArray.create(durationsWritten));
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        final HDF5DataSetInformation info = reader.getDataSetInformation(timeDurationDS);
        assertTrue(info.isTimeDuration());
        assertFalse(info.isTimeStamp());
        assertEquals(HDF5TimeUnit.SECONDS, info.tryGetTimeUnit());
        final HDF5TimeDurationArray durationsRead = reader.readTimeDurationArray(timeDurationDS);
        assertEquals(durationsWritten.length, durationsRead.getLength());
        for (int i = 0; i < durationsWritten.length; ++i)
        {
            assertTrue(durationsRead.get(i).isEquivalent(durationsWritten[i]));
        }
        assertEquals(new HDF5TimeDurationMDArray(new long[]
            { 2, 18000, 86400 }, new int[]
            { 3 }, HDF5TimeUnit.SECONDS), reader.duration().readMDArray(timeDurationDS));
        reader.close();
    }

    @Test
    public void testTimeDurationMDArray()
    {
        final File datasetFile = new File(workingDirectory, "timedurationarray.h5");
        final String timeDurationDS = "someDuration";
        final HDF5TimeDurationMDArray durationsWritten =
                new HDF5TimeDurationMDArray(new HDF5TimeDuration[]
                    { new HDF5TimeDuration(2, HDF5TimeUnit.SECONDS),
                            new HDF5TimeDuration(4, HDF5TimeUnit.SECONDS),
                            new HDF5TimeDuration(8, HDF5TimeUnit.SECONDS),
                            new HDF5TimeDuration(16, HDF5TimeUnit.SECONDS),
                            new HDF5TimeDuration(1, HDF5TimeUnit.MINUTES),
                            new HDF5TimeDuration(17, HDF5TimeUnit.MINUTES),
                            new HDF5TimeDuration(42, HDF5TimeUnit.MINUTES),
                            new HDF5TimeDuration(111, HDF5TimeUnit.MINUTES),
                            new HDF5TimeDuration(5, HDF5TimeUnit.HOURS),
                            new HDF5TimeDuration(10, HDF5TimeUnit.HOURS),
                            new HDF5TimeDuration(20, HDF5TimeUnit.HOURS),
                            new HDF5TimeDuration(40, HDF5TimeUnit.HOURS),
                            new HDF5TimeDuration(1, HDF5TimeUnit.DAYS),
                            new HDF5TimeDuration(2, HDF5TimeUnit.DAYS),
                            new HDF5TimeDuration(4, HDF5TimeUnit.DAYS),
                            new HDF5TimeDuration(8, HDF5TimeUnit.DAYS), }, new int[]
                    { 4, 4 });
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(datasetFile);
        writer.duration().writeMDArray(timeDurationDS, durationsWritten);
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        final HDF5DataSetInformation info = reader.getDataSetInformation(timeDurationDS);
        assertTrue(info.isTimeDuration());
        assertFalse(info.isTimeStamp());
        assertEquals(HDF5TimeUnit.SECONDS, info.tryGetTimeUnit());
        final HDF5TimeDurationMDArray durationsRead = reader.duration().readMDArray(timeDurationDS);
        assertEquals(durationsWritten, durationsRead);
        assertEquals(new HDF5TimeDurationMDArray(new long[]
            { 2, 4, 8, 16 }, new int[]
            { 1, 4 }, HDF5TimeUnit.SECONDS),
                reader.duration().readMDArrayBlock(timeDurationDS, new int[]
                    { 1, 4 }, new long[]
                    { 0, 0 }));
        assertEquals(
                new HDF5TimeDurationMDArray(new long[]
                    { 1, 17, 42, 111 }, new int[]
                    { 1, 4 }, HDF5TimeUnit.MINUTES),
                HDF5TimeUnit.MINUTES.convert(reader.duration().readMDArrayBlock(timeDurationDS,
                        new int[]
                            { 1, 4 }, new long[]
                            { 1, 0 })));
        assertEquals(
                new HDF5TimeDurationMDArray(new long[]
                    { 5, 10, 20, 40 }, new int[]
                    { 1, 4 }, HDF5TimeUnit.HOURS),
                HDF5TimeUnit.HOURS.convert(reader.duration().readMDArrayBlock(timeDurationDS,
                        new int[]
                            { 1, 4 }, new long[]
                            { 2, 0 })));
        assertEquals(
                new HDF5TimeDurationMDArray(new long[]
                    { 1, 2, 4, 8 }, new int[]
                    { 1, 4 }, HDF5TimeUnit.DAYS),
                HDF5TimeUnit.DAYS.convert(reader.duration().readMDArrayBlock(timeDurationDS,
                        new int[]
                            { 1, 4 }, new long[]
                            { 3, 0 })));
        reader.close();
    }

    @Test
    public void testTimeDurationArrayChunked()
    {
        final File datasetFile = new File(workingDirectory, "timeDurationArrayChunked.h5");
        final String timeDurationSeriesDS = "/some/timeseries";
        final String timeDurationSeriesDS2 = "/some/timeseries2";
        final long[] timeDurationSeriesMillis = new long[10];
        final long[] timeDurationSeriesMicros = new long[10];
        for (int i = 0; i < timeDurationSeriesMillis.length; ++i)
        {
            timeDurationSeriesMillis[i] = i * 10000L;
            timeDurationSeriesMicros[i] = timeDurationSeriesMillis[i] * 1000L;
        }
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(datasetFile);
        writer.duration().createArray(timeDurationSeriesDS, 100, 10, HDF5TimeUnit.MILLISECONDS,
                GENERIC_DEFLATE);
        for (int i = 0; i < 10; ++i)
        {
            writer.duration().writeArrayBlock(timeDurationSeriesDS,
                    new HDF5TimeDurationArray(timeDurationSeriesMicros, HDF5TimeUnit.MICROSECONDS),
                    i);
        }
        writer.duration().createArray(timeDurationSeriesDS2, 100, 10, HDF5TimeUnit.SECONDS,
                GENERIC_DEFLATE);
        final HDF5TimeDuration[] timeDurationSeries =
                new HDF5TimeDuration[]
                    {
                            new HDF5TimeDuration(timeDurationSeriesMicros[0],
                                    HDF5TimeUnit.MICROSECONDS),
                            new HDF5TimeDuration(timeDurationSeriesMicros[1],
                                    HDF5TimeUnit.MICROSECONDS),
                            new HDF5TimeDuration(timeDurationSeriesMillis[2],
                                    HDF5TimeUnit.MILLISECONDS),
                            new HDF5TimeDuration(timeDurationSeriesMillis[3],
                                    HDF5TimeUnit.MILLISECONDS),
                            new HDF5TimeDuration(timeDurationSeriesMillis[4] / 1000L,
                                    HDF5TimeUnit.SECONDS),
                            new HDF5TimeDuration(timeDurationSeriesMillis[5] / 1000L,
                                    HDF5TimeUnit.SECONDS),
                            new HDF5TimeDuration(6, HDF5TimeUnit.HOURS),
                            new HDF5TimeDuration(7, HDF5TimeUnit.HOURS),
                            new HDF5TimeDuration(8, HDF5TimeUnit.DAYS),
                            new HDF5TimeDuration(9, HDF5TimeUnit.DAYS) };
        for (int i = 0; i < 10; ++i)
        {
            writer.duration().writeArrayBlock(timeDurationSeriesDS2,
                    HDF5TimeDurationArray.create(timeDurationSeries), i);
        }
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        final HDF5DataSetInformation info = reader.getDataSetInformation(timeDurationSeriesDS);
        assertEquals(HDF5DataTypeVariant.TIME_DURATION_MILLISECONDS, info.tryGetTypeVariant());
        assertChunkSizes(info, 10);
        for (int i = 0; i < 10; ++i)
        {
            assertTrue(Arrays.equals(
                    timeDurationSeriesMicros,
                    HDF5TimeUnit.MICROSECONDS.convert(reader.duration().readArrayBlock(
                            timeDurationSeriesDS, 10, i))));
        }
        final HDF5DataSetInformation info2 = reader.getDataSetInformation(timeDurationSeriesDS2);
        assertEquals(HDF5DataTypeVariant.TIME_DURATION_SECONDS, info2.tryGetTypeVariant());
        assertChunkSizes(info2, 10);
        for (int i = 0; i < 10; ++i)
        {
            final long[] block =
                    HDF5TimeUnit.MICROSECONDS.convert(reader.duration().readArrayBlock(
                            timeDurationSeriesDS2, 10, i));
            for (int j = 0; j < block.length; ++j)
            {
                assertEquals(HDF5TimeUnit.MICROSECONDS.convert(timeDurationSeries[j]), block[j]);
            }
        }
        for (int i = 0; i < 10; ++i)
        {
            final HDF5TimeDurationArray block =
                    reader.duration().readArrayBlock(timeDurationSeriesDS2, 10, i);
            for (int j = 0; j < block.getLength(); ++j)
            {
                assertTrue(block.get(j).isEquivalent(timeDurationSeries[j]));
            }
        }
        for (HDF5DataBlock<HDF5TimeDurationArray> block : reader.duration().getArrayNaturalBlocks(
                timeDurationSeriesDS2))
        {
            final HDF5TimeDurationArray data = block.getData();
            for (int j = 0; j < data.getLength(); ++j)
            {
                assertTrue(data.get(j) + "<>" + timeDurationSeries[j],
                        data.get(j).isEquivalent(timeDurationSeries[j]));
            }
        }
        reader.close();
    }

    @Test
    public void testAttributes()
    {
        final File attributeFile = new File(workingDirectory, "attributes.h5");
        attributeFile.delete();
        assertFalse(attributeFile.exists());
        attributeFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(attributeFile);
        final String datasetName = "SomeDataSet";
        writer.int32().writeArray(datasetName, new int[0]);
        final String booleanAttributeName = "Boolean Attribute";
        final boolean booleanAttributeValueWritten = true;
        writer.bool().setAttr(datasetName, booleanAttributeName, booleanAttributeValueWritten);
        assertTrue(writer.object().hasAttribute(datasetName, booleanAttributeName));
        final String integerAttributeName = "Integer Attribute";
        final int integerAttributeValueWritten = 17;
        writer.int32().setAttr(datasetName, integerAttributeName, integerAttributeValueWritten);
        final String byteAttributeName = "Byte Attribute";
        final byte byteAttributeValueWritten = 17;
        writer.int8().setAttr(datasetName, byteAttributeName, byteAttributeValueWritten);
        final String unsignedByteAttributeName = "Unsigned Byte Attribute";
        final short unsignedByteAttributeValueWritten = 128;
        writer.uint8().setAttr(datasetName, unsignedByteAttributeName,
                (byte) unsignedByteAttributeValueWritten);
        final String stringAttributeName = "String Attribute";
        final String stringAttributeValueWritten = "Some String Value";
        writer.string().setAttr(datasetName, stringAttributeName, stringAttributeValueWritten);
        final String stringAttributeNameVL = "String Attribute VL";
        final String stringAttributeValueVLWritten1 = "Some String Value";
        writer.string().setAttrVL(datasetName, stringAttributeNameVL,
                stringAttributeValueVLWritten1);
        final String stringAttributeValueVLWritten2 = "Some Other String Value";
        writer.string().setAttrVL(datasetName, stringAttributeNameVL,
                stringAttributeValueVLWritten2);
        final String integerArrayAttributeName = "Integer Array Attribute";
        final int[] integerArrayAttributeValueWritten = new int[]
            { 17, 23, 42 };
        writer.int32().setArrayAttr(datasetName, integerArrayAttributeName,
                integerArrayAttributeValueWritten);
        final String stringArrayAttributeName = "String Array Attribute";
        final String[] stringArrayAttributeValueWritten = new String[]
            { "Some String Value I", "Some String Value II", "Some String Value III" };
        writer.string().setArrayAttr(datasetName, stringArrayAttributeName,
                stringArrayAttributeValueWritten);
        final String string2DArrayAttributeName = "String 2D Array Attribute";
        final MDArray<String> string2DArrayAttributeValueWritten =
                new MDArray<String>(
                        new String[]
                            { "Some String Value I", "Some String Value II",
                                    "Some String Value III", "IV" }, new int[]
                            { 2, 2 });
        writer.string().setMDArrayAttr(datasetName, string2DArrayAttributeName,
                string2DArrayAttributeValueWritten);
        final HDF5EnumerationType enumType = writer.enumeration().getType("MyEnum", new String[]
            { "ONE", "TWO", "THREE" }, false);
        final String enumAttributeName = "Enum Attribute";
        final HDF5EnumerationValue enumAttributeValueWritten =
                new HDF5EnumerationValue(enumType, "TWO");
        writer.enumeration().setAttr(datasetName, enumAttributeName, enumAttributeValueWritten);
        assertEquals(enumAttributeValueWritten.getType(),
                writer.enumeration().getAttributeType(datasetName, enumAttributeName));
        final String enumArrayAttributeName = "Enum Array Attribute";
        final HDF5EnumerationValueArray enumArrayAttributeValueWritten =
                new HDF5EnumerationValueArray(enumType, new String[]
                    { "TWO", "THREE", "ONE" });
        writer.enumeration().setArrayAttr(datasetName, enumArrayAttributeName,
                enumArrayAttributeValueWritten);
        final String enumMDArrayAttributeName = "Enum Array MD Attribute";
        final HDF5EnumerationValueMDArray enumMDArrayAttributeValueWritten =
                new HDF5EnumerationValueMDArray(enumType, new MDArray<String>(new String[]
                    { "TWO", "THREE", "ONE", "ONE" }, new int[]
                    { 2, 2 }));
        writer.enumeration().setMDArrayAttr(datasetName, enumMDArrayAttributeName,
                enumMDArrayAttributeValueWritten);
        final String volatileAttributeName = "Some Volatile Attribute";
        writer.int32().setAttr(datasetName, volatileAttributeName, 21);
        writer.object().deleteAttribute(datasetName, volatileAttributeName);
        final String floatArrayAttributeName = "Float Array Attribute";
        final float[] floatArrayAttribute = new float[]
            { 3f, 3.1f, 3.14f, 3.142f, 3.1416f };
        writer.float32().setArrayAttr(datasetName, floatArrayAttributeName, floatArrayAttribute);
        final String floatArrayMDAttributeName = "Float Array Multi-dimensional Attribute";
        final MDFloatArray floatMatrixAttribute = new MDFloatArray(new float[][]
            {
                { 1, 2, 3 },
                { 4, 5, 6 } });
        writer.float32().setMDArrayAttr(datasetName, floatArrayMDAttributeName,
                floatMatrixAttribute);
        final MDFloatArray floatMatrixAttribute2 = new MDFloatArray(new float[][]
            {
                { 2, 3, 4 },
                { 7, 8, 9 } });
        writer.float32().setMatrixAttr(datasetName, floatArrayMDAttributeName,
                floatMatrixAttribute2.toMatrix());
        final String byteArrayAttributeName = "Byte Array Attribute";
        final byte[] byteArrayAttribute = new byte[]
            { 1, 2, 3 };
        writer.int8().setArrayAttr(datasetName, byteArrayAttributeName, byteArrayAttribute);
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(attributeFile);
        final List<String> attributeNames = reader.object().getAttributeNames(datasetName);
        attributeNames.sort(new Comparator<String>() {
			@Override
			public int compare(String o1, String o2) {
				return o1.compareTo(o2);
			}
		});
        assertEquals(15, attributeNames.size());
        assertEquals(Arrays.asList("Boolean Attribute", 
        		"Byte Array Attribute", 
        		"Byte Attribute", 
        		"Enum Array Attribute", 
        		"Enum Array MD Attribute",
        		"Enum Attribute",
        		"Float Array Attribute",
        		"Float Array Multi-dimensional Attribute",
        		"Integer Array Attribute",
        		"Integer Attribute",
        		"String 2D Array Attribute",
        		"String Array Attribute",
        		"String Attribute",
        		"String Attribute VL",
        		"Unsigned Byte Attribute"), attributeNames);
        assertTrue(reader.object().hasAttribute(datasetName, booleanAttributeName));
        final boolean booleanAttributeValueRead =
                reader.bool().getAttr(datasetName, booleanAttributeName);
        assertEquals(booleanAttributeValueWritten, booleanAttributeValueRead);
        final int integerAttributeValueRead =
                reader.int32().getAttr(datasetName, integerAttributeName);
        assertEquals(integerAttributeValueWritten, integerAttributeValueRead);
        final byte byteAttributeValueRead = reader.int8().getAttr(datasetName, byteAttributeName);
        assertEquals(byteAttributeValueWritten, byteAttributeValueRead);
        final short unsignedByteAttributeValueRead =
                reader.int16().getAttr(datasetName, unsignedByteAttributeName);
        assertEquals(unsignedByteAttributeValueWritten, unsignedByteAttributeValueRead);
        HDF5DataTypeInformation info =
                reader.object().getAttributeInformation(datasetName, integerAttributeName);
        assertEquals(HDF5DataClass.INTEGER, info.getDataClass());
        assertEquals(4, info.getElementSize());
        final String stringAttributeValueRead =
                reader.string().getAttr(datasetName, stringAttributeName);
        assertEquals(stringAttributeValueWritten, stringAttributeValueRead);
        final int[] intArrayAttributeValueRead =
                reader.int32().getArrayAttr(datasetName, integerArrayAttributeName);
        assertTrue(Arrays.equals(integerArrayAttributeValueWritten, intArrayAttributeValueRead));
        final String[] stringArrayAttributeValueRead =
                reader.string().getArrayAttr(datasetName, stringArrayAttributeName);
        assertTrue(Arrays.equals(stringArrayAttributeValueWritten, stringArrayAttributeValueRead));
        info = reader.object().getAttributeInformation(datasetName, stringArrayAttributeName);
        assertTrue(info.isArrayType());
        assertEquals(HDF5DataClass.STRING, info.getDataClass());
        assertEquals(21, info.getElementSize()); // longest string in the string array
        assertEquals(3, info.getNumberOfElements());
        assertEquals(1, info.getDimensions().length);
        final MDArray<String> string2DArrayAttributeValueRead =
                reader.string().getMDArrayAttr(datasetName, string2DArrayAttributeName);
        assertEquals(string2DArrayAttributeValueWritten, string2DArrayAttributeValueRead);
        final String stringAttributeValueVLRead =
                reader.string().getAttr(datasetName, stringAttributeNameVL);
        assertEquals(stringAttributeValueVLWritten2, stringAttributeValueVLRead);
        final HDF5EnumerationValue enumAttributeValueRead =
                reader.enumeration().getAttr(datasetName, enumAttributeName);
        final String enumAttributeStringValueRead =
                reader.enumeration().getAttrAsString(datasetName, enumAttributeName);
        assertEquals(enumAttributeValueWritten.getValue(), enumAttributeValueRead.getValue());
        assertEquals(enumAttributeValueWritten.getValue(), enumAttributeStringValueRead);
        final HDF5EnumerationType enumAttributeType =
                reader.enumeration().getAttributeType(datasetName, enumAttributeName);
        assertEquals(enumAttributeValueWritten.getType(), enumAttributeType);
        assertEquals("MyEnum", enumAttributeType.getName());
        final String[] enumArrayAttributeReadAsString =
                reader.enumeration().getArrayAttr(datasetName, enumArrayAttributeName)
                        .toStringArray();
        assertEquals(enumArrayAttributeValueWritten.getLength(),
                enumArrayAttributeReadAsString.length);
        for (int i = 0; i < enumArrayAttributeReadAsString.length; ++i)
        {
            assertEquals(enumArrayAttributeValueWritten.getValue(i),
                    enumArrayAttributeReadAsString[i]);
        }
        final HDF5EnumerationValueArray enumArrayAttributeRead =
                reader.enumeration().getArrayAttr(datasetName, enumArrayAttributeName);
        final HDF5EnumerationType enumAttributeArrayType =
                reader.enumeration().getAttributeType(datasetName, enumArrayAttributeName);
        assertEquals(enumArrayAttributeRead.getType(), enumAttributeArrayType);
        assertEquals("MyEnum", enumAttributeArrayType.getName());
        assertEquals(enumArrayAttributeValueWritten.getLength(), enumArrayAttributeRead.getLength());
        for (int i = 0; i < enumArrayAttributeRead.getLength(); ++i)
        {
            assertEquals(enumArrayAttributeValueWritten.getValue(i),
                    enumArrayAttributeRead.getValue(i));
        }
        // Let's try to read the first element of the array using getEnumAttributeAsString
        assertEquals(enumArrayAttributeValueWritten.getValue(0), reader.enumeration()
                .getAttrAsString(datasetName, enumArrayAttributeName));
        // Let's try to read the first element of the array using getEnumAttribute
        assertEquals(enumArrayAttributeValueWritten.getValue(0),
                reader.enumeration().getAttr(datasetName, enumArrayAttributeName).getValue());
        assertFalse(reader.object().hasAttribute(datasetName, volatileAttributeName));
        final HDF5EnumerationValueMDArray enumMDArrayAttributeRead =
                reader.enumeration().getMDArrayAttr(datasetName, enumMDArrayAttributeName);
        assertEquals(enumMDArrayAttributeValueWritten.toStringArray(),
                enumMDArrayAttributeRead.toStringArray());
        assertEquals(enumArrayAttributeValueWritten.getLength(), enumArrayAttributeRead.getLength());
        for (int i = 0; i < enumArrayAttributeRead.getLength(); ++i)
        {
            assertEquals(enumArrayAttributeValueWritten.getValue(i),
                    enumArrayAttributeRead.getValue(i));
        }
        assertTrue(Arrays.equals(floatArrayAttribute,
                reader.float32().getArrayAttr(datasetName, floatArrayAttributeName)));
        assertTrue(floatMatrixAttribute2.equals(reader.float32().getMDArrayAttr(datasetName,
                floatArrayMDAttributeName)));
        assertTrue(floatMatrixAttribute2.equals(new MDFloatArray(reader.float32().getMatrixAttr(
                datasetName, floatArrayMDAttributeName))));
        assertTrue(Arrays.equals(byteArrayAttribute,
                reader.int8().getArrayAttr(datasetName, byteArrayAttributeName)));
        reader.close();
    }

    @Test
    public void testSimpleDataspaceAttributes()
    {
        final File attributeFile = new File(workingDirectory, "simpleDataspaceAttributes.h5");
        attributeFile.delete();
        assertFalse(attributeFile.exists());
        attributeFile.deleteOnExit();
        final IHDF5Writer writer =
                HDF5FactoryProvider.get().configure(attributeFile)
                        .useSimpleDataSpaceForAttributes().writer();
        final String datasetName = "SomeDataSet";
        final String floatAttrName = "SomeFloatAttr";
        final String floatAttrArrayName = "SomeFloatArrayAttr";
        final String floatAttrMDArrayName = "SomeFloatMDArrayAttr";
        final String unsignedIntAttrName = "SomeUnsignedIntAttr";
        final String unsignedIntAttrArrayName = "SomeUnsignedIntArrayAttr";
        final String unsignedIntAttrMDArrayName = "SomeUnsignedIntMDArrayAttr";
        final String referenceAttrArrayName = "SomeRefAttr";
        final String dateTimeAttrName = "SomeDateTimeAttr";
        final String dateTimeAttrArrayName = "SomeDateTimeArrayAttr";
        final String timeDurationAttrName = "SomeTimeDurationAttr";
        final String timeDurationAttrArrayName = "SomeTimeDurationArrayAttr";
        writer.float32().writeArray(datasetName, new float[0]);
        writer.float32().setAttr(datasetName, floatAttrName, 17.0f);
        final float[] floatArrayValueWritten = new float[]
            { 1, 2, 3, };
        writer.float32().setArrayAttr(datasetName, floatAttrArrayName, floatArrayValueWritten);
        final MDFloatArray floatMDArrayWritten = new MDFloatArray(new float[]
            { 1, 2, 3, 4 }, new int[]
            { 2, 2 });
        writer.float32().setMDArrayAttr(datasetName, floatAttrMDArrayName, floatMDArrayWritten);
        writer.uint32().setAttr(datasetName, unsignedIntAttrName, toInt32(4000000000L));
        final int[] uintArrayValueWritten = new int[]
            { toInt32(4000000001L), toInt32(4000000002L), toInt32(4000000003L) };
        writer.uint32().setArrayAttr(datasetName, unsignedIntAttrArrayName, uintArrayValueWritten);
        final MDIntArray uintMDArrayValueWritten =
                new MDIntArray(new int[]
                    { toInt32(4000000000L), toInt32(4000000002L), toInt32(4000000003L),
                            toInt32(4000000003L) }, new int[]
                    { 2, 2 });
        writer.uint32().setMDArrayAttr(datasetName, unsignedIntAttrMDArrayName,
                uintMDArrayValueWritten);
        writer.reference().setArrayAttr(datasetName, referenceAttrArrayName, new String[]
            { datasetName, datasetName });
        writer.time().setAttr(datasetName, dateTimeAttrName, 1000L);
        writer.time().setArrayAttr(datasetName, dateTimeAttrArrayName, new long[]
            { 1000L, 2000L });
        writer.duration().setAttr(datasetName, timeDurationAttrName,
                new HDF5TimeDuration(100L, HDF5TimeUnit.SECONDS));
        writer.duration().setArrayAttr(datasetName, timeDurationAttrArrayName,
                new HDF5TimeDurationArray(new long[]
                    { 100L, 150L }, HDF5TimeUnit.SECONDS));
        writer.close();

        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(attributeFile);
        assertTrue(reader.object().hasAttribute(datasetName, floatAttrName));
        final float attributeValue = reader.float32().getAttr(datasetName, floatAttrName);
        assertEquals(17.0f, attributeValue);
        final HDF5BaseReader baseReader = ((HDF5FloatReader) reader.float32()).getBaseReader();
        final long objectId =
                baseReader.h5.openObject(baseReader.fileId, datasetName, baseReader.fileRegistry);
        long attributeId =
                baseReader.h5.openAttribute(objectId, floatAttrName, baseReader.fileRegistry);
        long attributeTypeId =
                baseReader.h5.getDataTypeForAttribute(attributeId, baseReader.fileRegistry);
        assertEquals(H5T_FLOAT, baseReader.h5.getClassType(attributeTypeId));
        assertTrue(reader.object().hasAttribute(datasetName, floatAttrArrayName));
        final float[] attributeArrayValueRead =
                reader.float32().getArrayAttr(datasetName, floatAttrArrayName);
        assertTrue(Arrays.equals(floatArrayValueWritten, attributeArrayValueRead));
        attributeId =
                baseReader.h5.openAttribute(objectId, floatAttrArrayName, baseReader.fileRegistry);
        attributeTypeId =
                baseReader.h5.getDataTypeForAttribute(attributeId, baseReader.fileRegistry);
        assertEquals(H5T_FLOAT, baseReader.h5.getClassType(attributeTypeId));
        assertTrue(reader.object().hasAttribute(datasetName, floatAttrMDArrayName));
        final MDFloatArray attributeMDArrayValueRead =
                reader.float32().getMDArrayAttr(datasetName, floatAttrMDArrayName);
        assertEquals(floatMDArrayWritten, attributeMDArrayValueRead);
        assertEquals(toInt32(4000000000L), reader.uint32()
                .getAttr(datasetName, unsignedIntAttrName));
        attributeId =
                baseReader.h5.openAttribute(objectId, unsignedIntAttrName, baseReader.fileRegistry);
        attributeTypeId =
                baseReader.h5.getDataTypeForAttribute(attributeId, baseReader.fileRegistry);
        assertEquals(H5T_INTEGER, baseReader.h5.getClassType(attributeTypeId));
        assertTrue(Arrays.equals(uintArrayValueWritten,
                reader.uint32().getArrayAttr(datasetName, unsignedIntAttrArrayName)));
        attributeId =
                baseReader.h5.openAttribute(objectId, unsignedIntAttrArrayName,
                        baseReader.fileRegistry);
        attributeTypeId =
                baseReader.h5.getDataTypeForAttribute(attributeId, baseReader.fileRegistry);
        assertEquals(H5T_INTEGER, baseReader.h5.getClassType(attributeTypeId));
        assertEquals(uintMDArrayValueWritten,
                reader.uint32().getMDArrayAttr(datasetName, unsignedIntAttrMDArrayName));
        attributeId =
                baseReader.h5.openAttribute(objectId, unsignedIntAttrMDArrayName,
                        baseReader.fileRegistry);
        attributeTypeId =
                baseReader.h5.getDataTypeForAttribute(attributeId, baseReader.fileRegistry);
        assertEquals(H5T_INTEGER, baseReader.h5.getClassType(attributeTypeId));
        attributeId =
                baseReader.h5.openAttribute(objectId, referenceAttrArrayName,
                        baseReader.fileRegistry);
        attributeTypeId =
                baseReader.h5.getDataTypeForAttribute(attributeId, baseReader.fileRegistry);
        assertEquals(H5T_REFERENCE, baseReader.h5.getClassType(attributeTypeId));
        final String[] referenceValues =
                reader.reference().getArrayAttr(datasetName, referenceAttrArrayName);
        assertEquals(2, referenceValues.length);
        assertEquals("/" + datasetName, referenceValues[0]);
        assertEquals("/" + datasetName, referenceValues[1]);
        assertEquals(1000L, reader.time().getAttrAsLong(datasetName, dateTimeAttrName));
        assertTrue(Arrays.equals(new long[]
            { 1000L, 2000L }, reader.time().getArrayAttrAsLong(datasetName, dateTimeAttrArrayName)));
        attributeId =
                baseReader.h5.openAttribute(objectId, dateTimeAttrName, baseReader.fileRegistry);
        attributeTypeId =
                baseReader.h5.getDataTypeForAttribute(attributeId, baseReader.fileRegistry);
        assertEquals(H5T_INTEGER, baseReader.h5.getClassType(attributeTypeId));
        attributeId =
                baseReader.h5.openAttribute(objectId, "__TYPE_VARIANT__" + dateTimeAttrName + "__",
                        baseReader.fileRegistry);
        attributeTypeId =
                baseReader.h5.getDataTypeForAttribute(attributeId, baseReader.fileRegistry);
        assertEquals(H5T_ENUM, baseReader.h5.getClassType(attributeTypeId));
        assertEquals(new HDF5TimeDuration(100L, HDF5TimeUnit.SECONDS),
                reader.duration().getAttr(datasetName, timeDurationAttrName));
        assertEquals(new HDF5TimeDurationArray(new long[]
            { 100L, 150L }, HDF5TimeUnit.SECONDS),
                reader.duration().getArrayAttr(datasetName, timeDurationAttrArrayName));
        attributeId =
                baseReader.h5
                        .openAttribute(objectId, timeDurationAttrName, baseReader.fileRegistry);
        attributeTypeId =
                baseReader.h5.getDataTypeForAttribute(attributeId, baseReader.fileRegistry);
        assertEquals(H5T_INTEGER, baseReader.h5.getClassType(attributeTypeId));
        attributeId =
                baseReader.h5.openAttribute(objectId, "__TYPE_VARIANT__" + timeDurationAttrName
                        + "__", baseReader.fileRegistry);
        attributeTypeId =
                baseReader.h5.getDataTypeForAttribute(attributeId, baseReader.fileRegistry);
        assertEquals(H5T_ENUM, baseReader.h5.getClassType(attributeTypeId));
        reader.close();
    }

    @Test
    public void testTimeStampAttributes()
    {
        final File attributeFile = new File(workingDirectory, "timeStampAttributes.h5");
        attributeFile.delete();
        assertFalse(attributeFile.exists());
        attributeFile.deleteOnExit();
        final IHDF5Writer writer = HDF5Factory.open(attributeFile);
        final String datasetName = "SomeDataSet";
        final String lastChangedAttr = "lastChanged";
        final String someLongAttr = "someLong";
        final Date now = new Date();
        writer.int32().writeArray(datasetName, new int[0]);
        writer.int64().setAttr(datasetName, someLongAttr, 115L);
        writer.time().setAttr(datasetName, lastChangedAttr, now);
        writer.close();
        final IHDF5Reader reader = HDF5Factory.openForReading(attributeFile);
        assertEquals(HDF5DataTypeVariant.TIMESTAMP_MILLISECONDS_SINCE_START_OF_THE_EPOCH, reader
                .object().tryGetTypeVariant(datasetName, lastChangedAttr));
        assertFalse(reader.time().isTimeStamp(datasetName));
        assertTrue(reader.time().isTimeStamp(datasetName, lastChangedAttr));
        assertFalse(reader.duration().isTimeDuration(datasetName, lastChangedAttr));
        assertEquals(now, reader.time().getAttr(datasetName, lastChangedAttr));
        assertFalse(reader.time().isTimeStamp(datasetName, someLongAttr));
        try
        {
            reader.time().getAttrAsLong(datasetName, someLongAttr);
            fail("Did not detect non-time-stamp attribute.");
        } catch (HDF5JavaException ex)
        {
            assertEquals("Attribute 'someLong' of data set 'SomeDataSet' is not a time stamp.",
                    ex.getMessage());
        }
        reader.close();
    }

    @Test
    public void testTimeDurationAttributes()
    {
        final File attributeFile = new File(workingDirectory, "timeDurationAttributes.h5");
        attributeFile.delete();
        assertFalse(attributeFile.exists());
        attributeFile.deleteOnExit();
        final IHDF5Writer writer = HDF5Factory.open(attributeFile);
        final String datasetName = "SomeDataSet";
        final String validUntilAttr = "validUtil";
        final String someLongAttr = "someLong";
        writer.int32().writeArray(datasetName, new int[0]);
        writer.duration().setAttr(datasetName, validUntilAttr, 10, HDF5TimeUnit.MINUTES);
        writer.int64().setAttr(datasetName, someLongAttr, 115L);
        writer.close();
        final IHDF5Reader reader = HDF5Factory.openForReading(attributeFile);
        assertEquals(HDF5DataTypeVariant.TIME_DURATION_MINUTES,
                reader.object().tryGetTypeVariant(datasetName, validUntilAttr));
        assertFalse(reader.time().isTimeStamp(datasetName));
        assertFalse(reader.time().isTimeStamp(datasetName, validUntilAttr));
        assertTrue(reader.duration().isTimeDuration(datasetName, validUntilAttr));
        assertEquals(HDF5TimeUnit.MINUTES,
                reader.duration().tryGetTimeUnit(datasetName, validUntilAttr));
        assertEquals(new HDF5TimeDuration(10, HDF5TimeUnit.MINUTES),
                reader.duration().getAttr(datasetName, validUntilAttr));
        assertEquals(
                10 * 60,
                reader.duration().getAttr(datasetName, validUntilAttr)
                        .getValue(HDF5TimeUnit.SECONDS));
        assertFalse(reader.duration().isTimeDuration(datasetName, someLongAttr));
        try
        {
            reader.duration().getAttr(datasetName, someLongAttr);
            fail("Did not detect non-time-duration attribute.");
        } catch (HDF5JavaException ex)
        {
            assertEquals("Attribute 'someLong' of data set 'SomeDataSet' is not a time duration.",
                    ex.getMessage());
        }
        reader.close();
    }

    @Test
    public void testTimeStampArrayAttributes()
    {
        final File attributeFile = new File(workingDirectory, "timeStampArrayAttributes.h5");
        attributeFile.delete();
        assertFalse(attributeFile.exists());
        attributeFile.deleteOnExit();
        final IHDF5Writer writer = HDF5Factory.open(attributeFile);
        final String datasetName = "SomeDataSet";
        final String lastChangedAttr = "lastChanged";
        final String someDates = "someDates";
        final String someLongAttr = "someLong";
        final Date now = new Date();
        writer.int32().writeArray(datasetName, new int[0]);
        writer.int64().setArrayAttr(datasetName, someLongAttr, new long[]
            { 115L });
        writer.time().setArrayAttr(datasetName, lastChangedAttr, new Date[]
            { now });
        writer.time().setMDArrayAttr(
                datasetName,
                someDates,
                new MDArray<Date>(new Date[]
                    { now, new Date(now.getTime() - 1000L), new Date(now.getTime() - 2000L),
                            new Date(now.getTime() - 3000L) }, new int[]
                    { 2, 2 }));
        writer.close();
        final IHDF5Reader reader = HDF5Factory.openForReading(attributeFile);
        assertEquals(HDF5DataTypeVariant.TIMESTAMP_MILLISECONDS_SINCE_START_OF_THE_EPOCH, reader
                .object().tryGetTypeVariant(datasetName, lastChangedAttr));
        assertFalse(reader.time().isTimeStamp(datasetName));
        assertTrue(reader.time().isTimeStamp(datasetName, lastChangedAttr));
        assertFalse(reader.duration().isTimeDuration(datasetName, lastChangedAttr));
        assertEquals(1, reader.time().getArrayAttr(datasetName, lastChangedAttr).length);
        assertEquals(now, reader.time().getArrayAttr(datasetName, lastChangedAttr)[0]);
        assertFalse(reader.time().isTimeStamp(datasetName, someLongAttr));
        assertTrue(reader.time().isTimeStamp(datasetName, someDates));
        assertEquals(now.getTime(),
                reader.time().getMDArrayAttrAsLong(datasetName, someDates).get(0, 0));
        assertEquals(now.getTime() - 1000L,
                reader.time().getMDArrayAttrAsLong(datasetName, someDates).get(0, 1));
        assertEquals(now.getTime() - 2000L,
                reader.time().getMDArrayAttrAsLong(datasetName, someDates).get(1, 0));
        assertEquals(now.getTime() - 3000L,
                reader.time().getMDArrayAttrAsLong(datasetName, someDates).get(1, 1));
        try
        {
            reader.time().getArrayAttrAsLong(datasetName, someLongAttr);
            fail("Did not detect non-time-stamp attribute.");
        } catch (HDF5JavaException ex)
        {
            assertEquals("Attribute 'someLong' of data set 'SomeDataSet' is not a time stamp.",
                    ex.getMessage());
        }
        reader.close();
    }

    @Test
    public void testTimeDurationArrayAttributes()
    {
        final File attributeFile = new File(workingDirectory, "timeDurationArrayAttributes.h5");
        attributeFile.delete();
        assertFalse(attributeFile.exists());
        attributeFile.deleteOnExit();
        final IHDF5Writer writer = HDF5Factory.open(attributeFile);
        final String datasetName = "SomeDataSet";
        final String validUntilAttr = "validUtil";
        final String someDurations = "someDurations";
        final String someLongAttr = "someLong";
        writer.int32().writeArray(datasetName, new int[0]);
        writer.duration().setArrayAttr(datasetName, validUntilAttr,
                HDF5TimeDurationArray.create(HDF5TimeUnit.MINUTES, 10));
        final HDF5TimeDurationMDArray someDurationValues = new HDF5TimeDurationMDArray(new long[]
            { 1, 2, 3, 4 }, new int[]
            { 2, 2 }, HDF5TimeUnit.MINUTES);
        writer.duration().setMDArrayAttr(datasetName, someDurations, someDurationValues);
        writer.int64().setArrayAttr(datasetName, someLongAttr, new long[]
            { 115L });
        writer.close();
        final IHDF5Reader reader = HDF5Factory.openForReading(attributeFile);
        assertEquals(HDF5DataTypeVariant.TIME_DURATION_MINUTES,
                reader.object().tryGetTypeVariant(datasetName, validUntilAttr));
        assertFalse(reader.time().isTimeStamp(datasetName));
        assertFalse(reader.time().isTimeStamp(datasetName, validUntilAttr));
        assertTrue(reader.duration().isTimeDuration(datasetName, validUntilAttr));
        assertEquals(HDF5TimeUnit.MINUTES,
                reader.duration().tryGetTimeUnit(datasetName, validUntilAttr));
        assertEquals(1, reader.duration().getArrayAttr(datasetName, validUntilAttr).getLength());
        assertEquals(new HDF5TimeDuration(10, HDF5TimeUnit.MINUTES), reader.duration()
                .getArrayAttr(datasetName, validUntilAttr).get(0));
        assertEquals(
                10 * 60,
                reader.duration().getArrayAttr(datasetName, validUntilAttr)
                        .getValue(0, HDF5TimeUnit.SECONDS));
        assertFalse(reader.duration().isTimeDuration(datasetName, someLongAttr));
        assertTrue(reader.duration().isTimeDuration(datasetName, someDurations));
        assertEquals(someDurationValues,
                reader.duration().getMDArrayAttr(datasetName, someDurations));
        try
        {
            reader.duration().getArrayAttr(datasetName, someLongAttr);
            fail("Did not detect non-time-duration attribute.");
        } catch (HDF5JavaException ex)
        {
            assertEquals("Attribute 'someLong' of data set 'SomeDataSet' is not a time duration.",
                    ex.getMessage());
        }
        reader.close();
    }

    @Test
    public void testAttributeDimensionArray()
    {
        final File attributeFile = new File(workingDirectory, "attributeDimensionalArray.h5");
        attributeFile.delete();
        assertFalse(attributeFile.exists());
        attributeFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(attributeFile);
        final HDF5ArrayTypeFloatWriter efWriter = new HDF5ArrayTypeFloatWriter((HDF5Writer) writer);
        final String datasetName = "SomeDataSet";
        final String attributeName = "farray";
        final float[] farray = new float[]
            { 0, 10, 100 };

        writer.int32().writeArray(datasetName, new int[0]);
        efWriter.setFloatArrayAttributeDimensional(datasetName, attributeName, farray);
        final HDF5DataTypeInformation info =
                writer.object().getAttributeInformation(datasetName, attributeName);
        assertEquals("FLOAT(4, #3)", info.toString());
        assertFalse(info.isArrayType());
        writer.close();

        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(attributeFile);
        assertTrue(Arrays.equals(farray, reader.float32().getArrayAttr(datasetName, attributeName)));
        reader.close();
    }

    @Test
    public void testAttributeDimensionArrayOverwrite()
    {
        final File attributeFile =
                new File(workingDirectory, "attributeDimensionalArrayOverwrite.h5");
        attributeFile.delete();
        assertFalse(attributeFile.exists());
        attributeFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(attributeFile);
        final HDF5ArrayTypeFloatWriter efWriter = new HDF5ArrayTypeFloatWriter((HDF5Writer) writer);
        final String datasetName = "SomeDataSet";
        final String attributeName = "farray";
        final float[] farray = new float[]
            { 0, 10, 100 };

        writer.int32().writeArray(datasetName, new int[0]);
        efWriter.setFloatArrayAttributeDimensional(datasetName, attributeName, farray);
        writer.float32().setArrayAttr(datasetName, attributeName, farray);
        final HDF5DataTypeInformation info =
                writer.object().getAttributeInformation(datasetName, attributeName);
        assertEquals("FLOAT(4, #3)", info.toString());
        assertTrue(info.isArrayType());
        writer.close();

        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(attributeFile);
        assertTrue(Arrays.equals(farray, reader.float32().getArrayAttr(datasetName, attributeName)));
        reader.close();
    }

    @Test
    public void testCreateDataTypes()
    {
        final File file = new File(workingDirectory, "types.h5");
        final String enumName = "TestEnum";
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(file);
        try
        {
            final List<String> initialDataTypes =
                    writer.getGroupMembers(HDF5Utils.getDataTypeGroup(""));

            writer.enumeration().getType(enumName, new String[]
                { "ONE", "TWO", "THREE" }, false);
            final Set<String> dataTypes =
                    new HashSet<String>(writer.getGroupMembers(HDF5Utils.getDataTypeGroup("")));
            assertEquals(initialDataTypes.size() + 1, dataTypes.size());
            assertTrue(dataTypes.contains(HDF5Utils.ENUM_PREFIX + enumName));
        } finally
        {
            writer.close();
        }
    }

    @Test
    public void testGroups()
    {
        final File groupFile = new File(workingDirectory, "groups.h5");
        groupFile.delete();
        assertFalse(groupFile.exists());
        groupFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(groupFile);
        final String groupName1 = "/group";
        final String groupName2 = "/group2";
        final String groupName4 = "/dataSetGroup";
        final String groupName5 = "/group5";
        final String dataSetName = groupName4 + "/dataset";
        writer.object().createGroup(groupName1);
        writer.object().createGroup(groupName2);
        writer.int8().writeArray(dataSetName, new byte[]
            { 1 });
        assertTrue(writer.isGroup(groupName1));
        assertTrue(writer.isGroup(groupName2));
        assertTrue(writer.isGroup(groupName4));
        assertFalse(writer.isGroup(dataSetName));
        assertFalse(writer.isGroup(groupName5));
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(groupFile);
        assertTrue(reader.isGroup(groupName1));
        assertEquals(HDF5ObjectType.GROUP, reader.object().getObjectType(groupName1));
        assertTrue(reader.isGroup(groupName4));
        assertEquals(HDF5ObjectType.GROUP, reader.object().getObjectType(groupName4));
        assertFalse(reader.isGroup(dataSetName));
        reader.close();
    }

    @Test
    public void testDefaultHousekeepingFile()
    {
        final File file = new File(workingDirectory, "defaultHousekeepingFile.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(file);
        assertEquals("", writer.file().getHouseKeepingNameSuffix());
        assertEquals("__abc__", writer.object().toHouseKeepingPath("abc"));
        writer.string().write(writer.object().toHouseKeepingPath("abc"), "ABC");
        assertTrue(writer.exists("__abc__"));
        assertTrue(writer.object().getGroupMemberPaths("/").isEmpty());
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(file);
        assertTrue(reader.object().getAttributeNames("/").isEmpty());
        assertEquals("", reader.file().getHouseKeepingNameSuffix());
        assertEquals("__abc__", reader.object().toHouseKeepingPath("abc"));
        assertTrue(reader.exists("__abc__"));
        assertEquals("ABC", reader.readString("__abc__"));
        assertTrue(reader.object().getGroupMemberPaths("/").isEmpty());
        reader.close();
    }

    @Test
    public void testNonDefaultHousekeepingFile()
    {
        final File file = new File(workingDirectory, "nonDefaultHousekeepingFile.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer writer =
                HDF5Factory.configure(file).houseKeepingNameSuffix("XXX").writer();
        assertEquals("XXX", writer.file().getHouseKeepingNameSuffix());
        assertEquals("abcXXX", writer.object().toHouseKeepingPath("abc"));
        writer.string().write(writer.object().toHouseKeepingPath("abc"), "ABC");
        assertTrue(writer.exists("abcXXX"));
        assertFalse(writer.exists("__abc__"));
        assertTrue(writer.object().getGroupMemberPaths("/").isEmpty());
        writer.close();

        // The house keeping index is only considered when creating a new file.
        // If the file exists, the one saved in the file takes precedence.
        final IHDF5Writer writer2 =
                HDF5Factory.configure(file).houseKeepingNameSuffix("YYY").writer();
        assertEquals("XXX", writer2.file().getHouseKeepingNameSuffix());
        assertEquals("abcXXX", writer2.object().toHouseKeepingPath("abc"));
        assertTrue(writer2.exists("abcXXX"));
        writer2.string().write(writer2.object().toHouseKeepingPath("abc"), "CAB");
        assertFalse(writer2.exists("__abc__"));
        assertFalse(writer2.exists("abcYYY"));
        assertEquals("CAB", writer2.readString("abcXXX"));
        assertTrue(writer2.object().getGroupMemberPaths("/").isEmpty());
        writer2.close();

        final IHDF5Reader reader = HDF5Factory.openForReading(file);
        assertTrue(reader.object().getAttributeNames("/").isEmpty());
        assertEquals("XXX", reader.file().getHouseKeepingNameSuffix());
        assertEquals("abcXXX", reader.object().toHouseKeepingPath("abc"));
        assertTrue(reader.exists("abcXXX"));
        assertFalse(reader.exists("__abc__"));
        assertEquals("CAB", reader.readString("abcXXX"));
        assertTrue(reader.object().getGroupMemberPaths("/").isEmpty());
        reader.close();
    }

    @Test
    public void testHousekeepingFileSuffixNonPrintable()
    {
        final File file = new File(workingDirectory, "housekeepingFileSuffixNonPrintable.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer writer =
                HDF5Factory.configure(file).houseKeepingNameSuffix("\1\0").writer();
        assertEquals("\1\0", writer.file().getHouseKeepingNameSuffix());
        assertEquals("abc\1\0", writer.object().toHouseKeepingPath("abc"));
        writer.string().write(writer.object().toHouseKeepingPath("abc"), "ABC");
        assertTrue(writer.exists("abc\1\0"));
        assertFalse(writer.exists("__abc__"));
        assertTrue(writer.object().getGroupMemberPaths("/").isEmpty());
        writer.close();
        final IHDF5Reader reader = HDF5Factory.openForReading(file);
        assertEquals("\1\0", reader.file().getHouseKeepingNameSuffix());
        assertEquals("abc\1\0", reader.object().toHouseKeepingPath("abc"));
        assertTrue(reader.exists("abc\1\0"));
        assertFalse(reader.exists("__abc__"));
        assertEquals("ABC", reader.readString("abc\1\0"));
        assertTrue(reader.object().getGroupMemberPaths("/").isEmpty());
        reader.close();
    }

    @Test
    public void testGetObjectType()
    {
        final File file = new File(workingDirectory, "typeInfo.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(file);
        writer.writeBoolean("/some/flag", false);
        writer.object().createSoftLink("/some", "/linkToSome");
        writer.object().createSoftLink("/some/flag", "/linkToFlag");
        writer.object().createHardLink("/some/flag", "/some/flag2");
        writer.bool().setAttr("/some/flag2", "test", true);
        assertEquals(HDF5ObjectType.GROUP, writer.object().getObjectType("/some"));
        assertEquals(HDF5ObjectType.SOFT_LINK, writer.object().getObjectType("/linkToSome", false));
        assertEquals(HDF5ObjectType.GROUP, writer.object().getObjectType("/some"));
        assertEquals(HDF5ObjectType.GROUP, writer.object().getObjectType("/linkToSome"));
        assertEquals(HDF5ObjectType.DATASET, writer.object().getObjectType("/some/flag", false));
        assertEquals(HDF5ObjectType.DATASET, writer.object().getObjectType("/some/flag"));
        assertEquals(HDF5ObjectType.SOFT_LINK, writer.object().getObjectType("/linkToFlag", false));
        assertEquals(HDF5ObjectType.DATASET, writer.object().getObjectType("/linkToFlag"));
        assertFalse(writer.exists("non_existent"));
        assertEquals(HDF5ObjectType.NONEXISTENT, writer.object().getObjectType("non_existent"));
        writer.close();
    }

    @Test(expectedExceptions = HDF5JavaException.class)
    public void testGetLinkInformationFailed()
    {
        final File file = new File(workingDirectory, "linkInfo.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(file);
        try
        {
            assertFalse(writer.exists("non_existent"));
            writer.object().getLinkInformation("non_existent").checkExists();
        } finally
        {
            writer.close();
        }
    }

    @Test
    public void testGetDataSetInformation()
    {
        final File file = new File(workingDirectory, "dsInfo.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(file);
        writer.int32().write("dsScalar", 12);
        writer.int16().writeMatrix("ds", new short[][]
            {
                { (short) 1, (short) 2, (short) 3 },
                { (short) 4, (short) 5, (short) 6 } });
        final String s = "this is a string";
        writer.string().write("stringDS", s);
        writer.string().writeVL("stringDSVL", s);
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(file);
        final HDF5DataSetInformation scalarInfo = reader.getDataSetInformation("dsScalar");
        assertEquals(HDF5DataClass.INTEGER, scalarInfo.getTypeInformation().getDataClass());
        assertEquals(4, scalarInfo.getTypeInformation().getElementSize());
        assertEquals(0, scalarInfo.getRank());
        assertTrue(scalarInfo.isScalar());
        assertEquals(0, scalarInfo.getDimensions().length);
        assertNull(scalarInfo.tryGetChunkSizes());
        final HDF5DataSetInformation info = reader.getDataSetInformation("ds");
        assertEquals(HDF5DataClass.INTEGER, info.getTypeInformation().getDataClass());
        assertEquals(2, info.getTypeInformation().getElementSize());
        assertEquals(2, info.getRank());
        assertFalse(info.isScalar());
        assertEquals(2, info.getDimensions()[0]);
        assertEquals(3, info.getDimensions()[1]);
        assertChunkSizes(info, 2, 3);
        final HDF5DataSetInformation stringInfo = reader.getDataSetInformation("stringDS");
        assertEquals(HDF5DataClass.STRING, stringInfo.getTypeInformation().getDataClass());
        assertEquals(s.length(), stringInfo.getTypeInformation().getElementSize());
        assertEquals(0, stringInfo.getDimensions().length);
        assertEquals(0, stringInfo.getMaxDimensions().length);
        assertEquals(HDF5StorageLayout.COMPACT, stringInfo.getStorageLayout());
        assertNull(stringInfo.tryGetChunkSizes());
        final HDF5DataSetInformation stringInfoVL = reader.getDataSetInformation("stringDSVL");
        assertEquals(HDF5DataClass.STRING, stringInfoVL.getTypeInformation().getDataClass());
        assertTrue(stringInfoVL.getTypeInformation().isVariableLengthString());
        assertEquals(-1, stringInfoVL.getTypeInformation().getElementSize());
        assertEquals(0, stringInfoVL.getDimensions().length);
        assertEquals(HDF5StorageLayout.COMPACT, stringInfoVL.getStorageLayout());
        assertNull(stringInfoVL.tryGetChunkSizes());
        assertEquals(0, stringInfoVL.getDimensions().length);
        assertEquals(0, stringInfoVL.getMaxDimensions().length);
        reader.close();
    }

    @Test(expectedExceptions = HDF5SymbolTableException.class)
    public void testGetDataSetInformationFailed()
    {
        final File file = new File(workingDirectory, "dsInfo.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(file);
        try
        {
            assertFalse(writer.exists("non_existent"));
            writer.getDataSetInformation("non_existent");
        } finally
        {
            writer.close();
        }
    }

    @Test
    public void testGetGroupMemberInformation()
    {
        final File groupFile = new File(workingDirectory, "groupMemberInformation.h5");
        groupFile.delete();
        assertFalse(groupFile.exists());
        groupFile.deleteOnExit();
        final String groupName1 = "/group";
        final String groupName2 = "/dataSetGroup";
        final String dataSetName = groupName2 + "/dataset";
        final String dataSetName2 = "ds2";
        final String sLinkName = "/slink";
        final String tFileName = "target.h5";
        final String dataSetName3 = "ds3";
        final String eLinkName = "/elink";
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(groupFile);
        try
        {
            writer.object().createGroup(groupName1);
            writer.int8().writeArray(dataSetName, new byte[]
                { 1 });
            writer.string().write(dataSetName2, "abc");
            writer.object().createSoftLink(dataSetName2, sLinkName);
            writer.object().createExternalLink(tFileName, dataSetName3, eLinkName);
        } finally
        {
            writer.close();
        }
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(groupFile);
        final Map<String, HDF5LinkInformation> map = new HashMap<String, HDF5LinkInformation>();
        for (HDF5LinkInformation info : reader.object().getAllGroupMemberInformation("/", false))
        {
            map.put(info.getPath(), info);
        }
        HDF5LinkInformation info;
        assertEquals(6, map.size());
        info = map.get(groupName1);
        assertNotNull(info);
        assertTrue(info.exists());
        assertEquals(HDF5ObjectType.GROUP, info.getType());
        assertNull(info.tryGetSymbolicLinkTarget());
        info = map.get(groupName2);
        assertNotNull(info);
        assertTrue(info.exists());
        assertEquals(HDF5ObjectType.GROUP, info.getType());
        assertNull(info.tryGetSymbolicLinkTarget());
        info = map.get("/" + dataSetName2);
        assertNotNull(info);
        assertTrue(info.exists());
        assertEquals(HDF5ObjectType.DATASET, info.getType());
        assertNull(info.tryGetSymbolicLinkTarget());
        info = map.get(sLinkName);
        assertNotNull(info);
        assertTrue(info.exists());
        assertEquals(HDF5ObjectType.SOFT_LINK, info.getType());
        assertNull(info.tryGetSymbolicLinkTarget());
        info = map.get(eLinkName);
        assertNotNull(info);
        assertTrue(info.exists());
        assertEquals(HDF5ObjectType.EXTERNAL_LINK, info.getType());
        assertNull(info.tryGetSymbolicLinkTarget());

        map.clear();
        for (HDF5LinkInformation info2 : reader.object().getGroupMemberInformation("/", true))
        {
            map.put(info2.getPath(), info2);
        }
        assertEquals(5, map.size());
        info = map.get(groupName1);
        assertNotNull(info);
        assertTrue(info.exists());
        assertEquals(HDF5ObjectType.GROUP, info.getType());
        assertNull(info.tryGetSymbolicLinkTarget());
        info = map.get(groupName2);
        assertNotNull(info);
        assertTrue(info.exists());
        assertEquals(HDF5ObjectType.GROUP, info.getType());
        assertNull(info.tryGetSymbolicLinkTarget());
        info = map.get("/" + dataSetName2);
        assertNotNull(info);
        assertTrue(info.exists());
        assertEquals(HDF5ObjectType.DATASET, info.getType());
        assertNull(info.tryGetSymbolicLinkTarget());
        info = map.get(sLinkName);
        assertNotNull(info);
        assertTrue(info.exists());
        assertEquals(HDF5ObjectType.SOFT_LINK, info.getType());
        assertEquals(dataSetName2, info.tryGetSymbolicLinkTarget());
        assertNull(info.tryGetExternalLinkFilename());
        assertNull(info.tryGetExternalLinkTarget());
        info = map.get(eLinkName);
        assertNotNull(info);
        assertTrue(info.exists());
        assertEquals(HDF5ObjectType.EXTERNAL_LINK, info.getType());
        assertEquals("EXTERNAL::" + tFileName + "::" + dataSetName3, info.tryGetSymbolicLinkTarget());
        assertEquals(tFileName, info.tryGetExternalLinkFilename());
        assertEquals(dataSetName3, info.tryGetExternalLinkTarget());

        reader.close();
    }

    @Test
    public void testHardLink()
    {
        final File linkFile = new File(workingDirectory, "hardLink.h5");
        linkFile.delete();
        assertFalse(linkFile.exists());
        linkFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(linkFile);
        final String str = "BlaBlub";
        writer.string().write("/data/set", str);
        writer.object().createHardLink("/data/set", "/data/link");
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(linkFile);
        assertEquals(HDF5ObjectType.DATASET, reader.object().getObjectType("/data/link"));
        assertEquals(str, reader.readString("/data/link"));
        reader.close();
    }

    @Test
    public void testSoftLink()
    {
        final File linkFile = new File(workingDirectory, "softLink.h5");
        linkFile.delete();
        assertFalse(linkFile.exists());
        linkFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(linkFile);
        writer.writeBoolean("/data/set", true);
        writer.object().createSoftLink("/data/set", "/data/link");
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(linkFile);
        assertEquals(HDF5ObjectType.SOFT_LINK, reader.object().getObjectType("/data/link", false));
        assertEquals("/data/set", reader.object().getLinkInformation("/data/link")
                .tryGetSymbolicLinkTarget());
        reader.close();
    }

    @Test
    public void testUpdateSoftLink()
    {
        final File file = new File(workingDirectory, "updateSoftLink.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(file);
        final long now = System.currentTimeMillis();
        final String dataSetName1 = "creationTime1";
        final String dataSetName2 = "creationTime2";
        final String linkName = "time";
        writer.time().write(dataSetName1, now);
        writer.time().write(dataSetName2, now);
        writer.object().createSoftLink(dataSetName1, linkName);
        writer.object().createOrUpdateSoftLink(dataSetName2, linkName);
        try
        {
            writer.object().createOrUpdateSoftLink(dataSetName1, dataSetName2);
        } catch (HDF5LibraryException ex)
        {
            assertEquals(HDF5Constants.H5E_EXISTS, ex.getMinorErrorNumber());
        }
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(file);
        assertEquals(dataSetName2, reader.object().getLinkInformation(linkName)
                .tryGetSymbolicLinkTarget());
        reader.close();
    }

    @Test
    public void testBrokenSoftLink()
    {
        final File linkFile = new File(workingDirectory, "brokenSoftLink.h5");
        linkFile.delete();
        assertFalse(linkFile.exists());
        linkFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(linkFile);
        writer.object().createSoftLink("/does/not/exist", "/linkToNowhere");
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(linkFile);
        assertFalse(reader.exists("/linkToNowhere"));
        assertTrue(reader.object().exists("/linkToNowhere", false));
        assertEquals(HDF5ObjectType.SOFT_LINK,
                reader.object().getObjectType("/linkToNowhere", false));
        assertEquals("/does/not/exist", reader.object().getLinkInformation("/linkToNowhere")
                .tryGetSymbolicLinkTarget());
        reader.close();
    }

    @Test
    public void testDeleteSoftLink()
    {
        final File linkFile = new File(workingDirectory, "deleteSoftLink.h5");
        linkFile.delete();
        assertFalse(linkFile.exists());
        linkFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(linkFile);
        writer.writeBoolean("/group/boolean", true);
        writer.object().createSoftLink("/group", "/link");
        writer.delete("/link");
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(linkFile);
        assertFalse(reader.object().exists("/link", false));
        assertTrue(reader.exists("/group"));
        assertTrue(reader.exists("/group/boolean"));
        reader.close();
    }

    @Test
    public void testNullOnGetSymbolicLinkTargetForNoLink()
    {
        final File noLinkFile = new File(workingDirectory, "noLink.h5");
        noLinkFile.delete();
        assertFalse(noLinkFile.exists());
        noLinkFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(noLinkFile);
        writer.writeBoolean("/data/set", true);
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(noLinkFile);
        try
        {
            assertNull(reader.object().getLinkInformation("/data/set").tryGetSymbolicLinkTarget());
        } finally
        {
            reader.close();
        }
    }

    @Test
    public void testExternalLink()
    {
        final File fileToLinkTo = new File(workingDirectory, "fileToLinkTo.h5");
        fileToLinkTo.delete();
        assertFalse(fileToLinkTo.exists());
        fileToLinkTo.deleteOnExit();
        final IHDF5Writer writer1 = HDF5FactoryProvider.get().open(fileToLinkTo);
        final String dataSetName = "/data/set";
        final String dataSetValue = "Some data set value...";
        writer1.string().write(dataSetName, dataSetValue);
        writer1.close();
        final File linkFile = new File(workingDirectory, "externalLink.h5");
        linkFile.delete();
        assertFalse(linkFile.exists());
        linkFile.deleteOnExit();
        final IHDF5Writer writer2 = HDF5FactoryProvider.get().open(linkFile);
        final String linkName = "/data/link";
        writer2.object().createExternalLink(fileToLinkTo.getPath(), dataSetName, linkName);
        writer2.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(linkFile);
        assertEquals(HDF5ObjectType.EXTERNAL_LINK, reader.object().getObjectType(linkName, false));
        assertEquals(dataSetValue, reader.readString(linkName));
        final String expectedExternalLinkFilename = OSUtilities.isWindows() ? "targets\\unit-test-wd\\hdf5-roundtrip-wd\\fileToLinkTo.h5"
                : "targets/unit-test-wd/hdf5-roundtrip-wd/fileToLinkTo.h5";
        final String expectedLink = "EXTERNAL::" + expectedExternalLinkFilename + "::" + dataSetName;
        final HDF5LinkInformation info = reader.object().getLinkInformation(linkName);
        assertEquals(expectedLink, info.tryGetSymbolicLinkTarget());
        assertEquals(expectedExternalLinkFilename, info.tryGetExternalLinkFilename());
        assertEquals(dataSetName, info.tryGetExternalLinkTarget());
        reader.close();
    }

    @Test
    public void testDataTypeInfoOptions()
    {
        final File file = new File(workingDirectory, "dataTypeInfoOptions.h5");
        final String enumDsName = "/testEnum";
        final String dateDsName = "/testDate";
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer writer = HDF5Factory.configure(file).writer();
        writer.enumeration().write(enumDsName, JavaEnum.TWO);
        writer.time().write(dateDsName, new Date(10000L));
        writer.close();
        final IHDF5Reader reader = HDF5Factory.openForReading(file);
        final HDF5DataTypeInformation minimalEnumInfo =
                reader.object().getDataSetInformation(enumDsName, DataTypeInfoOptions.MINIMAL)
                        .getTypeInformation();
        assertFalse(minimalEnumInfo.knowsDataTypePath());
        assertFalse(minimalEnumInfo.knowsDataTypeVariant());
        assertNull(minimalEnumInfo.tryGetName());
        assertNull(minimalEnumInfo.tryGetTypeVariant());
        final HDF5DataTypeInformation defaultInfo =
                reader.getDataSetInformation(enumDsName).getTypeInformation();
        assertFalse(defaultInfo.knowsDataTypePath());
        assertTrue(defaultInfo.knowsDataTypeVariant());
        assertNull(defaultInfo.tryGetName());
        assertEquals(HDF5DataTypeVariant.NONE, defaultInfo.tryGetTypeVariant());
        final HDF5DataTypeInformation allInfo =
                reader.object().getDataSetInformation(enumDsName, DataTypeInfoOptions.ALL)
                        .getTypeInformation();
        assertTrue(allInfo.knowsDataTypePath());
        assertTrue(allInfo.knowsDataTypeVariant());
        assertEquals(JavaEnum.class.getSimpleName(), allInfo.tryGetName());

        final HDF5DataTypeInformation minimalDateInfo =
                reader.object().getDataSetInformation(dateDsName, DataTypeInfoOptions.MINIMAL)
                        .getTypeInformation();
        assertFalse(minimalDateInfo.knowsDataTypePath());
        assertFalse(minimalDateInfo.knowsDataTypeVariant());
        assertNull(minimalDateInfo.tryGetName());
        assertNull(minimalDateInfo.tryGetTypeVariant());

        final HDF5DataTypeInformation defaultDateInfo =
                reader.object().getDataSetInformation(dateDsName, DataTypeInfoOptions.DEFAULT)
                        .getTypeInformation();
        assertFalse(defaultDateInfo.knowsDataTypePath());
        assertTrue(defaultDateInfo.knowsDataTypeVariant());
        assertNull(defaultDateInfo.tryGetName());
        assertEquals(HDF5DataTypeVariant.TIMESTAMP_MILLISECONDS_SINCE_START_OF_THE_EPOCH,
                defaultDateInfo.tryGetTypeVariant());

        final HDF5DataTypeInformation allDateInfo =
                reader.object().getDataSetInformation(dateDsName, DataTypeInfoOptions.ALL)
                        .getTypeInformation();
        assertTrue(allDateInfo.knowsDataTypePath());
        assertTrue(allDateInfo.knowsDataTypeVariant());
        assertNull(allDateInfo.tryGetName());
        assertEquals(HDF5DataTypeVariant.TIMESTAMP_MILLISECONDS_SINCE_START_OF_THE_EPOCH,
                allDateInfo.tryGetTypeVariant());

        reader.close();
    }

    enum JavaEnum
    {
        ONE, TWO, THREE
    }

    @Test
    public void testJavaEnum()
    {
        final File file = new File(workingDirectory, "javaEnum.h5");
        final String dsName = "/testEnum";
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer writer = HDF5Factory.configure(file).keepDataSetsIfTheyExist().writer();
        writer.enumeration().write(dsName, JavaEnum.THREE);
        writer.enumeration().setAttr(dsName, "attr", JavaEnum.TWO);
        writer.close();
        final IHDF5Reader reader = HDF5Factory.openForReading(file);
        assertEquals(JavaEnum.THREE, reader.enumeration().read(dsName, JavaEnum.class));
        assertEquals(JavaEnum.TWO,
                reader.enumeration().getAttr(dsName, "attr").getValue(JavaEnum.class));
        final String valueStr = reader.readEnumAsString(dsName);
        assertEquals("THREE", valueStr);
        final HDF5EnumerationValue value = reader.enumeration().read(dsName);
        assertEquals("THREE", value.getValue());
        final String expectedDataTypePath =
                HDF5Utils.createDataTypePath(HDF5Utils.ENUM_PREFIX, "",
                        JavaEnum.class.getSimpleName());
        assertEquals(expectedDataTypePath, reader.object().tryGetDataTypePath(value.getType()));
        assertEquals(expectedDataTypePath, reader.object().tryGetDataTypePath(dsName));
        final HDF5EnumerationType type = reader.enumeration().getDataSetType(dsName);
        assertEquals(3, type.getValues().size());
        assertEquals("ONE", type.getValues().get(0));
        assertEquals("TWO", type.getValues().get(1));
        assertEquals("THREE", type.getValues().get(2));
        reader.close();
    }

    @Test
    public void testEnum()
    {
        final File file = new File(workingDirectory, "enum.h5");
        final String enumTypeName = "testEnumType";
        final String dsName = "/testEnum";
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer writer = HDF5Factory.configure(file).keepDataSetsIfTheyExist().writer();
        HDF5EnumerationType type = writer.enumeration().getType(enumTypeName, new String[]
            { "ONE", "TWO", "THREE" }, false);
        writer.enumeration().write(dsName, new HDF5EnumerationValue(type, "THREE"));
        // That is wrong, but we disable the check, so no exception should be thrown.
        writer.enumeration().getType(enumTypeName, new String[]
            { "THREE", "ONE", "TWO" }, false);
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(file);
        type = reader.enumeration().getType(enumTypeName);
        assertEquals(enumTypeName, type.tryGetName());
        final HDF5DataTypeInformation typeInfo =
                reader.object().getDataSetInformation(dsName, DataTypeInfoOptions.ALL)
                        .getTypeInformation();
        assertEquals(enumTypeName, typeInfo.tryGetName());
        assertEquals(HDF5Utils.createDataTypePath(HDF5Utils.ENUM_PREFIX, "", enumTypeName),
                typeInfo.tryGetDataTypePath());
        final String valueStr = reader.readEnumAsString(dsName);
        assertEquals("THREE", valueStr);
        final HDF5EnumerationValue value = reader.enumeration().read(dsName);
        assertEquals("THREE", value.getValue());
        final String expectedDataTypePath =
                HDF5Utils.createDataTypePath(HDF5Utils.ENUM_PREFIX, "", enumTypeName);
        assertEquals(expectedDataTypePath, reader.object().tryGetDataTypePath(value.getType()));
        assertEquals(expectedDataTypePath, reader.object().tryGetDataTypePath(dsName));
        type = reader.enumeration().getDataSetType(dsName);
        assertEquals("THREE", reader.enumeration().read(dsName, type).getValue());
        reader.close();
        final IHDF5Writer writer2 = HDF5FactoryProvider.get().open(file);
        type = writer2.enumeration().getType(enumTypeName, new String[]
            { "ONE", "TWO", "THREE" }, true);
        assertEquals("THREE", writer2.enumeration().read(dsName, type).getValue());
        writer2.close();
    }

    enum NumberEnum
    {
        ONE, TWO, THREE, FOUR, FIVE
    }

    @Test
    public void testAnonymousEnum()
    {
        final File file = new File(workingDirectory, "anonymousEnum.h5");
        final String dsName = "/testEnum";
        final String dsName2 = "/testEnum2";
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer writer = HDF5Factory.configure(file).keepDataSetsIfTheyExist().writer();
        HDF5EnumerationType type = writer.enumeration().getAnonType(new String[]
            { "ONE", "TWO", "THREE", "FOUR", "INFINITY" });
        writer.enumeration().write(dsName, new HDF5EnumerationValue(type, "INFINITY"));
        HDF5EnumerationType type2 = writer.enumeration().getAnonType(NumberEnum.class);
        writer.enumeration().write(dsName2, new HDF5EnumerationValue(type2, NumberEnum.FIVE));
        writer.close();

        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(file);
        assertEquals("INFINITY", reader.readEnumAsString(dsName));
        assertEquals("INFINITY", reader.enumeration().read(dsName).getValue());
        assertEquals("FIVE", reader.readEnumAsString(dsName2));
        assertEquals(NumberEnum.FIVE, reader.enumeration().read(dsName2).getValue(NumberEnum.class));
        reader.close();
    }

    @Test
    public void testEnum16()
    {
        final File file = new File(workingDirectory, "enum16bit.h5");
        final String enumTypeName = "testEnumType16";
        final String dsName = "/testEnum";
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer writer = HDF5Factory.configure(file).keepDataSetsIfTheyExist().writer();
        HDF5EnumerationType type = createEnum16Bit(writer, enumTypeName);
        writer.enumeration().write(dsName, new HDF5EnumerationValue(type, "17"));
        final String[] confusedValues = new String[type.getEnumType().getValueArray().length];
        System.arraycopy(confusedValues, 0, confusedValues, 1, confusedValues.length - 1);
        confusedValues[0] = "XXX";
        // This is wrong, but we disabled the check.
        writer.enumeration().getType(enumTypeName, confusedValues, false);
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(file);
        type = reader.enumeration().getType(enumTypeName);
        final String valueStr = reader.readEnumAsString(dsName);
        assertEquals("17", valueStr);
        final HDF5EnumerationValue value = reader.enumeration().read(dsName);
        assertEquals("17", value.getValue());
        type = reader.enumeration().getDataSetType(dsName);
        assertEquals("17", reader.enumeration().read(dsName, type).getValue());
        reader.close();
        final IHDF5Writer writer2 = HDF5FactoryProvider.get().open(file);
        type =
                writer2.enumeration().getType(enumTypeName, type.getEnumType().getValueArray(),
                        true);
        assertEquals("17", writer2.enumeration().read(dsName, type).getValue());
        // That is wrong, but we disable the check, so no exception should be thrown.
        writer2.close();
    }

    @Test(expectedExceptions = HDF5JavaException.class)
    public void testConfusedEnum()
    {
        final File file = new File(workingDirectory, "confusedEnum.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        IHDF5Writer writer = HDF5Factory.open(file);
        HDF5EnumerationType type = writer.enumeration().getType("testEnum", new String[]
            { "ONE", "TWO", "THREE" }, false);
        writer.enumeration().write("/testEnum", new HDF5EnumerationValue(type, 2));
        writer.close();
        try
        {
            writer = HDF5Factory.configure(file).keepDataSetsIfTheyExist().writer();
            writer.enumeration().getType("testEnum", new String[]
                { "THREE", "ONE", "TWO" }, true);
        } finally
        {
            writer.close();
        }
    }

    @Test
    public void testReplaceConfusedEnum()
    {
        final File file = new File(workingDirectory, "replaceConfusedEnum.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        IHDF5Writer writer = HDF5Factory.open(file);
        HDF5EnumerationType type = writer.enumeration().getType("testEnum", new String[]
            { "ONE", "TWO", "THREE" }, false);
        writer.enumeration().write("/testEnum", new HDF5EnumerationValue(type, 2));
        writer.close();
        writer = HDF5Factory.open(file);
        final HDF5EnumerationType type2 = writer.enumeration().getType("testEnum", new String[]
            { "THREE", "ONE", "TWO" }, true);
        assertEquals("testEnum", type2.getName());
        assertEquals("testEnum__REPLACED_1", writer.enumeration().getDataSetType("/testEnum")
                .getName());
        writer.close();
    }

    @Test
    public void testEnumArray()
    {
        final File file = new File(workingDirectory, "enumArray.h5");
        final String enumTypeName = "testEnum";
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(file);
        HDF5EnumerationType enumType = writer.enumeration().getType(enumTypeName, new String[]
            { "ONE", "TWO", "THREE" }, false);
        HDF5EnumerationValueArray arrayWritten =
                new HDF5EnumerationValueArray(enumType, new String[]
                    { "TWO", "ONE", "THREE" });
        writer.enumeration().writeArray("/testEnum", arrayWritten);
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(file);
        final HDF5EnumerationValueArray arrayRead = reader.enumeration().readArray("/testEnum");
        enumType = reader.enumeration().getDataSetType("/testEnum");
        final HDF5EnumerationValueArray arrayRead2 =
                reader.enumeration().readArray("/testEnum", enumType);
        final String[] stringArrayRead =
                reader.enumeration().readArray("/testEnum").toStringArray();
        assertEquals(arrayWritten.getLength(), stringArrayRead.length);
        assertEquals(arrayWritten.getLength(), arrayRead.getLength());
        assertEquals(arrayWritten.getLength(), arrayRead2.getLength());
        for (int i = 0; i < stringArrayRead.length; ++i)
        {
            assertEquals("Index " + i, arrayWritten.getValue(i), arrayRead.getValue(i));
            assertEquals("Index " + i, arrayWritten.getValue(i), arrayRead2.getValue(i));
            assertEquals("Index " + i, arrayWritten.getValue(i), stringArrayRead[i]);
        }
        reader.close();
    }

    @Test
    public void testEnumMDArray()
    {
        final File file = new File(workingDirectory, "enumMDArray.h5");
        final String enumTypeName = "testEnum";
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(file);
        HDF5EnumerationValueMDArray arrayWritten =
                writer.enumeration().newMDArray(
                        enumTypeName,
                        new String[]
                            { "ONE", "TWO", "THREE" },
                        new MDArray<String>(new String[]
                            { "TWO", "ONE", "THREE", "TWO", "ONE", "THREE", "TWO", "ONE", "THREE",
                                    "TWO", "ONE", "THREE", "TWO", "ONE", "THREE", "TWO", "ONE",
                                    "THREE", "TWO", "ONE", "THREE", "TWO", "ONE", "THREE" },
                                new int[]
                                    { 2, 3, 4 }));
        writer.enumeration().writeMDArray("/testEnum", arrayWritten);
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(file);
        final HDF5EnumerationValueMDArray arrayRead = reader.enumeration().readMDArray("/testEnum");
        final HDF5EnumerationType enumType = reader.enumeration().getDataSetType("/testEnum");
        final HDF5EnumerationValueMDArray arrayRead2 =
                reader.enumeration().readMDArray("/testEnum", enumType);
        final MDArray<String> stringArrayRead = arrayRead2.toStringArray();
        assertTrue(Arrays.equals(arrayWritten.dimensions(), stringArrayRead.dimensions()));
        assertTrue(Arrays.equals(arrayWritten.dimensions(), arrayRead.dimensions()));
        assertTrue(Arrays.equals(arrayWritten.dimensions(), arrayRead2.dimensions()));
        for (int i = 0; i < stringArrayRead.size(); ++i)
        {
            assertEquals("Index " + i, arrayWritten.getValue(i), arrayRead.getValue(i));
            assertEquals("Index " + i, arrayWritten.getValue(i), arrayRead2.getValue(i));
            assertEquals("Index " + i, arrayWritten.getValue(i), stringArrayRead.get(i));
        }
        reader.close();
    }

    @Test
    public void testEnumMDArrayBlockWise()
    {
        final File file = new File(workingDirectory, "enumMDArrayBlockWise.h5");
        final String enumTypeName = "testEnum";
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(file);
        HDF5EnumerationType enumType = writer.enumeration().getType(enumTypeName, new String[]
            { "ONE", "TWO", "THREE" }, false);
        HDF5EnumerationValueMDArray arrayBlockWritten1 =
                new HDF5EnumerationValueMDArray(enumType, new MDArray<String>(new String[]
                    { "TWO", "ONE", "THREE", "THREE", "TWO", "ONE", }, new int[]
                    { 2, 3, 1 }));
        HDF5EnumerationValueMDArray arrayBlockWritten2 =
                new HDF5EnumerationValueMDArray(enumType, new MDArray<String>(new String[]
                    { "ONE", "TWO", "THREE", "THREE", "TWO", "ONE", }, new int[]
                    { 2, 3, 1 }));
        HDF5EnumerationValueMDArray arrayBlockWritten3 =
                new HDF5EnumerationValueMDArray(enumType, new MDArray<String>(new String[]
                    { "THREE", "TWO", "ONE", "ONE", "TWO", "THREE", }, new int[]
                    { 2, 3, 1 }));
        writer.enumeration().createMDArray("/testEnum", enumType, new int[]
            { 2, 3, 1 });
        for (int i = 0; i < 4; ++i)
        {
            writer.enumeration().writeMDArrayBlock("/testEnum", arrayBlockWritten1, new long[]
                { 0, 0, i });
            writer.enumeration().writeMDArrayBlock("/testEnum", arrayBlockWritten2, new long[]
                { 1, 0, i });
            writer.enumeration().writeMDArrayBlock("/testEnum", arrayBlockWritten3, new long[]
                { 0, 1, i });
        }
        writer.close();

        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(file);
        assertTrue(Arrays.equals(new long[]
            { 4, 6, 4 }, reader.getDataSetInformation("/testEnum").getDimensions()));
        for (int i = 0; i < 4; ++i)
        {
            assertEquals(arrayBlockWritten1,
                    reader.enumeration().readMDArrayBlock("/testEnum", new int[]
                        { 2, 3, 1 }, new long[]
                        { 0, 0, i }));
            assertEquals(arrayBlockWritten2,
                    reader.enumeration().readMDArrayBlock("/testEnum", new int[]
                        { 2, 3, 1 }, new long[]
                        { 1, 0, i }));
            assertEquals(arrayBlockWritten3,
                    reader.enumeration().readMDArrayBlock("/testEnum", new int[]
                        { 2, 3, 1 }, new long[]
                        { 0, 1, i }));
        }

        enumType = reader.enumeration().getDataSetType("/testEnum");
        for (int i = 0; i < 4; ++i)
        {
            assertEquals(arrayBlockWritten1,
                    reader.enumeration().readMDArrayBlock("/testEnum", enumType, new int[]
                        { 2, 3, 1 }, new long[]
                        { 0, 0, i }));
            assertEquals(arrayBlockWritten2,
                    reader.enumeration().readMDArrayBlock("/testEnum", enumType, new int[]
                        { 2, 3, 1 }, new long[]
                        { 1, 0, i }));
            assertEquals(arrayBlockWritten3,
                    reader.enumeration().readMDArrayBlock("/testEnum", enumType, new int[]
                        { 2, 3, 1 }, new long[]
                        { 0, 1, i }));
        }
        for (HDF5MDEnumBlock block : reader.enumeration().getMDArrayBlocks("/testEnum", enumType))
        {
            assertTrue(Long.toString(block.getIndex()[2]),
                    block.getIndex()[2] >= 0 && block.getIndex()[2] < 4);
            if (block.getIndex()[0] == 0 && block.getIndex()[1] == 0)
            {
                assertEquals(arrayBlockWritten1, block.getData());
            } else if (block.getIndex()[0] == 0 && block.getIndex()[1] == 1)
            {
                assertEquals(arrayBlockWritten3, block.getData());
            } else if (block.getIndex()[0] == 1 && block.getIndex()[1] == 0)
            {
                assertEquals(arrayBlockWritten2, block.getData());
            } else if (block.getIndex()[0] == 1 && block.getIndex()[1] == 1)
            {
                assertTrue(Arrays.equals(new int[]
                    { 2, 3, 1 }, block.getData().dimensions()));
                assertTrue(Arrays.equals(new byte[6], ((MDByteArray) block.getData()
                        .getOrdinalValues()).getAsFlatArray()));
            } else
            {
                fail("Unexpected index " + Arrays.toString(block.getIndex()));
            }
        }
        reader.close();
    }

    @Test
    public void testJavaEnumArray()
    {
        final File file = new File(workingDirectory, "javaEnumArray.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(file);
        final JavaEnum[] arrayWritten = new JavaEnum[]
            { JavaEnum.TWO, JavaEnum.ONE, JavaEnum.THREE, JavaEnum.ONE };
        final JavaEnum[] arrayBlockTwoWritten = new JavaEnum[]
            { JavaEnum.THREE, JavaEnum.ONE, JavaEnum.TWO, JavaEnum.THREE };
        writer.enumeration().writeArray("/testEnum", writer.enumeration().newArray(arrayWritten));
        final HDF5EnumerationType type =
                writer.enumeration().createArray("/testEnumBlockwise",
                        writer.enumeration().getType(JavaEnum.class), 16);
        writer.enumeration().writeArrayBlock("/testEnumBlockwise",
                new HDF5EnumerationValueArray(type, arrayWritten), 0);
        writer.enumeration().writeArrayBlock("/testEnumBlockwise",
                writer.enumeration().newArray(arrayBlockTwoWritten), 1);
        writer.enumeration().writeArrayBlockWithOffset("/testEnumBlockwise",
                new HDF5EnumerationValueArray(type, arrayBlockTwoWritten),
                arrayBlockTwoWritten.length, 8);
        writer.enumeration().writeArrayBlockWithOffset("/testEnumBlockwise",
                writer.enumeration().newArray(arrayWritten), arrayWritten.length, 12);
        final JavaEnum[] attributeArrayWritten = new JavaEnum[]
            { JavaEnum.THREE, JavaEnum.ONE, JavaEnum.TWO };
        writer.enumeration().setArrayAttr("/testEnum", "attr",
                writer.enumeration().newArray(attributeArrayWritten));
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(file);
        final JavaEnum[] arrayRead =
                reader.enumeration().readArray("/testEnum").toEnumArray(JavaEnum.class);
        final JavaEnum[] attributeArrayRead =
                reader.enumeration().getArrayAttr("/testEnum", "attr").toEnumArray(JavaEnum.class);
        final JavaEnum[] arrayBlockRead0 =
                reader.enumeration().readArrayBlock("/testEnumBlockwise", 4, 0)
                        .toEnumArray(JavaEnum.class);
        final JavaEnum[] arrayBlockRead1 =
                reader.enumeration().readArrayBlock("/testEnumBlockwise", 4, 1)
                        .toEnumArray(JavaEnum.class);
        final JavaEnum[] arrayBlockRead2 =
                reader.enumeration().readArrayBlock("/testEnumBlockwise", 4, 2)
                        .toEnumArray(JavaEnum.class);
        final JavaEnum[] arrayBlockRead3 =
                reader.enumeration().readArrayBlock("/testEnumBlockwise", 4, 3)
                        .toEnumArray(JavaEnum.class);
        reader.close();
        assertEquals(arrayWritten.length, arrayRead.length);
        for (int i = 0; i < arrayWritten.length; ++i)
        {
            assertEquals(arrayWritten[i], arrayRead[i]);
        }
        assertEquals(attributeArrayWritten.length, attributeArrayRead.length);
        for (int i = 0; i < attributeArrayWritten.length; ++i)
        {
            assertEquals(attributeArrayWritten[i], attributeArrayRead[i]);
        }
        assertEquals(arrayWritten.length, arrayBlockRead0.length);
        assertEquals(arrayWritten.length, arrayBlockRead1.length);
        assertEquals(arrayWritten.length, arrayBlockRead2.length);
        assertEquals(arrayWritten.length, arrayBlockRead3.length);
        for (int i = 0; i < arrayWritten.length; ++i)
        {
            assertEquals(arrayWritten[i], arrayBlockRead0[i]);
            assertEquals(arrayBlockTwoWritten[i], arrayBlockRead1[i]);
            assertEquals(arrayBlockTwoWritten[i], arrayBlockRead2[i]);
            assertEquals(arrayWritten[i], arrayBlockRead3[i]);
        }
    }

    @Test
    public void testEnumArrayBlock()
    {
        final File file = new File(workingDirectory, "enumArrayBlock.h5");
        final String enumTypeName = "testEnum";
        final int chunkSize = 4;
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(file);
        HDF5EnumerationType enumType = writer.enumeration().getType(enumTypeName, new String[]
            { "ONE", "TWO", "THREE" }, false);
        writer.enumeration().createArray("/testEnum", enumType, chunkSize);
        HDF5EnumerationValueArray arrayWritten =
                new HDF5EnumerationValueArray(enumType, new String[]
                    { "TWO", "ONE", "THREE", "TWO" });
        writer.enumeration().writeArrayBlock("/testEnum", arrayWritten, 1);
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(file);
        final HDF5EnumerationValueArray arrayReadBlock0 =
                reader.enumeration().readArrayBlock(enumTypeName, chunkSize, 0);
        enumType = reader.enumeration().getDataSetType(enumTypeName);
        final HDF5EnumerationValueArray arrayReadBlock1 =
                reader.enumeration().readArrayBlock(enumTypeName, enumType, chunkSize, 1);
        final String[] stringArrayRead =
                reader.enumeration().readArray(enumTypeName).toStringArray();
        assertEquals(arrayWritten.getLength() * 2, stringArrayRead.length);
        assertEquals(arrayWritten.getLength(), arrayReadBlock0.getLength());
        assertEquals(arrayWritten.getLength(), arrayReadBlock1.getLength());
        for (int i = 0; i < arrayReadBlock0.getLength(); ++i)
        {
            assertEquals("Index " + i, "ONE", arrayReadBlock0.getValue(i));
            assertEquals("Index " + i, "ONE", stringArrayRead[i]);
        }
        for (int i = 0; i < arrayReadBlock0.getLength(); ++i)
        {
            assertEquals("Index " + i, arrayWritten.getValue(i), arrayReadBlock1.getValue(i));
            assertEquals("Index " + i, arrayWritten.getValue(i), stringArrayRead[chunkSize + i]);
        }
        final HDF5EnumerationValueArray[] dataBlocksExpected = new HDF5EnumerationValueArray[]
            { arrayReadBlock0, arrayReadBlock1 };
        int blockIndex = 0;
        for (HDF5DataBlock<HDF5EnumerationValueArray> block : reader.enumeration().getArrayBlocks(
                enumTypeName, enumType))
        {
            final HDF5EnumerationValueArray blockExpected = dataBlocksExpected[blockIndex++];
            final HDF5EnumerationValueArray blockRead = block.getData();
            assertEquals(chunkSize, blockRead.getLength());
            for (int i = 0; i < blockExpected.getLength(); ++i)
            {
                assertEquals("Index " + i, blockExpected.getValue(i), blockRead.getValue(i));
            }
        }
        reader.close();
    }

    @Test
    public void testEnumArrayBlockScalingCompression()
    {
        final File file = new File(workingDirectory, "enumArrayBlockScalingCompression.h5");
        final String enumTypeName = "testEnum";
        final int chunkSize = 4;
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(file);
        HDF5EnumerationType enumType = writer.enumeration().getType(enumTypeName, new String[]
            { "ONE", "TWO", "THREE" }, false);
        writer.enumeration().createArray("/testEnum", enumType, 0, chunkSize,
                HDF5IntStorageFeatures.INT_AUTO_SCALING);
        HDF5EnumerationValueArray arrayWritten =
                new HDF5EnumerationValueArray(enumType, new String[]
                    { "TWO", "ONE", "THREE", "ONE" });
        writer.enumeration().writeArrayBlock("/testEnum", arrayWritten, 1);
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(file);
        final HDF5EnumerationValueArray arrayReadBlock0 =
                reader.enumeration().readArrayBlock(enumTypeName, chunkSize, 0);
        enumType = reader.enumeration().getDataSetType(enumTypeName);
        final HDF5EnumerationValueArray arrayReadBlock1 =
                reader.enumeration().readArrayBlock(enumTypeName, enumType, chunkSize, 1);
        final String[] stringArrayRead =
                reader.enumeration().readArray(enumTypeName).toStringArray();
        assertEquals(arrayWritten.getLength() * 2, stringArrayRead.length);
        assertEquals(arrayWritten.getLength(), arrayReadBlock0.getLength());
        assertEquals(arrayWritten.getLength(), arrayReadBlock1.getLength());
        for (int i = 0; i < arrayReadBlock0.getLength(); ++i)
        {
            assertEquals("Index " + i, "ONE", arrayReadBlock0.getValue(i));
            assertEquals("Index " + i, "ONE", stringArrayRead[i]);
        }
        for (int i = 0; i < arrayReadBlock0.getLength(); ++i)
        {
            assertEquals("Index " + i, arrayWritten.getValue(i), arrayReadBlock1.getValue(i));
            assertEquals("Index " + i, arrayWritten.getValue(i), stringArrayRead[chunkSize + i]);
        }
        final HDF5EnumerationValueArray[] dataBlocksExpected = new HDF5EnumerationValueArray[]
            { arrayReadBlock0, arrayReadBlock1 };
        int blockIndex = 0;
        for (HDF5DataBlock<HDF5EnumerationValueArray> block : reader.enumeration().getArrayBlocks(
                enumTypeName, enumType))
        {
            final HDF5EnumerationValueArray blockExpected = dataBlocksExpected[blockIndex++];
            final HDF5EnumerationValueArray blockRead = block.getData();
            assertEquals(chunkSize, blockRead.getLength());
            for (int i = 0; i < blockExpected.getLength(); ++i)
            {
                assertEquals("Index " + i, blockExpected.getValue(i), blockRead.getValue(i));
            }
        }
        reader.close();
    }

    @Test
    public void testEnumArray16BitFromIntArray()
    {
        final File file = new File(workingDirectory, "enumArray16BitFromIntArray.h5");
        final String enumTypeName = "testEnum";
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(file);
        final HDF5EnumerationType enumType = createEnum16Bit(writer, enumTypeName);
        final int[] arrayWritten = new int[]
            { 8, 16, 722, 913, 333 };
        writer.enumeration().writeArray("/testEnum",
                new HDF5EnumerationValueArray(enumType, arrayWritten));
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(file);
        final String[] stringArrayRead =
                reader.enumeration().readArray("/testEnum").toStringArray();
        assertEquals(arrayWritten.length, stringArrayRead.length);
        for (int i = 0; i < stringArrayRead.length; ++i)
        {
            assertEquals("Index " + i, enumType.getValues().get(arrayWritten[i]),
                    stringArrayRead[i]);
        }
        final HDF5EnumerationValueArray arrayRead = reader.enumeration().readArray("/testEnum");
        assertEquals(arrayWritten.length, arrayRead.getLength());
        for (int i = 0; i < arrayRead.getLength(); ++i)
        {
            assertEquals("Index " + i, enumType.getValues().get(arrayWritten[i]),
                    arrayRead.getValue(i));
        }
        reader.close();
    }

    @Test
    public void testEnumArray16BitFromIntArrayScaled()
    {
        final File file = new File(workingDirectory, "testEnumArray16BitFromIntArrayScaled.h5");
        final String enumTypeName = "testEnum";
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(file);
        final HDF5EnumerationType enumType = createEnum16Bit(writer, enumTypeName);
        final int[] arrayWritten = new int[]
            { 8, 16, 722, 913, 333 };
        writer.enumeration().writeArray("/testEnum",
                new HDF5EnumerationValueArray(enumType, arrayWritten),
                HDF5IntStorageFeatures.INT_AUTO_SCALING);
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(file);
        final String[] stringArrayRead =
                reader.enumeration().readArray("/testEnum").toStringArray();
        assertEquals(arrayWritten.length, stringArrayRead.length);
        for (int i = 0; i < stringArrayRead.length; ++i)
        {
            assertEquals("Index " + i, enumType.getValues().get(arrayWritten[i]),
                    stringArrayRead[i]);
        }
        final HDF5EnumerationValueArray arrayRead = reader.enumeration().readArray("/testEnum");
        assertEquals(arrayWritten.length, arrayRead.getLength());
        for (int i = 0; i < arrayRead.getLength(); ++i)
        {
            assertEquals("Index " + i, enumType.getValues().get(arrayWritten[i]),
                    arrayRead.getValue(i));
        }
        reader.close();
    }

    @Test
    public void testEnumArray16BitFromIntArrayLarge()
    {
        final File file = new File(workingDirectory, "enumArray16BitFromIntArrayLarge.h5");
        final String enumTypeName = "testEnum";
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(file);
        final HDF5EnumerationType enumType = createEnum16Bit(writer, enumTypeName);
        final int[] arrayWritten = new int[100];
        for (int i = 0; i < arrayWritten.length; ++i)
        {
            arrayWritten[i] = 10 * i;
        }
        writer.enumeration().writeArray("/testEnum",
                new HDF5EnumerationValueArray(enumType, arrayWritten));
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(file);
        final String[] stringArrayRead =
                reader.enumeration().readArray("/testEnum").toStringArray();
        assertEquals(arrayWritten.length, stringArrayRead.length);
        for (int i = 0; i < stringArrayRead.length; ++i)
        {
            assertEquals("Index " + i, enumType.getValues().get(arrayWritten[i]),
                    stringArrayRead[i]);
        }
        reader.close();
    }

    @Test
    public void testEnumArrayBlock16Bit()
    {
        final File file = new File(workingDirectory, "enumArrayBlock16Bit.h5");
        final String enumTypeName = "testEnum";
        final int chunkSize = 4;
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(file);
        HDF5EnumerationType enumType = createEnum16Bit(writer, enumTypeName);
        writer.enumeration().createArray("/testEnum", enumType, chunkSize);
        final HDF5EnumerationValueArray arrayWritten =
                new HDF5EnumerationValueArray(enumType, new int[]
                    { 8, 16, 722, 913 });
        writer.enumeration().writeArrayBlock("/testEnum", arrayWritten, 1);
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(file);
        final HDF5EnumerationValueArray arrayReadBlock0 =
                reader.enumeration().readArrayBlock(enumTypeName, chunkSize, 0);
        enumType = reader.enumeration().getDataSetType(enumTypeName);
        final HDF5EnumerationValueArray arrayReadBlock1 =
                reader.enumeration().readArrayBlock(enumTypeName, enumType, chunkSize, 1);
        final String[] stringArrayRead =
                reader.enumeration().readArray(enumTypeName).toStringArray();
        assertEquals(arrayWritten.getLength() * 2, stringArrayRead.length);
        assertEquals(arrayWritten.getLength(), arrayReadBlock0.getLength());
        assertEquals(arrayWritten.getLength(), arrayReadBlock1.getLength());
        for (int i = 0; i < arrayReadBlock0.getLength(); ++i)
        {
            assertEquals("Index " + i, "0", arrayReadBlock0.getValue(i));
            assertEquals("Index " + i, "0", stringArrayRead[i]);
        }
        for (int i = 0; i < arrayReadBlock0.getLength(); ++i)
        {
            assertEquals("Index " + i, arrayWritten.getValue(i), arrayReadBlock1.getValue(i));
            assertEquals("Index " + i, arrayWritten.getValue(i), stringArrayRead[chunkSize + i]);
        }
        final HDF5EnumerationValueArray[] dataBlocksExpected = new HDF5EnumerationValueArray[]
            { arrayReadBlock0, arrayReadBlock1 };
        int blockIndex = 0;
        for (HDF5DataBlock<HDF5EnumerationValueArray> block : reader.enumeration().getArrayBlocks(
                enumTypeName, enumType))
        {
            final HDF5EnumerationValueArray blockExpected = dataBlocksExpected[blockIndex++];
            final HDF5EnumerationValueArray blockRead = block.getData();
            assertEquals(chunkSize, blockRead.getLength());
            for (int i = 0; i < blockExpected.getLength(); ++i)
            {
                assertEquals("Index " + i, blockExpected.getValue(i), blockRead.getValue(i));
            }
        }
        reader.close();
    }

    @Test
    public void testEnumArrayScaleCompression()
    {
        final File file = new File(workingDirectory, "enumArrayScaleCompression.h5");
        final String enumTypeName = "testEnum";
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(file);
        HDF5EnumerationType enumType = writer.enumeration().getType(enumTypeName, new String[]
            { "A", "C", "G", "T" }, false);
        final Random rng = new Random();
        final String[] arrayWrittenString = new String[100000];
        for (int i = 0; i < arrayWrittenString.length; ++i)
        {
            arrayWrittenString[i] = enumType.getValues().get(rng.nextInt(4));
        }
        final HDF5EnumerationValueArray arrayWritten =
                new HDF5EnumerationValueArray(enumType, arrayWrittenString);
        writer.enumeration().writeArray("/testEnum", arrayWritten, INT_AUTO_SCALING);
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(file);
        assertEquals(HDF5DataClass.ENUM, reader.object().getDataSetInformation("/testEnum")
                .getTypeInformation().getDataClass());
        final HDF5EnumerationValueArray arrayRead = reader.enumeration().readArray("/testEnum");
        enumType = reader.enumeration().getDataSetType("/testEnum");
        final HDF5EnumerationValueArray arrayRead2 =
                reader.enumeration().readArray("/testEnum", enumType);
        final String[] stringArrayRead =
                reader.enumeration().readArray("/testEnum").toStringArray();
        assertEquals(arrayWritten.getLength(), stringArrayRead.length);
        assertEquals(arrayWritten.getLength(), arrayRead.getLength());
        assertEquals(arrayWritten.getLength(), arrayRead2.getLength());
        for (int i = 0; i < stringArrayRead.length; ++i)
        {
            assertEquals("Index " + i, arrayWritten.getValue(i), arrayRead.getValue(i));
            assertEquals("Index " + i, arrayWritten.getValue(i), arrayRead2.getValue(i));
            assertEquals("Index " + i, arrayWritten.getValue(i), stringArrayRead[i]);
        }
        reader.close();
    }

    @Test
    public void testOpaqueType()
    {
        final File file = new File(workingDirectory, "opaqueType.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final String opaqueDataSetName = "/opaque/ds";
        final String byteArrayDataSetName = "/bytearr/ds";
        final String opaqueTag = "my opaque type";
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(file);
        final byte[] byteArrayWritten = new byte[]
            { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
        writer.int8().writeArray(byteArrayDataSetName, byteArrayWritten);
        writer.opaque().writeArray(opaqueDataSetName, opaqueTag, byteArrayWritten);
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(file);
        HDF5DataSetInformation info = reader.getDataSetInformation(byteArrayDataSetName);
        assertEquals(HDF5DataClass.INTEGER, info.getTypeInformation().getDataClass());
        assertChunkSizes(info, byteArrayWritten.length);
        info = reader.getDataSetInformation(opaqueDataSetName);
        assertEquals(HDF5DataClass.OPAQUE, info.getTypeInformation().getDataClass());
        assertEquals(opaqueTag, info.getTypeInformation().tryGetOpaqueTag());
        assertChunkSizes(info, byteArrayWritten.length);
        assertEquals(opaqueTag, reader.opaque().tryGetOpaqueTag(opaqueDataSetName));
        assertEquals(opaqueTag, reader.opaque().tryGetOpaqueType(opaqueDataSetName).getTag());
        assertNull(reader.opaque().tryGetOpaqueTag(byteArrayDataSetName));
        assertNull(reader.opaque().tryGetOpaqueType(byteArrayDataSetName));
        final byte[] byteArrayRead = reader.readAsByteArray(byteArrayDataSetName);
        assertTrue(Arrays.equals(byteArrayWritten, byteArrayRead));
        final byte[] byteArrayReadOpaque = reader.readAsByteArray(opaqueDataSetName);
        assertTrue(Arrays.equals(byteArrayWritten, byteArrayReadOpaque));
        reader.close();
    }

    private HDF5EnumerationType createEnum16Bit(final IHDF5Writer writer, final String enumTypeName)
    {
        final String[] enumValues = new String[1024];
        for (int i = 0; i < enumValues.length; ++i)
        {
            enumValues[i] = Integer.toString(i);
        }
        final HDF5EnumerationType enumType =
                writer.enumeration().getType(enumTypeName, enumValues, false);
        return enumType;
    }

    @Test
    public void testEnumArrayFromIntArray()
    {
        final File file = new File(workingDirectory, "enumArrayFromIntArray.h5");
        final String enumTypeName = "testEnum";
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(file);
        final HDF5EnumerationType enumType =
                writer.enumeration().getType(enumTypeName, new String[]
                    { "ONE", "TWO", "THREE" }, false);
        final int[] arrayWritten =
                new int[]
                    { enumType.tryGetIndexForValue("TWO").byteValue(),
                            enumType.tryGetIndexForValue("ONE").byteValue(),
                            enumType.tryGetIndexForValue("THREE").byteValue() };
        writer.enumeration().writeArray("/testEnum",
                new HDF5EnumerationValueArray(enumType, arrayWritten));
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(file);
        final String[] stringArrayRead =
                reader.enumeration().readArray("/testEnum").toStringArray();
        assertEquals(arrayWritten.length, stringArrayRead.length);
        for (int i = 0; i < stringArrayRead.length; ++i)
        {
            assertEquals("Index " + i, enumType.getValues().get(arrayWritten[i]),
                    stringArrayRead[i]);
        }
        reader.close();
    }

    static class Record
    {
        int a;

        float b;

        long l;

        double c;

        @CompoundElement(unsigned = true)
        short d;

        boolean e;

        String f;

        HDF5EnumerationValue g;

        int[] ar;

        float[] br;

        long[] lr;

        double[] cr;

        short[] dr;

        @CompoundElement(unsigned = true)
        byte[] er;

        MDIntArray fr;

        char[] gr;

        Record(int a, float b, long l, double c, short d, boolean e, String f,
                HDF5EnumerationValue g, int[] ar, float[] br, long[] lr, double[] cr, short[] dr,
                byte[] er, MDIntArray fr, char[] gr)
        {
            this.a = a;
            this.b = b;
            this.l = l;
            this.c = c;
            this.d = d;
            this.e = e;
            this.f = f;
            this.g = g;
            this.ar = ar;
            this.br = br;
            this.lr = lr;
            this.cr = cr;
            this.dr = dr;
            this.er = er;
            this.fr = fr;
            this.gr = gr;
        }

        Record()
        {
        }

        static HDF5CompoundMemberInformation[] getMemberInfo(HDF5EnumerationType enumType)
        {
            return HDF5CompoundMemberInformation.create(Record.class, "",
                    getShuffledMapping(enumType));
        }

        static HDF5CompoundType<Record> getHDF5Type(IHDF5Reader reader)
        {
            final HDF5EnumerationType enumType =
                    reader.enumeration().getType("someEnumType", new String[]
                        { "1", "Two", "THREE" });
            return reader.compound().getType(null, Record.class, getMapping(enumType));
        }

        private static HDF5CompoundMemberMapping[] getMapping(HDF5EnumerationType enumType)
        {
            return new HDF5CompoundMemberMapping[]
                { mapping("a"), mapping("b"), mapping("l"), mapping("c"), mapping("d").unsigned(),
                        mapping("e"), mapping("f").length(3), mapping("g").enumType(enumType),
                        mapping("ar").length(3), mapping("br").length(2), mapping("lr").length(3),
                        mapping("cr").length(1), mapping("dr").length(2),
                        mapping("er").length(4).unsigned(), mapping("fr").dimensions(2, 2),
                        mapping("gr").length(5) };
        }

        private static HDF5CompoundMemberMapping[] getShuffledMapping(HDF5EnumerationType enumType)
        {
            return new HDF5CompoundMemberMapping[]
                { mapping("er").length(4), mapping("e"), mapping("b"), mapping("br").length(2),
                        mapping("g").enumType(enumType), mapping("lr").length(3),
                        mapping("gr").length(5), mapping("c"), mapping("ar").length(3),
                        mapping("a"), mapping("d"), mapping("cr").length(1),
                        mapping("f").length(3), mapping("fr").dimensions(2, 2),
                        mapping("dr").length(2), mapping("l") };
        }

        //
        // Object
        //

        @Override
        public int hashCode()
        {
            final HashCodeBuilder builder = new HashCodeBuilder();
            builder.append(a);
            builder.append(b);
            builder.append(c);
            builder.append(d);
            builder.append(e);
            builder.append(f);
            builder.append(g);
            builder.append(ar);
            builder.append(br);
            builder.append(cr);
            builder.append(dr);
            builder.append(er);
            return builder.toHashCode();
        }

        @Override
        public boolean equals(Object obj)
        {
            if (obj == null || obj instanceof Record == false)
            {
                return false;
            }
            final Record that = (Record) obj;
            final EqualsBuilder builder = new EqualsBuilder();
            builder.append(a, that.a);
            builder.append(b, that.b);
            builder.append(c, that.c);
            builder.append(d, that.d);
            builder.append(e, that.e);
            builder.append(f, that.f);
            builder.append(g, that.g);
            builder.append(ar, that.ar);
            builder.append(br, that.br);
            builder.append(cr, that.cr);
            builder.append(dr, that.dr);
            builder.append(er, that.er);
            builder.append(fr, that.fr);
            return builder.isEquals();
        }

        @Override
        public String toString()
        {
            final ToStringBuilder builder = new ToStringBuilder(this);
            builder.append(a);
            builder.append(b);
            builder.append(c);
            builder.append(d);
            builder.append(e);
            builder.append(f);
            builder.append(g);
            builder.append(ar);
            builder.append(br);
            builder.append(cr);
            builder.append(dr);
            builder.append(er);
            builder.append(fr);
            return builder.toString();
        }

    }

    @Test
    public void testCompoundAttribute()
    {
        final File file = new File(workingDirectory, "compoundAttribute.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer writer = HDF5Factory.open(file);
        final SimpleInheretingRecord recordWritten =
                new SimpleInheretingRecord(3.14159f, 42, (short) 17, "xzy", new long[][]
                    {
                        { 1, 2, 3 },
                        { 4, 5, 6 } });
        writer.compound().setAttr("/", "cpd", recordWritten);
        final SimpleInheretingRecord[] recordArrWritten =
                new SimpleInheretingRecord[]
                    {
                            new SimpleInheretingRecord(3.14159f, 100, (short) 1, "abc",
                                    new long[][]
                                        {
                                            { 10, 20, 30 },
                                            { 40, 50, 60 } }),
                            new SimpleInheretingRecord(3.14159f, 1000, (short) 2, "def",
                                    new long[][]
                                        {
                                            { 70, 80, 90 },
                                            { 100, 110, 120 } }), };
        writer.compound().setArrayAttr("/", "cpdArray", recordArrWritten);
        writer.compound().setArrayAttr("/", "cpdArray", recordArrWritten);
        final MDArray<SimpleInheretingRecord> recordMDArrWritten =
                new MDArray<SimpleInheretingRecord>(new SimpleInheretingRecord[]
                    {
                            new SimpleInheretingRecord(3.14159f, 100, (short) 1, "abc",
                                    new long[][]
                                        {
                                            { 10, 20, 30 },
                                            { 40, 50, 60 } }),
                            new SimpleInheretingRecord(3.14159f, 1000, (short) 2, "def",
                                    new long[][]
                                        {
                                            { 70, 80, 90 },
                                            { 100, 110, 120 } }),
                            new SimpleInheretingRecord(-1f, 10000, (short) 1, "ghi", new long[][]
                                {
                                    { 10, 20, 30 },
                                    { 40, 50, 60 } }),
                            new SimpleInheretingRecord(11.111111f, 100000, (short) 2, "jkl",
                                    new long[][]
                                        {
                                            { 70, 80, 90 },
                                            { 100, 110, 120 } }), }, new int[]
                    { 2, 2 });
        writer.compound().setMDArrayAttr("/", "cpdMDArray", recordMDArrWritten);
        writer.close();

        final IHDF5Reader reader = HDF5Factory.openForReading(file);
        final HDF5CompoundType<SimpleInheretingRecord> type =
                reader.compound().getAttributeType("/", "cpd", SimpleInheretingRecord.class);
        assertFalse(type.isMappingIncomplete());
        assertFalse(type.isDiskRepresentationIncomplete());
        assertFalse(type.isMemoryRepresentationIncomplete());
        type.checkMappingComplete();
        final SimpleInheretingRecord recordRead =
                reader.compound().getAttr("/", "cpd", SimpleInheretingRecord.class);
        assertEquals(recordWritten, recordRead);
        final SimpleInheretingRecord[] recordArrRead =
                reader.compound().getArrayAttr("/", "cpdArray", SimpleInheretingRecord.class);
        assertTrue(Arrays.equals(recordArrWritten, recordArrRead));
        final MDArray<SimpleInheretingRecord> recordMDArrRead =
                reader.compound().getMDArrayAttr("/", "cpdMDArray", SimpleInheretingRecord.class);
        assertEquals(recordMDArrWritten, recordMDArrRead);
        reader.close();
    }

    static class RecordRequiringMemAlignment
    {
        byte b1;

        short s;

        byte b2;

        int i;

        byte b3;

        long l;

        public RecordRequiringMemAlignment()
        {
        }

        RecordRequiringMemAlignment(byte b1, short s, byte b2, int i, byte b3, long l)
        {
            super();
            this.b1 = b1;
            this.s = s;
            this.b2 = b2;
            this.i = i;
            this.b3 = b3;
            this.l = l;
        }

        @Override
        public int hashCode()
        {
            final int prime = 31;
            int result = 1;
            result = prime * result + b1;
            result = prime * result + b2;
            result = prime * result + b3;
            result = prime * result + i;
            result = prime * result + (int) (l ^ (l >>> 32));
            result = prime * result + s;
            return result;
        }

        @Override
        public boolean equals(Object obj)
        {
            if (this == obj)
            {
                return true;
            }
            if (obj == null)
            {
                return false;
            }
            if (getClass() != obj.getClass())
            {
                return false;
            }
            RecordRequiringMemAlignment other = (RecordRequiringMemAlignment) obj;
            if (b1 != other.b1)
            {
                return false;
            }
            if (b2 != other.b2)
            {
                return false;
            }
            if (b3 != other.b3)
            {
                return false;
            }
            if (i != other.i)
            {
                return false;
            }
            if (l != other.l)
            {
                return false;
            }
            if (s != other.s)
            {
                return false;
            }
            return true;
        }

        @Override
        public String toString()
        {
            return "RecordRequringMemAlignment [b1=" + b1 + ", s=" + s + ", b2=" + b2 + ", i=" + i
                    + ", b3=" + b3 + ", l=" + l + "]";
        }

    }

    @Test
    public void testCompoundAttributeMemoryAlignment()
    {
        final File file = new File(workingDirectory, "compoundAttributeMemoryAlignment.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer writer = HDF5Factory.open(file);
        final RecordRequiringMemAlignment recordWritten =
                new RecordRequiringMemAlignment((byte) 1, (short) 2, (byte) 3, 4, (byte) 5, 6L);
        writer.int32().write("val", 0);
        writer.compound().setAttr("val", "attr0d", recordWritten);
        final RecordRequiringMemAlignment[] recordArrayWritten =
                new RecordRequiringMemAlignment[]
                    {
                            new RecordRequiringMemAlignment((byte) 7, (short) 8, (byte) 9, 10,
                                    (byte) 11, 12L),
                            new RecordRequiringMemAlignment((byte) 13, (short) 14, (byte) 15, 16,
                                    (byte) 17, 18L) };
        writer.compound().setArrayAttr("val", "attr1d", recordArrayWritten);
        final MDArray<RecordRequiringMemAlignment> recordMDArrayWritten =
                new MDArray<RecordRequiringMemAlignment>(new RecordRequiringMemAlignment[]
                    {
                            new RecordRequiringMemAlignment((byte) 19, (short) 20, (byte) 21, 22,
                                    (byte) 23, 24L),
                            new RecordRequiringMemAlignment((byte) 25, (short) 26, (byte) 27, 28,
                                    (byte) 29, 30L),
                            new RecordRequiringMemAlignment((byte) 31, (short) 32, (byte) 33, 34,
                                    (byte) 35, 36L),
                            new RecordRequiringMemAlignment((byte) 37, (short) 38, (byte) 39, 40,
                                    (byte) 41, 42L), }, new long[]
                    { 2, 2 });
        writer.compound().setMDArrayAttr("val", "attr2d", recordMDArrayWritten);
        writer.close();

        final IHDF5Reader reader = HDF5Factory.openForReading(file);
        final HDF5CompoundType<RecordRequiringMemAlignment> type =
                reader.compound().getInferredType(RecordRequiringMemAlignment.class);
        final HDF5CompoundMemberInformation[] infos = type.getCompoundMemberInformation();
        for (int i = 0; i < infos.length; ++i)
        {
            assertEquals(infos[i].getName() + "(" + i + ")", type.getObjectByteifyer()
                    .getByteifyers()[i].getOffsetOnDisk(), infos[i].getOffsetOnDisk());
            assertEquals(infos[i].getName() + "(" + i + ")", type.getObjectByteifyer()
                    .getByteifyers()[i].getOffsetInMemory(), infos[i].getOffsetInMemory());
            assertEquals(infos[i].getName() + "(" + i + ")", type.getObjectByteifyer()
                    .getByteifyers()[i].getSize(), infos[i].getType().getElementSize());
        }
        final RecordRequiringMemAlignment recordRead =
                reader.compound().getAttr("val", "attr0d", RecordRequiringMemAlignment.class);
        assertEquals(recordWritten, recordRead);
        final RecordRequiringMemAlignment[] recordArrayRead =
                reader.compound().getArrayAttr("val", "attr1d", RecordRequiringMemAlignment.class);
        assertTrue(Arrays.equals(recordArrayWritten, recordArrayRead));
        final MDArray<RecordRequiringMemAlignment> recordMDArrayRead =
                reader.compound()
                        .getMDArrayAttr("val", "attr2d", RecordRequiringMemAlignment.class);
        assertTrue(recordMDArrayWritten.equals(recordMDArrayRead));
        reader.close();
    }

    @Test
    public void testCompound()
    {
        final File file = new File(workingDirectory, "compound.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer writer = HDF5Factory.open(file);
        final SimpleInheretingRecord recordWritten =
                new SimpleInheretingRecord(3.14159f, 42, (short) 17, "xzy", new long[][]
                    {
                        { 1, 2, 3 },
                        { 4, 5, 6 } });
        writer.compound().write("cpd", recordWritten);
        final SimpleInheretingRecord2 recordWritten2 =
                new SimpleInheretingRecord2(3.14159f, 42, (short) 17, "xzy", new long[][]
                    {
                        { 1, 2, 3 },
                        { 4, 5, 6 },
                        { 7, 8, 9 } });
        writer.compound().write("cpd2", recordWritten2);
        final SimpleInheretingRecord3 recordWritten3 =
                new SimpleInheretingRecord3(3.14159f, 42, (short) 17, "xzy", new long[][]
                    {
                        { 1, 2, 3 },
                        { 4, 5, 6 },
                        { 7, 8, 11 } });
        writer.compound().write("cpd3", recordWritten3);

        final File file2 = new File(workingDirectory, "compound2.h5");
        file2.delete();
        assertFalse(file2.exists());
        file2.deleteOnExit();
        final IHDF5Writer writer2 = HDF5Factory.open(file2);
        final HDF5CompoundType<HDF5CompoundDataMap> clonedType =
                writer2.compound().getClonedType(
                        writer.compound().getDataSetType("cpd", HDF5CompoundDataMap.class));
        writer2.compound().write("cpd", clonedType,
                writer.compound().read("cpd", HDF5CompoundDataMap.class));

        writer.close();
        writer2.close();

        final IHDF5Reader reader = HDF5Factory.openForReading(file);
        final HDF5CompoundType<SimpleInheretingRecord> type =
                reader.compound().getDataSetType("cpd", SimpleInheretingRecord.class);
        assertFalse(type.isMappingIncomplete());
        assertFalse(type.isDiskRepresentationIncomplete());
        assertFalse(type.isMemoryRepresentationIncomplete());
        type.checkMappingComplete();
        final SimpleInheretingRecord recordRead = reader.compound().read("cpd", type);
        assertEquals(recordWritten, recordRead);
        final SimpleInheretingRecord2 recordRead2 =
                reader.compound().read("cpd2", SimpleInheretingRecord2.class);
        assertEquals(recordWritten2, recordRead2);
        final SimpleInheretingRecord3 recordRead3 =
                reader.compound().read("cpd3", SimpleInheretingRecord3.class);
        assertEquals(recordWritten3, recordRead3);
        HDF5CompoundMemberInformation[] infos = type.getCompoundMemberInformation();
        for (int i = 0; i < infos.length; ++i)
        {
            assertEquals("" + i, type.getObjectByteifyer().getByteifyers()[i].getOffsetOnDisk(),
                    infos[i].getOffsetOnDisk());
            assertEquals("" + i, type.getObjectByteifyer().getByteifyers()[i].getOffsetInMemory(),
                    infos[i].getOffsetInMemory());
        }
        reader.close();

        final IHDF5Reader reader2 = HDF5Factory.openForReading(file2);
        final HDF5CompoundType<SimpleInheretingRecord> type2 =
                reader2.compound().getDataSetType("cpd", SimpleInheretingRecord.class);
        assertFalse(type2.isMappingIncomplete());
        assertFalse(type2.isDiskRepresentationIncomplete());
        assertFalse(type2.isMemoryRepresentationIncomplete());
        assertEquals("SimpleInheretingRecord", type2.getName());
        type2.checkMappingComplete();
        final SimpleInheretingRecord recordReadFile2 = reader2.compound().read("cpd", type2);
        assertEquals(recordWritten, recordReadFile2);
        reader2.close();
    }

    static class SimpleStringRecord
    {
        String s1;

        String s2;

        SimpleStringRecord()
        {
        }

        SimpleStringRecord(String s, String s2)
        {
            this.s1 = s;
            this.s2 = s2;
        }

        @Override
        public int hashCode()
        {
            final int prime = 31;
            int result = 1;
            result = prime * result + ((s1 == null) ? 0 : s1.hashCode());
            result = prime * result + ((s2 == null) ? 0 : s2.hashCode());
            return result;
        }

        @Override
        public boolean equals(Object obj)
        {
            if (this == obj)
            {
                return true;
            }
            if (obj == null)
            {
                return false;
            }
            if (getClass() != obj.getClass())
            {
                return false;
            }
            SimpleStringRecord other = (SimpleStringRecord) obj;
            if (s1 == null)
            {
                if (other.s1 != null)
                {
                    return false;
                }
            } else if (!s1.equals(other.s1))
            {
                return false;
            }
            if (s2 == null)
            {
                if (other.s2 != null)
                {
                    return false;
                }
            } else if (!s2.equals(other.s2))
            {
                return false;
            }
            return true;
        }
    }

    @Test
    public void testCompoundInferStringLength()
    {
        final File file = new File(workingDirectory, "stringsInCompound.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer writer = HDF5Factory.open(file);
        final SimpleStringRecord recordWritten = new SimpleStringRecord("hello", "X");
        writer.compound().write("strings", recordWritten);
        final SimpleStringRecord[] recordArrayWritten = new SimpleStringRecord[]
            { new SimpleStringRecord("hello", "X"), new SimpleStringRecord("Y2", "0123456789") };
        writer.compound().writeArray("stringsArray", recordArrayWritten);
        writer.close();

        final IHDF5Reader reader = HDF5Factory.openForReading(file);
        final HDF5CompoundType<SimpleStringRecord> type =
                reader.compound().getInferredType(recordWritten);
        assertEquals(2, type.getCompoundMemberInformation().length);
        assertEquals("s1", type.getCompoundMemberInformation()[0].getName());
        assertEquals(HDF5DataClass.STRING, type.getCompoundMemberInformation()[0].getType()
                .getDataClass());
        assertEquals(recordWritten.s1.length(), type.getCompoundMemberInformation()[0].getType()
                .getSize());
        assertEquals("s2", type.getCompoundMemberInformation()[1].getName());
        assertEquals(HDF5DataClass.STRING, type.getCompoundMemberInformation()[1].getType()
                .getDataClass());
        assertEquals(recordWritten.s2.length(), type.getCompoundMemberInformation()[1].getType()
                .getSize());
        final SimpleStringRecord recordRead = reader.compound().read("strings", type);
        assertEquals(recordWritten, recordRead);

        final HDF5CompoundType<SimpleStringRecord> arrayType =
                reader.compound().getInferredType(recordArrayWritten);
        assertEquals("s1", arrayType.getCompoundMemberInformation()[0].getName());
        assertEquals(HDF5DataClass.STRING, arrayType.getCompoundMemberInformation()[0].getType()
                .getDataClass());
        assertEquals(recordArrayWritten[0].s1.length(), arrayType.getCompoundMemberInformation()[0]
                .getType().getSize());
        assertEquals("s2", arrayType.getCompoundMemberInformation()[1].getName());
        assertEquals(HDF5DataClass.STRING, arrayType.getCompoundMemberInformation()[1].getType()
                .getDataClass());
        assertEquals(recordArrayWritten[1].s2.length(), arrayType.getCompoundMemberInformation()[1]
                .getType().getSize());
        final SimpleStringRecord[] recordArrayRead =
                reader.compound().readArray("stringsArray", SimpleStringRecord.class);
        assertEquals(2, recordArrayRead.length);
        assertEquals(recordArrayWritten[0], recordArrayRead[0]);
        assertEquals(recordArrayWritten[1], recordArrayRead[1]);

        reader.close();

    }

    static class SimpleRecordWithStringsAndIntsAnnoted
    {
        @CompoundElement(variableLength = true)
        String s1;

        int i1;

        @CompoundElement(variableLength = true)
        String s2;

        int i2;

        SimpleRecordWithStringsAndIntsAnnoted()
        {
        }

        SimpleRecordWithStringsAndIntsAnnoted(String s1, int i1, String s2, int i2)
        {
            this.s1 = s1;
            this.i1 = i1;
            this.s2 = s2;
            this.i2 = i2;
        }

        @Override
        public int hashCode()
        {
            final int prime = 31;
            int result = 1;
            result = prime * result + i1;
            result = prime * result + i2;
            result = prime * result + ((s1 == null) ? 0 : s1.hashCode());
            result = prime * result + ((s2 == null) ? 0 : s2.hashCode());
            return result;
        }

        @Override
        public boolean equals(Object obj)
        {
            if (this == obj)
            {
                return true;
            }
            if (obj == null)
            {
                return false;
            }
            if (getClass() != obj.getClass())
            {
                return false;
            }
            SimpleRecordWithStringsAndIntsAnnoted other =
                    (SimpleRecordWithStringsAndIntsAnnoted) obj;
            if (i1 != other.i1)
            {
                return false;
            }
            if (i2 != other.i2)
            {
                return false;
            }
            if (s1 == null)
            {
                if (other.s1 != null)
                {
                    return false;
                }
            } else if (!s1.equals(other.s1))
            {
                return false;
            }
            if (s2 == null)
            {
                if (other.s2 != null)
                {
                    return false;
                }
            } else if (!s2.equals(other.s2))
            {
                return false;
            }
            return true;
        }

        @Override
        public String toString()
        {
            return "SimpleRecordWithStringsAndIntsAnnotated [s1=" + s1 + ", i1=" + i1 + ", s2="
                    + s2 + ", i2=" + i2 + "]";
        }

    }

    static class SimpleRecordWithStringsAndInts
    {
        String s1;

        int i1;

        @CompoundElement(dimensions =
            { 10 })
        String s2;

        int i2;

        SimpleRecordWithStringsAndInts()
        {
        }

        SimpleRecordWithStringsAndInts(String s1, int i1, String s2, int i2)
        {
            this.s1 = s1;
            this.i1 = i1;
            this.s2 = s2;
            this.i2 = i2;
        }

        @Override
        public int hashCode()
        {
            final int prime = 31;
            int result = 1;
            result = prime * result + i1;
            result = prime * result + i2;
            result = prime * result + ((s1 == null) ? 0 : s1.hashCode());
            result = prime * result + ((s2 == null) ? 0 : s2.hashCode());
            return result;
        }

        @Override
        public boolean equals(Object obj)
        {
            if (this == obj)
            {
                return true;
            }
            if (obj == null)
            {
                return false;
            }
            if (getClass() != obj.getClass())
            {
                return false;
            }
            SimpleRecordWithStringsAndInts other = (SimpleRecordWithStringsAndInts) obj;
            if (i1 != other.i1)
            {
                return false;
            }
            if (i2 != other.i2)
            {
                return false;
            }
            if (s1 == null)
            {
                if (other.s1 != null)
                {
                    return false;
                }
            } else if (!s1.equals(other.s1))
            {
                return false;
            }
            if (s2 == null)
            {
                if (other.s2 != null)
                {
                    return false;
                }
            } else if (!s2.equals(other.s2))
            {
                return false;
            }
            return true;
        }

        @Override
        public String toString()
        {
            return "SimpleRecordWithStringsAndInts [s1=" + s1 + ", i1=" + i1 + ", s2=" + s2
                    + ", i2=" + i2 + "]";
        }

    }

    @Test
    public void testCompoundVariableLengthString()
    {
        final File file = new File(workingDirectory, "variableLengthStringsInCompound.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer writer = HDF5Factory.open(file);
        final HDF5CompoundType<SimpleRecordWithStringsAndIntsAnnoted> typeWritten =
                writer.compound().getInferredType(SimpleRecordWithStringsAndIntsAnnoted.class);
        final SimpleRecordWithStringsAndIntsAnnoted recordWritten =
                new SimpleRecordWithStringsAndIntsAnnoted("hello", 17, "world", 1);
        writer.compound().write("stringAntInt", typeWritten, recordWritten);
        final SimpleRecordWithStringsAndIntsAnnoted[] recordArrayWritten =
                new SimpleRecordWithStringsAndIntsAnnoted[]
                    { new SimpleRecordWithStringsAndIntsAnnoted("hello", 3, "0123456789", 100000),
                            new SimpleRecordWithStringsAndIntsAnnoted("Y2", -1, "What?", -100000) };
        writer.compound().writeArray("stringsArray", typeWritten, recordArrayWritten);
        writer.close();

        final IHDF5Reader reader = HDF5Factory.openForReading(file);
        final HDF5CompoundType<SimpleRecordWithStringsAndIntsAnnoted> typeRead =
                reader.compound().getDataSetType("stringAntInt",
                        SimpleRecordWithStringsAndIntsAnnoted.class);
        assertEquals(4, typeRead.getCompoundMemberInformation().length);
        assertEquals("s1", typeRead.getCompoundMemberInformation()[0].getName());
        assertEquals(HDF5DataClass.STRING, typeRead.getCompoundMemberInformation()[0].getType()
                .getDataClass());
        assertTrue(typeRead.getCompoundMemberInformation()[0].getType().isVariableLengthString());
        assertEquals(HDFHelper.getMachineWordSize(), typeRead.getCompoundMemberInformation()[0]
                .getType().getSize());
        assertEquals("i1", typeRead.getCompoundMemberInformation()[1].getName());
        assertEquals(HDF5DataClass.INTEGER, typeRead.getCompoundMemberInformation()[1].getType()
                .getDataClass());
        assertEquals("s2", typeRead.getCompoundMemberInformation()[2].getName());
        assertEquals(HDF5DataClass.STRING, typeRead.getCompoundMemberInformation()[2].getType()
                .getDataClass());
        assertTrue(typeRead.getCompoundMemberInformation()[2].getType().isVariableLengthString());
        assertEquals(HDFHelper.getMachineWordSize(), typeRead.getCompoundMemberInformation()[2]
                .getType().getSize());
        assertEquals("i2", typeRead.getCompoundMemberInformation()[3].getName());
        assertEquals(HDF5DataClass.INTEGER, typeRead.getCompoundMemberInformation()[3].getType()
                .getDataClass());
        assertFalse(typeRead.getCompoundMemberInformation()[3].getType().isVariableLengthString());
        final SimpleRecordWithStringsAndIntsAnnoted recordRead =
                reader.compound().read("stringAntInt", typeRead);
        assertEquals(recordWritten, recordRead);

        final HDF5CompoundType<SimpleRecordWithStringsAndIntsAnnoted> arrayTypeRead =
                reader.compound().getDataSetType("stringsArray",
                        SimpleRecordWithStringsAndIntsAnnoted.class);
        assertEquals(4, arrayTypeRead.getCompoundMemberInformation().length);
        assertEquals("s1", arrayTypeRead.getCompoundMemberInformation()[0].getName());
        assertEquals(HDF5DataClass.STRING, arrayTypeRead.getCompoundMemberInformation()[0]
                .getType().getDataClass());
        assertTrue(arrayTypeRead.getCompoundMemberInformation()[0].getType()
                .isVariableLengthString());
        assertEquals(HDFHelper.getMachineWordSize(),
                arrayTypeRead.getCompoundMemberInformation()[0].getType().getSize());
        assertEquals("i1", arrayTypeRead.getCompoundMemberInformation()[1].getName());
        assertEquals(HDF5DataClass.INTEGER, arrayTypeRead.getCompoundMemberInformation()[1]
                .getType().getDataClass());
        assertEquals("s2", arrayTypeRead.getCompoundMemberInformation()[2].getName());
        assertEquals(HDF5DataClass.STRING, arrayTypeRead.getCompoundMemberInformation()[2]
                .getType().getDataClass());
        assertTrue(arrayTypeRead.getCompoundMemberInformation()[2].getType()
                .isVariableLengthString());
        assertEquals(HDFHelper.getMachineWordSize(),
                arrayTypeRead.getCompoundMemberInformation()[2].getType().getSize());
        assertEquals("i2", arrayTypeRead.getCompoundMemberInformation()[3].getName());
        assertEquals(HDF5DataClass.INTEGER, arrayTypeRead.getCompoundMemberInformation()[3]
                .getType().getDataClass());
        assertFalse(arrayTypeRead.getCompoundMemberInformation()[3].getType()
                .isVariableLengthString());
        final SimpleRecordWithStringsAndIntsAnnoted[] recordArrayRead =
                reader.compound().readArray("stringsArray", arrayTypeRead);
        assertEquals(recordArrayWritten.length, recordArrayRead.length);
        assertEquals(recordArrayWritten[0], recordArrayRead[0]);
        assertEquals(recordArrayWritten[1], recordArrayRead[1]);

        reader.close();
    }

    @Test
    public void testCompoundVariableLengthStringUsingHints()
    {
        final File file =
                new File(workingDirectory, "variableLengthStringsInCompoundUsingHints.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer writer = HDF5Factory.open(file);
        final HDF5CompoundType<SimpleRecordWithStringsAndInts> typeWritten =
                writer.compound().getInferredType(SimpleRecordWithStringsAndInts.class,
                        new HDF5CompoundMappingHints().useVariableLengthStrings());
        final SimpleRecordWithStringsAndInts recordWritten =
                new SimpleRecordWithStringsAndInts("hello", 17, "world", 1);
        writer.compound().write("stringAntInt", typeWritten, recordWritten);
        final SimpleRecordWithStringsAndInts[] recordArrayWritten =
                new SimpleRecordWithStringsAndInts[]
                    { new SimpleRecordWithStringsAndInts("hello", 3, "0123456789", 100000),
                            new SimpleRecordWithStringsAndInts("Y2", -1, "What?", -100000) };
        writer.compound().writeArray("stringsArray", typeWritten, recordArrayWritten);
        writer.close();

        final IHDF5Reader reader = HDF5Factory.openForReading(file);
        final HDF5CompoundType<SimpleRecordWithStringsAndInts> typeRead =
                reader.compound().getDataSetType("stringAntInt",
                        SimpleRecordWithStringsAndInts.class);
        assertEquals(4, typeRead.getCompoundMemberInformation().length);
        assertEquals("s1", typeRead.getCompoundMemberInformation()[0].getName());
        assertEquals(HDF5DataClass.STRING, typeRead.getCompoundMemberInformation()[0].getType()
                .getDataClass());
        assertTrue(typeRead.getCompoundMemberInformation()[0].getType().isVariableLengthString());
        assertEquals(HDFHelper.getMachineWordSize(), typeRead.getCompoundMemberInformation()[0]
                .getType().getSize());
        assertEquals("i1", typeRead.getCompoundMemberInformation()[1].getName());
        assertEquals(HDF5DataClass.INTEGER, typeRead.getCompoundMemberInformation()[1].getType()
                .getDataClass());
        assertEquals("s2", typeRead.getCompoundMemberInformation()[2].getName());
        assertEquals(HDF5DataClass.STRING, typeRead.getCompoundMemberInformation()[2].getType()
                .getDataClass());
        assertFalse(typeRead.getCompoundMemberInformation()[2].getType().isVariableLengthString());
        assertEquals(10, typeRead.getCompoundMemberInformation()[2].getType().getElementSize());
        assertEquals(10, typeRead.getCompoundMemberInformation()[2].getType().getSize());
        assertEquals("i2", typeRead.getCompoundMemberInformation()[3].getName());
        assertEquals(HDF5DataClass.INTEGER, typeRead.getCompoundMemberInformation()[3].getType()
                .getDataClass());
        assertFalse(typeRead.getCompoundMemberInformation()[3].getType().isVariableLengthString());
        final SimpleRecordWithStringsAndInts recordRead =
                reader.compound().read("stringAntInt", typeRead);
        assertEquals(recordWritten, recordRead);

        final HDF5CompoundType<SimpleRecordWithStringsAndInts> arrayTypeRead =
                reader.compound().getDataSetType("stringsArray",
                        SimpleRecordWithStringsAndInts.class);
        assertEquals(4, arrayTypeRead.getCompoundMemberInformation().length);
        assertEquals("s1", arrayTypeRead.getCompoundMemberInformation()[0].getName());
        assertEquals(HDF5DataClass.STRING, arrayTypeRead.getCompoundMemberInformation()[0]
                .getType().getDataClass());
        assertTrue(arrayTypeRead.getCompoundMemberInformation()[0].getType()
                .isVariableLengthString());
        assertEquals(HDFHelper.getMachineWordSize(),
                arrayTypeRead.getCompoundMemberInformation()[0].getType().getSize());
        assertEquals("i1", arrayTypeRead.getCompoundMemberInformation()[1].getName());
        assertEquals(HDF5DataClass.INTEGER, arrayTypeRead.getCompoundMemberInformation()[1]
                .getType().getDataClass());
        assertEquals("s2", arrayTypeRead.getCompoundMemberInformation()[2].getName());
        assertEquals(HDF5DataClass.STRING, arrayTypeRead.getCompoundMemberInformation()[2]
                .getType().getDataClass());
        assertFalse(arrayTypeRead.getCompoundMemberInformation()[2].getType()
                .isVariableLengthString());
        assertEquals(10, arrayTypeRead.getCompoundMemberInformation()[2].getType().getElementSize());
        assertEquals(10, arrayTypeRead.getCompoundMemberInformation()[2].getType().getSize());
        assertEquals("i2", arrayTypeRead.getCompoundMemberInformation()[3].getName());
        assertEquals(HDF5DataClass.INTEGER, arrayTypeRead.getCompoundMemberInformation()[3]
                .getType().getDataClass());
        assertFalse(arrayTypeRead.getCompoundMemberInformation()[3].getType()
                .isVariableLengthString());
        final SimpleRecordWithStringsAndInts[] recordArrayRead =
                reader.compound().readArray("stringsArray", arrayTypeRead);
        assertEquals(recordArrayWritten.length, recordArrayRead.length);
        assertEquals(recordArrayWritten[0], recordArrayRead[0]);
        assertEquals(recordArrayWritten[1], recordArrayRead[1]);

        reader.close();
    }

    static class SimpleRecordWithReference
    {
        @CompoundElement(reference = true)
        String ref;

        SimpleRecordWithReference()
        {
        }

        SimpleRecordWithReference(String ref)
        {
            this.ref = ref;
        }

        @Override
        public int hashCode()
        {
            final int prime = 31;
            int result = 1;
            result = prime * result + ((ref == null) ? 0 : ref.hashCode());
            return result;
        }

        @Override
        public boolean equals(Object obj)
        {
            if (this == obj)
            {
                return true;
            }
            if (obj == null)
            {
                return false;
            }
            if (getClass() != obj.getClass())
            {
                return false;
            }
            SimpleRecordWithReference other = (SimpleRecordWithReference) obj;
            if (ref == null)
            {
                if (other.ref != null)
                {
                    return false;
                }
            } else if (!ref.equals(other.ref))
            {
                return false;
            }
            return true;
        }

        @Override
        public String toString()
        {
            return "SimpleRecordWithReference [ref=" + ref + "]";
        }
    }

    @Test
    public void testCompoundReference()
    {
        final File file = new File(workingDirectory, "compoundReference.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer writer = HDF5Factory.open(file);
        writer.int32().write("a", 17);
        writer.float64().write("b", 0.001);
        writer.compound().write("cpd1", new SimpleRecordWithReference("a"));
        writer.compound().write("cpd2", new SimpleRecordWithReference("b"));
        writer.close();

        final IHDF5Reader reader = HDF5Factory.openForReading(file);
        final HDF5CompoundMemberInformation[] infoFromJavaObjs =
                HDF5CompoundMemberInformation.create(SimpleRecordWithReference.class, "",
                        HDF5CompoundMemberMapping.inferMapping(SimpleRecordWithReference.class));
        assertEquals(1, infoFromJavaObjs.length);
        assertEquals("ref:REFERENCE(8)", infoFromJavaObjs[0].toString());
        final HDF5CompoundMemberInformation[] infoFromHDF5Objs =
                reader.compound().getDataSetInfo("cpd1");
        assertEquals(1, infoFromHDF5Objs.length);
        assertEquals("ref:REFERENCE(8)", infoFromHDF5Objs[0].toString());
        final SimpleRecordWithReference recordRead1 =
                reader.compound().read("cpd1", SimpleRecordWithReference.class);
        assertEquals("/a", reader.reference().resolvePath(recordRead1.ref));
        assertEquals("INTEGER(4):{}", reader.object().getDataSetInformation(recordRead1.ref)
                .toString());
        assertEquals(17, reader.int32().read(recordRead1.ref));

        final HDF5CompoundMemberInformation[] info2 = reader.compound().getDataSetInfo("cpd2");
        assertEquals(1, info2.length);
        assertEquals("ref:REFERENCE(8)", info2[0].toString());
        final SimpleRecordWithReference recordRead2 =
                reader.compound().read("cpd2", SimpleRecordWithReference.class);
        assertEquals("/b", reader.reference().resolvePath(recordRead2.ref));
        assertEquals("FLOAT(8):{}", reader.object().getDataSetInformation(recordRead2.ref)
                .toString());
        assertEquals(0.001, reader.float64().read(recordRead2.ref));
        reader.close();
    }

    @Test
    public void testClosedCompoundType()
    {
        final File file = new File(workingDirectory, "closedCompoundType.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer writer = HDF5Factory.open(file);
        final SimpleInheretingRecord recordWritten =
                new SimpleInheretingRecord(3.14159f, 42, (short) 17, "xzy", new long[][]
                    {
                        { 1, 2, 3 },
                        { 4, 5, 6 } });
        final HDF5CompoundType<SimpleInheretingRecord> type =
                writer.compound().getInferredType(SimpleInheretingRecord.class);
        writer.compound().write("cpd", recordWritten);
        writer.close();

        final File file2 = new File(workingDirectory, "closedCompoundType2.h5");
        file2.delete();
        assertFalse(file2.exists());
        file2.deleteOnExit();
        final IHDF5Writer writer2 = HDF5Factory.open(file2);
        try
        {
            writer2.compound().write("cpd", type, recordWritten);
            fail("Failed to detect closed type.");
        } catch (HDF5JavaException ex)
        {
            assertEquals("Type SimpleInheretingRecord is closed.", ex.getMessage());
        }
        try
        {
            writer2.compound().getClonedType(type);
            fail("Failed to detect closed type.");
        } catch (HDF5JavaException ex)
        {
            assertEquals("Type SimpleInheretingRecord is closed.", ex.getMessage());
        }
        writer2.close();
    }

    @Test
    public void testAnonCompound()
    {
        final File file = new File(workingDirectory, "anonCompound.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer writer = HDF5Factory.open(file);
        final HDF5CompoundType<SimpleInheretingRecord> anonType1 =
                writer.compound().getInferredAnonType(SimpleInheretingRecord.class);
        assertEquals("UNKNOWN", anonType1.getName());
        final SimpleInheretingRecord recordWritten =
                new SimpleInheretingRecord(3.14159f, 42, (short) 17, "xzy", new long[][]
                    {
                        { 1, 2, 3 },
                        { 4, 5, 6 } });
        writer.compound().write("cpd", anonType1, recordWritten);
        final SimpleInheretingRecord2 recordWritten2 =
                new SimpleInheretingRecord2(3.14159f, 42, (short) 17, "xzy", new long[][]
                    {
                        { 1, 2, 3 },
                        { 4, 5, 6 },
                        { 7, 8, 9 } });
        final HDF5CompoundType<SimpleInheretingRecord2> anonType2 =
                writer.compound().getInferredAnonType(recordWritten2);
        assertEquals("UNKNOWN", anonType2.getName());
        writer.compound().write("cpd2", anonType2, recordWritten2);
        final SimpleInheretingRecord3 recordWritten3 =
                new SimpleInheretingRecord3(3.14159f, 42, (short) 17, "xzy", new long[][]
                    {
                        { 1, 2, 3 },
                        { 4, 5, 6 },
                        { 7, 8, 11 } });
        final HDF5CompoundType<SimpleInheretingRecord3> anonType3 =
                writer.compound().getInferredAnonType(recordWritten3);
        assertEquals("UNKNOWN", anonType3.getName());
        writer.compound().write("cpd3", anonType3, recordWritten3);

        final File file2 = new File(workingDirectory, "anonCompound2.h5");
        file2.delete();
        assertFalse(file2.exists());
        file2.deleteOnExit();
        final IHDF5Writer writer2 = HDF5Factory.open(file2);
        final HDF5CompoundType<HDF5CompoundDataMap> clonedType =
                writer2.compound().getClonedType(
                        writer.compound().getDataSetType("cpd", HDF5CompoundDataMap.class));
        writer2.compound().write("cpd", clonedType,
                writer.compound().read("cpd", HDF5CompoundDataMap.class));

        writer.close();
        writer2.close();

        final IHDF5Reader reader = HDF5Factory.openForReading(file);
        final HDF5CompoundType<SimpleInheretingRecord> type =
                reader.compound().getDataSetType("cpd", SimpleInheretingRecord.class);
        assertFalse(type.isMappingIncomplete());
        assertFalse(type.isDiskRepresentationIncomplete());
        assertFalse(type.isMemoryRepresentationIncomplete());
        type.checkMappingComplete();
        final SimpleInheretingRecord recordRead = reader.compound().read("cpd", type);
        assertEquals(recordWritten, recordRead);
        final SimpleInheretingRecord2 recordRead2 =
                reader.compound().read("cpd2", SimpleInheretingRecord2.class);
        assertEquals(recordWritten2, recordRead2);
        final SimpleInheretingRecord3 recordRead3 =
                reader.compound().read("cpd3", SimpleInheretingRecord3.class);
        assertEquals(recordWritten3, recordRead3);
        reader.close();

        final IHDF5Reader reader2 = HDF5Factory.openForReading(file2);
        final HDF5CompoundType<SimpleInheretingRecord> type2 =
                reader2.compound().getDataSetType("cpd", SimpleInheretingRecord.class);
        assertFalse(type2.isMappingIncomplete());
        assertFalse(type2.isDiskRepresentationIncomplete());
        assertFalse(type2.isMemoryRepresentationIncomplete());
        assertEquals("UNKNOWN", type2.getName());
        type2.checkMappingComplete();
        final SimpleInheretingRecord recordReadFile2 = reader2.compound().read("cpd", type2);
        assertEquals(recordWritten, recordReadFile2);
        reader2.close();
    }

    static class StringEnumCompoundType
    {
        String fruit;

        StringEnumCompoundType()
        {
        }

        StringEnumCompoundType(String fruit)
        {
            this.fruit = fruit;
        }
    }

    static class OrdinalEnumCompoundType
    {
        int fruit;

        OrdinalEnumCompoundType()
        {
        }

        OrdinalEnumCompoundType(int fruit)
        {
            this.fruit = fruit;
        }
    }

    @Test
    public void testCompoundJavaEnum()
    {
        final File file = new File(workingDirectory, "compoundJavaEnum.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer writer = HDF5Factory.open(file);
        final JavaEnumCompoundType recordWritten = new JavaEnumCompoundType(FruitEnum.CHERRY);
        writer.compound().write("cpd", recordWritten);
        writer.close();

        final IHDF5Reader reader = HDF5Factory.openForReading(file);
        final HDF5CompoundType<JavaEnumCompoundType> type =
                reader.compound().getDataSetType("cpd", JavaEnumCompoundType.class);
        assertFalse(type.isMappingIncomplete());
        assertFalse(type.isDiskRepresentationIncomplete());
        assertFalse(type.isMemoryRepresentationIncomplete());
        type.checkMappingComplete();
        final JavaEnumCompoundType recordRead = reader.compound().read("cpd", type);
        assertEquals(recordWritten, recordRead);
        final StringEnumCompoundType stringRecordRead =
                reader.readCompound("cpd", StringEnumCompoundType.class);
        assertEquals(FruitEnum.CHERRY.name(), stringRecordRead.fruit);
        final OrdinalEnumCompoundType ordinalRecordRead =
                reader.readCompound("cpd", OrdinalEnumCompoundType.class);
        assertEquals(FruitEnum.CHERRY.ordinal(), ordinalRecordRead.fruit);
        reader.close();
    }

    @Test
    public void testEnumFromCompoundJavaEnum()
    {
        final File file = new File(workingDirectory, "enumsFromCompoundJavaEnum.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer writer = HDF5Factory.open(file);
        final JavaMultipleEnumsCompoundType recordWritten =
                new JavaMultipleEnumsCompoundType(FruitEnum.APPLE, ColorEnum.BLUE,
                        StateEnum.ONGOING);
        HDF5CompoundType<JavaMultipleEnumsCompoundType> type =
                writer.compound().getInferredAnonType(JavaMultipleEnumsCompoundType.class);
        writer.compound().write("cpd", type, recordWritten);
        Map<String, HDF5EnumerationType> enumMap = type.getEnumTypeMap();
        assertEquals("[fruit, color, state]", enumMap.keySet().toString());
        writer.enumeration().write("fruit",
                new HDF5EnumerationValue(enumMap.get("fruit"), "ORANGE"));
        writer.enumeration()
                .write("color", new HDF5EnumerationValue(enumMap.get("color"), "BLACK"));
        writer.enumeration()
                .write("state", new HDF5EnumerationValue(enumMap.get("state"), "READY"));
        writer.close();

        final IHDF5Reader reader = HDF5Factory.openForReading(file);
        type = reader.compound().getDataSetType("cpd", JavaMultipleEnumsCompoundType.class);
        assertFalse(type.isMappingIncomplete());
        assertFalse(type.isDiskRepresentationIncomplete());
        assertFalse(type.isMemoryRepresentationIncomplete());
        type.checkMappingComplete();
        final JavaMultipleEnumsCompoundType recordRead = reader.compound().read("cpd", type);
        enumMap = type.getEnumTypeMap();
        assertEquals(recordWritten, recordRead);
        assertEquals(FruitEnum.APPLE, recordRead.fruit);
        assertEquals(ColorEnum.BLUE, recordRead.color);
        assertEquals(StateEnum.ONGOING, recordRead.state);
        assertEquals(reader.enumeration().getDataSetType("fruit"), enumMap.get("fruit"));
        assertEquals(reader.enumeration().getDataSetType("color"), enumMap.get("color"));
        assertEquals(reader.enumeration().getDataSetType("state"), enumMap.get("state"));
        assertEquals("ORANGE", reader.enumeration().read("fruit").getValue());
        assertEquals("BLACK", reader.enumeration().read("color").getValue());
        assertEquals("READY", reader.enumeration().read("state").getValue());
        reader.close();
    }

    static class JavaEnumArrayCompoundType
    {
        FruitEnum[] fruits;

        JavaEnumArrayCompoundType()
        {
        }

        JavaEnumArrayCompoundType(FruitEnum[] fruits)
        {
            this.fruits = fruits;
        }

        @Override
        public int hashCode()
        {
            final int prime = 31;
            int result = 1;
            result = prime * result + Arrays.hashCode(fruits);
            return result;
        }

        @Override
        public boolean equals(Object obj)
        {
            if (this == obj)
            {
                return true;
            }
            if (obj == null)
            {
                return false;
            }
            if (getClass() != obj.getClass())
            {
                return false;
            }
            JavaEnumArrayCompoundType other = (JavaEnumArrayCompoundType) obj;
            if (!Arrays.equals(fruits, other.fruits))
            {
                return false;
            }
            return true;
        }

        @Override
        public String toString()
        {
            return "JavaEnumArrayCompoundType [fruits=" + Arrays.toString(fruits) + "]";
        }
    }

    static class StringEnumArrayCompoundType
    {
        String[] fruits;

        StringEnumArrayCompoundType()
        {
        }

        StringEnumArrayCompoundType(String[] fruits)
        {
            this.fruits = fruits;
        }
    }

    static class OrdinalEnumArrayCompoundType
    {
        int[] fruits;

        OrdinalEnumArrayCompoundType()
        {
        }

        OrdinalEnumArrayCompoundType(int[] fruits)
        {
            this.fruits = fruits;
        }
    }

    @Test
    public void testCompoundJavaEnumArray()
    {
        final File file = new File(workingDirectory, "compoundJavaEnumArray.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer writer = HDF5Factory.open(file);
        final JavaEnumArrayCompoundType recordWritten =
                new JavaEnumArrayCompoundType(new FruitEnum[]
                    { FruitEnum.CHERRY, FruitEnum.APPLE, FruitEnum.ORANGE });
        writer.compound().write("cpd", recordWritten);
        writer.close();

        final IHDF5Reader reader = HDF5Factory.openForReading(file);
        final HDF5CompoundType<JavaEnumArrayCompoundType> type =
                reader.compound().getDataSetType("cpd", JavaEnumArrayCompoundType.class);
        assertFalse(type.isMappingIncomplete());
        assertFalse(type.isDiskRepresentationIncomplete());
        assertFalse(type.isMemoryRepresentationIncomplete());
        type.checkMappingComplete();
        final JavaEnumArrayCompoundType recordRead = reader.compound().read("cpd", type);
        assertEquals(recordWritten, recordRead);
        final StringEnumArrayCompoundType stringRecordRead =
                reader.readCompound("cpd", StringEnumArrayCompoundType.class);
        assertTrue(Arrays.toString(stringRecordRead.fruits), Arrays.equals(new String[]
            { "CHERRY", "APPLE", "ORANGE" }, stringRecordRead.fruits));
        final OrdinalEnumArrayCompoundType ordinalRecordRead =
                reader.readCompound("cpd", OrdinalEnumArrayCompoundType.class);
        assertTrue(Arrays.toString(ordinalRecordRead.fruits), Arrays.equals(new int[]
            { 2, 0, 1 }, ordinalRecordRead.fruits));
        reader.close();
    }

    @Test
    public void testCompoundJavaEnumMap()
    {
        final File file = new File(workingDirectory, "compoundJavaEnumMap.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer writer = HDF5Factory.open(file);
        final HDF5CompoundDataMap recordWritten = new HDF5CompoundDataMap();
        recordWritten.put("fruit", FruitEnum.ORANGE);
        writer.compound().write("cpd", recordWritten);
        writer.close();

        final IHDF5Reader reader = HDF5Factory.openForReading(file);
        final HDF5CompoundType<HDF5CompoundDataMap> type =
                reader.compound().getDataSetType("cpd", HDF5CompoundDataMap.class);
        assertFalse(type.isMappingIncomplete());
        assertFalse(type.isDiskRepresentationIncomplete());
        assertFalse(type.isMemoryRepresentationIncomplete());
        type.checkMappingComplete();
        final Map<String, Object> recordRead = reader.compound().read("cpd", type);
        assertEquals(1, recordRead.size());
        assertEquals("ORANGE", recordRead.get("fruit").toString());
        reader.close();
    }

    @Test
    public void testCompoundIncompleteJavaPojo()
    {
        final File file = new File(workingDirectory, "compoundIncompleteJavaPojo.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer writer = HDF5Factory.open(file);
        final SimpleInheretingRecord recordWritten =
                new SimpleInheretingRecord(3.14159f, 42, (short) 17, "xzy", new long[][]
                    {
                        { 1, 2, 3 },
                        { 4, 5, 6 } });
        writer.compound().write("cpd", recordWritten);
        writer.close();

        final IHDF5Reader reader = HDF5Factory.openForReading(file);
        final HDF5CompoundType<SimpleRecord> type =
                reader.compound().getDataSetType("cpd", SimpleRecord.class);
        assertTrue(type.isMappingIncomplete());
        assertFalse(type.isDiskRepresentationIncomplete());
        assertTrue(type.isMemoryRepresentationIncomplete());
        try
        {
            type.checkMappingComplete();
            fail("Uncomplete mapping not detected.");
        } catch (HDF5JavaException ex)
        {
            assertEquals(
                    "Incomplete mapping for compound type 'SimpleInheretingRecord': unmapped members: {ll}",
                    ex.getMessage());
        }
        final SimpleRecord recordRead = reader.compound().read("cpd", type);
        assertEquals(recordWritten.getF(), recordRead.getF());
        assertEquals(recordWritten.getI(), recordRead.getI());
        assertEquals(recordWritten.getD(), recordRead.getD());
        assertEquals(recordWritten.getS(), recordRead.getS());
        final HDF5CompoundType<SimpleRecord> type2 =
                reader.compound().getInferredType("cpd", SimpleRecord.class, null, false);
        assertFalse(type2.isMappingIncomplete());
        assertFalse(type2.isDiskRepresentationIncomplete());
        assertFalse(type2.isMemoryRepresentationIncomplete());
        type2.checkMappingComplete();
        final SimpleRecord recordRead2 = reader.compound().read("cpd", type2);
        assertEquals(recordWritten.getF(), recordRead2.getF());
        assertEquals(recordWritten.getI(), recordRead2.getI());
        assertEquals(recordWritten.getD(), recordRead2.getD());
        assertEquals(recordWritten.getS(), recordRead2.getS());
        reader.close();
    }

    @Test
    public void testCompoundHintVLString()
    {
        final File file = new File(workingDirectory, "testCompoundHintVLString.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer writer = HDF5Factory.configure(file).useUTF8CharacterEncoding().writer();
        writer.compound().getInferredType(SimpleStringRecord.class,
                new HDF5CompoundMappingHints().useVariableLengthStrings());
        final HDF5CompoundType<SimpleStringRecord> typeWritten =
                writer.compound().getInferredType("SimpleStringRecordByTemplate",
                        new SimpleStringRecord("aaa", "bb"),
                        new HDF5CompoundMappingHints().useVariableLengthStrings());
        final SimpleStringRecord recordWritten = new SimpleStringRecord("aaa", "\u3453");
        writer.compound().write("cpd", typeWritten, recordWritten);
        writer.close();
        final IHDF5Reader reader = HDF5Factory.openForReading(file);
        final HDF5CompoundType<SimpleStringRecord> typeRead =
                reader.compound().getNamedType(SimpleStringRecord.class);
        final HDF5CompoundType<SimpleStringRecord> type2Read =
                reader.compound().getNamedType("SimpleStringRecordByTemplate",
                        SimpleStringRecord.class);
        assertEquals(2, typeRead.getCompoundMemberInformation().length);
        assertTrue(typeRead.getCompoundMemberInformation()[0].getType().isVariableLengthString());
        assertTrue(typeRead.getCompoundMemberInformation()[1].getType().isVariableLengthString());
        assertTrue(type2Read.getCompoundMemberInformation()[0].getType().isVariableLengthString());
        assertTrue(type2Read.getCompoundMemberInformation()[1].getType().isVariableLengthString());
        final SimpleStringRecord recordRead = reader.compound().read("cpd", type2Read);
        assertEquals(recordWritten, recordRead);
        reader.close();
    }

    @Test
    public void testCompoundMap()
    {
        final File file = new File(workingDirectory, "testCompoundMap.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer writer = HDF5Factory.configure(file).useUTF8CharacterEncoding().writer();
        final HDF5EnumerationType enumType =
                writer.enumeration().getType("someEnumType", new String[]
                    { "1", "Two", "THREE" });
        final HDF5CompoundDataMap map = new HDF5CompoundDataMap();
        final float a = 3.14159f;
        map.put("a", a);
        final int[] b = new int[]
            { 17, -1 };
        map.put("b", b);
        final String c = "Teststring\u3453";
        map.put("c", c);
        final HDF5EnumerationValueArray d = new HDF5EnumerationValueArray(enumType, new String[]
            { "Two", "1" });
        map.put("d", d);
        final BitSet e = new BitSet();
        e.set(15);
        map.put("e", e);
        final float[][] f = new float[][]
            {
                { 1.0f, -1.0f },
                { 1e6f, -1e6f } };
        map.put("f", f);
        final MDLongArray g = new MDLongArray(new long[]
            { 1, 2, 3, 4, 5, 6, 7, 8 }, new int[]
            { 2, 2, 2 });
        map.put("g", g);
        final HDF5TimeDuration h = new HDF5TimeDuration(17, HDF5TimeUnit.HOURS);
        map.put("h", h);
        final Date ii = new Date(10000);
        map.put("i", ii);
        writer.compound().write("cpd", map);
        writer.close();

        final IHDF5Reader reader = HDF5Factory.openForReading(file);
        final HDF5CompoundType<HDF5CompoundDataMap> typeRead =
                reader.compound().getDataSetType("cpd", HDF5CompoundDataMap.class);
        assertEquals("a:b:c:d:e:f:g:h:i", typeRead.getName());
        final HDF5CompoundDataMap mapRead = reader.compound().read("cpd", typeRead);
        assertEquals(9, mapRead.size());
        assertEquals(a, mapRead.get("a"));
        assertTrue(ArrayUtils.toString(mapRead.get("b")), Objects.deepEquals(b, mapRead.get("b")));
        assertEquals(c, mapRead.get("c"));
        final HDF5EnumerationValueArray dRead = (HDF5EnumerationValueArray) mapRead.get("d");
        assertEquals("someEnumType", dRead.getType().getName());
        assertEquals(d.getLength(), dRead.getLength());
        for (int i = 0; i < d.getLength(); ++i)
        {
            assertEquals("enum array idx=" + i, d.getValue(i), dRead.getValue(i));
        }
        assertEquals(e, mapRead.get("e"));
        assertTrue(ArrayUtils.toString(mapRead.get("f")), Objects.deepEquals(f, mapRead.get("f")));
        assertEquals(g, mapRead.get("g"));
        assertEquals(h, mapRead.get("h"));
        assertEquals(ii, mapRead.get("i"));

        final HDF5CompoundType<HDF5CompoundDataMap> typeRead2 =
                reader.compound().getDataSetType("cpd", HDF5CompoundDataMap.class,
                        new HDF5CompoundMappingHints().enumReturnType(EnumReturnType.STRING));
        final HDF5CompoundDataMap mapRead2 = reader.compound().read("cpd", typeRead2);
        final String[] dRead2 = (String[]) mapRead2.get("d");
        assertEquals(dRead.getLength(), dRead2.length);
        for (int i = 0; i < dRead2.length; ++i)
        {
            assertEquals(dRead.getValue(i), dRead2[i]);
        }

        final HDF5CompoundType<HDF5CompoundDataMap> typeRead3 =
                reader.compound().getDataSetType("cpd", HDF5CompoundDataMap.class,
                        new HDF5CompoundMappingHints().enumReturnType(EnumReturnType.ORDINAL));
        final HDF5CompoundDataMap mapRead3 = reader.compound().read("cpd", typeRead3);
        final int[] dRead3 = (int[]) mapRead3.get("d");
        assertEquals(dRead.getLength(), dRead3.length);
        for (int i = 0; i < dRead3.length; ++i)
        {
            assertEquals(dRead.getOrdinal(i), dRead3[i]);
        }
        reader.close();
    }

    @Test
    public void testCompoundMapManualMapping()
    {
        final File file = new File(workingDirectory, "testCompoundMapManualMapping.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(file);
        final HDF5EnumerationType enumType =
                writer.enumeration().getType("someEnumType", new String[]
                    { "1", "Two", "THREE" });
        final HDF5CompoundType<HDF5CompoundDataMap> type =
                writer.compound()
                        .getType(
                                "MapCompoundA",
                                HDF5CompoundDataMap.class,
                                new HDF5CompoundMemberMapping[]
                                    {
                                            HDF5CompoundMemberMapping.mapping("a").memberClass(
                                                    float.class),
                                            mapping("b").memberClass(int[].class).length(2),
                                            mapping("c").memberClass(char[].class).length(12),
                                            mapping("d").enumType(enumType).length(2),
                                            mapping("e").memberClass(BitSet.class).length(2),
                                            mapping("f").memberClass(float[][].class).dimensions(2,
                                                    2),
                                            mapping("g").memberClass(MDLongArray.class).dimensions(
                                                    new int[]
                                                        { 2, 2, 2 }),
                                            mapping("h")
                                                    .memberClass(HDF5TimeDuration.class)
                                                    .typeVariant(
                                                            HDF5DataTypeVariant.TIME_DURATION_HOURS),
                                            mapping("i").memberClass(Date.class) });
        final HDF5CompoundDataMap map = new HDF5CompoundDataMap();
        final float a = 3.14159f;
        map.put("a", a);
        final int[] b = new int[]
            { 17, -1 };
        map.put("b", b);
        final String c = "Teststring";
        map.put("c", c);
        final HDF5EnumerationValueArray d = new HDF5EnumerationValueArray(enumType, new String[]
            { "Two", "1" });
        map.put("d", d);
        final BitSet e = new BitSet();
        e.set(15);
        map.put("e", e);
        final float[][] f = new float[][]
            {
                { 1.0f, -1.0f },
                { 1e6f, -1e6f } };
        map.put("f", f);
        final MDLongArray g = new MDLongArray(new long[]
            { 1, 2, 3, 4, 5, 6, 7, 8 }, new int[]
            { 2, 2, 2 });
        map.put("g", g);
        final HDF5TimeDuration h = new HDF5TimeDuration(17, HDF5TimeUnit.HOURS);
        map.put("h", h);
        final Date ii = new Date(10000);
        map.put("i", ii);
        writer.compound().write("cpd", type, map);
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(file);
        final HDF5CompoundType<HDF5CompoundDataMap> typeRead =
                reader.compound().getDataSetType("cpd", HDF5CompoundDataMap.class);
        assertEquals("MapCompoundA", typeRead.getName());
        final HDF5CompoundDataMap mapRead = reader.compound().read("cpd", typeRead);
        assertEquals(9, mapRead.size());
        assertEquals(a, mapRead.get("a"));
        assertTrue(ArrayUtils.toString(mapRead.get("b")), Objects.deepEquals(b, mapRead.get("b")));
        assertEquals(c, mapRead.get("c"));
        final HDF5EnumerationValueArray dRead = (HDF5EnumerationValueArray) mapRead.get("d");
        assertEquals("someEnumType", dRead.getType().getName());
        assertEquals(d.getLength(), dRead.getLength());
        for (int i = 0; i < d.getLength(); ++i)
        {
            assertEquals("enum array idx=" + i, d.getValue(i), dRead.getValue(i));
        }
        assertEquals(e, mapRead.get("e"));
        assertTrue(ArrayUtils.toString(mapRead.get("f")), Objects.deepEquals(f, mapRead.get("f")));
        assertEquals(g, mapRead.get("g"));
        assertEquals(h, mapRead.get("h"));
        assertEquals(ii, mapRead.get("i"));
        reader.close();
    }

    @Test
    public void testCompoundMapManualMappingWithConversion()
    {
        final File file =
                new File(workingDirectory, "testCompoundMapManualMappingWithConversion.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(file);
        final HDF5EnumerationType enumType =
                writer.enumeration().getType("someEnumType", new String[]
                    { "1", "Two", "THREE" });
        final HDF5CompoundType<HDF5CompoundDataMap> type =
                writer.compound()
                        .getType(
                                "MapCompoundA",
                                HDF5CompoundDataMap.class,
                                new HDF5CompoundMemberMapping[]
                                    {
                                            HDF5CompoundMemberMapping.mapping("a").memberClass(
                                                    float.class),
                                            mapping("b").memberClass(short.class),
                                            mapping("c").memberClass(Date.class),
                                            mapping("d").enumType(enumType).length(2),
                                            mapping("e").memberClass(double.class),
                                            mapping("f")
                                                    .memberClass(HDF5TimeDuration.class)
                                                    .typeVariant(
                                                            HDF5DataTypeVariant.TIME_DURATION_HOURS) });
        final HDF5CompoundDataMap map = new HDF5CompoundDataMap();
        final double a = 3.14159;
        map.put("a", a);
        final int b = 17;
        map.put("b", b);
        final long c = System.currentTimeMillis();
        map.put("c", c);
        final int[] d = new int[]
            { 1, 0 };
        map.put("d", d);
        final long e = 187493613;
        map.put("e", e);
        final short f = 12;
        map.put("f", f);
        writer.compound().write("cpd", type, map);
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(file);
        final HDF5CompoundType<HDF5CompoundDataMap> typeRead =
                reader.compound().getDataSetType("cpd", HDF5CompoundDataMap.class);
        assertEquals("MapCompoundA", typeRead.getName());
        final HDF5CompoundDataMap mapRead = reader.compound().read("cpd", typeRead);
        assertEquals(map.size(), mapRead.size());
        assertEquals((float) a, mapRead.get("a"));
        assertEquals((short) b, mapRead.get("b"));
        assertEquals(new Date(c), mapRead.get("c"));
        final HDF5EnumerationValueArray dRead = (HDF5EnumerationValueArray) mapRead.get("d");
        assertEquals("someEnumType", dRead.getType().getName());
        assertEquals(d.length, dRead.getLength());
        for (int i = 0; i < d.length; ++i)
        {
            assertEquals("enum array idx=" + i, d[i], dRead.getOrdinal(i));
        }
        assertEquals((double) e, mapRead.get("e"));
        assertEquals(new HDF5TimeDuration(f, HDF5TimeUnit.HOURS), mapRead.get("f"));
        reader.close();
    }

    @Test
    public void testCompoundManualMapping()
    {
        final File file = new File(workingDirectory, "compoundManualMapping.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(file);
        HDF5CompoundType<Record> compoundType = Record.getHDF5Type(writer);
        HDF5EnumerationType enumType = writer.enumeration().getType("someEnumType");
        final Record recordWritten =
                new Record(1, 2.0f, 100000000L, 3.0, (short) 4, true, "one",
                        new HDF5EnumerationValue(enumType, "THREE"), new int[]
                            { 1, 2, 3 }, new float[]
                            { 8.0f, -17.0f }, new long[]
                            { -10, -11, -12 }, new double[]
                            { 3.14159 }, new short[]
                            { 1000, 2000 }, new byte[]
                            { 11, 12, 13, 14 }, new MDIntArray(new int[][]
                            {
                                { 1, 2 },
                                { 3, 4 } }), new char[]
                            { 'A', 'b', 'C' });
        writer.compound().write("/testCompound", compoundType, recordWritten);
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(file);
        final HDF5CompoundMemberInformation[] memMemberInfo =
                Record.getMemberInfo(reader.enumeration().getType("someEnumType"));
        final HDF5CompoundMemberInformation[] diskMemberInfo =
                reader.compound().getDataSetInfo("/testCompound", DataTypeInfoOptions.ALL);
        assertEquals(memMemberInfo.length, diskMemberInfo.length);
        Arrays.sort(memMemberInfo);
        Arrays.sort(diskMemberInfo);
        for (int i = 0; i < memMemberInfo.length; ++i)
        {
            assertEquals(memMemberInfo[i], diskMemberInfo[i]);
        }
        compoundType = Record.getHDF5Type(reader);
        final Record recordRead =
                reader.compound().read("/testCompound", Record.getHDF5Type(reader));
        assertEquals(recordWritten, recordRead);
        reader.close();
    }

    @Test
    public void testCompoundMapArray()
    {
        final File file = new File(workingDirectory, "testCompoundMapArray.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer writer = HDF5Factory.open(file);
        final HDF5CompoundDataMap map1 = new HDF5CompoundDataMap();
        final float a1 = 3.14159f;
        map1.put("a", a1);
        final HDF5CompoundDataMap map2 = new HDF5CompoundDataMap();
        final float a2 = 18.32f;
        map2.put("a", a2);
        final HDF5CompoundDataMap map3 = new HDF5CompoundDataMap();
        final float a3 = 1.546e5f;
        map3.put("a", a3);
        writer.writeCompoundArray("cpd", new HDF5CompoundDataMap[]
            { map1, map2, map3 });
        writer.close();

        final IHDF5Reader reader = HDF5Factory.openForReading(file);
        final HDF5CompoundDataMap[] maps =
                reader.compound().readArray("cpd", HDF5CompoundDataMap.class);
        assertEquals(3, maps.length);
        assertEquals(map1, maps[0]);
        assertEquals(map2, maps[1]);
        assertEquals(map3, maps[2]);
        reader.close();
    }

    @Test
    public void testCompoundMapMDArray()
    {
        final File file = new File(workingDirectory, "testCompoundMapMDArray.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer writer = HDF5Factory.open(file);
        final HDF5CompoundDataMap map1 = new HDF5CompoundDataMap();
        final float a1 = 3.14159f;
        map1.put("a", a1);
        final HDF5CompoundDataMap map2 = new HDF5CompoundDataMap();
        final float a2 = 18.32f;
        map2.put("a", a2);
        final HDF5CompoundDataMap map3 = new HDF5CompoundDataMap();
        final float a3 = 1.546e5f;
        map3.put("a", a3);
        final HDF5CompoundDataMap map4 = new HDF5CompoundDataMap();
        final float a4 = -3.2f;
        map4.put("a", a4);
        writer.compound().writeMDArray("cpd",
                new MDArray<HDF5CompoundDataMap>(new HDF5CompoundDataMap[]
                    { map1, map2, map3, map4 }, new int[]
                    { 2, 2 }));
        writer.close();

        final IHDF5Reader reader = HDF5Factory.openForReading(file);
        final MDArray<HDF5CompoundDataMap> maps =
                reader.compound().readMDArray("cpd", HDF5CompoundDataMap.class);
        assertTrue(Objects.deepEquals(new int[]
            { 2, 2 }, maps.dimensions()));
        assertEquals(map1, maps.get(0, 0));
        assertEquals(map2, maps.get(0, 1));
        assertEquals(map3, maps.get(1, 0));
        assertEquals(map4, maps.get(1, 1));
        reader.close();
    }

    static class DateRecord
    {
        Date d;

        DateRecord()
        {
        }

        DateRecord(Date d)
        {
            this.d = d;
        }

        @Override
        public int hashCode()
        {
            final int prime = 31;
            int result = 1;
            result = prime * result + ((d == null) ? 0 : d.hashCode());
            return result;
        }

        @Override
        public boolean equals(Object obj)
        {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;
            DateRecord other = (DateRecord) obj;
            if (d == null)
            {
                if (other.d != null)
                    return false;
            } else if (!d.equals(other.d))
                return false;
            return true;
        }

    }

    @Test
    public void testDateCompound()
    {
        final File file = new File(workingDirectory, "compoundWithDate.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(file);
        HDF5CompoundType<DateRecord> compoundType =
                writer.compound().getType(DateRecord.class, new HDF5CompoundMemberMapping[]
                    { mapping("d") });
        final DateRecord recordWritten = new DateRecord(new Date());
        final String objectPath = "/testDateCompound";
        writer.compound().write(objectPath, compoundType, recordWritten);
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(file);
        final HDF5CompoundMemberInformation[] memMemberInfo =
                HDF5CompoundMemberInformation.create(DateRecord.class, "", mapping("d"));
        final HDF5CompoundMemberInformation[] diskMemberInfo =
                HDF5CompoundMemberInformation.create(DateRecord.class, "",
                        new HDF5CompoundMemberMapping[]
                            { mapping("d") });
        assertEquals(memMemberInfo.length, diskMemberInfo.length);
        for (int i = 0; i < memMemberInfo.length; ++i)
        {
            assertEquals(memMemberInfo[i], diskMemberInfo[i]);
        }
        compoundType = reader.compound().getType(DateRecord.class, new HDF5CompoundMemberMapping[]
            { mapping("d") });
        assertEquals(HDF5DataTypeVariant.TIMESTAMP_MILLISECONDS_SINCE_START_OF_THE_EPOCH,
                compoundType.getObjectByteifyer().getByteifyers()[0].getTypeVariant());
        final DateRecord recordRead =
                reader.compound().read(objectPath,
                        reader.compound().getType(DateRecord.class, mapping("d")));
        assertEquals(recordWritten, recordRead);
        HDF5CompoundType<HDF5CompoundDataMap> mapCompoundType =
                reader.compound().getDataSetType(objectPath, HDF5CompoundDataMap.class);
        assertEquals(HDF5DataTypeVariant.TIMESTAMP_MILLISECONDS_SINCE_START_OF_THE_EPOCH,
                mapCompoundType.getObjectByteifyer().getByteifyers()[0].getTypeVariant());
        final HDF5CompoundDataMap mapRead = reader.compound().read(objectPath, mapCompoundType);
        assertEquals(recordWritten.d, mapRead.get("d"));
        reader.close();
    }

    static class MatrixRecord
    {
        byte[][] b;

        short[][] s;

        int[][] i;

        long[][] l;

        float[][] f;

        double[][] d;

        MatrixRecord()
        {
        }

        MatrixRecord(byte[][] b, short[][] s, int[][] i, long[][] l, float[][] f, double[][] d)
        {
            this.b = b;
            this.s = s;
            this.i = i;
            this.l = l;
            this.f = f;
            this.d = d;
        }

        static HDF5CompoundMemberMapping[] getMapping()
        {
            return new HDF5CompoundMemberMapping[]
                { mapping("b").dimensions(1, 2), mapping("s").dimensions(2, 1),
                        mapping("i").dimensions(2, 2), mapping("l").dimensions(3, 2),
                        mapping("f").dimensions(2, 2), mapping("d").dimensions(2, 3) };
        }

        @Override
        public boolean equals(Object obj)
        {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;
            MatrixRecord other = (MatrixRecord) obj;
            if (!HDF5RoundtripTest.equals(b, other.b))
                return false;
            if (!HDF5RoundtripTest.equals(d, other.d))
                return false;
            if (!HDF5RoundtripTest.equals(f, other.f))
                return false;
            if (!HDF5RoundtripTest.equals(i, other.i))
                return false;
            if (!HDF5RoundtripTest.equals(l, other.l))
                return false;
            if (!HDF5RoundtripTest.equals(s, other.s))
                return false;
            return true;
        }

    }

    @Test
    public void testMatrixCompound()
    {
        assertEquals(0, HDF5Factory.getOpenHDF5FileCount());
        final File file = new File(workingDirectory, "compoundWithMatrix.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(file);
        HDF5CompoundType<MatrixRecord> compoundType =
                writer.compound().getType(MatrixRecord.class, MatrixRecord.getMapping());
        final MatrixRecord recordWritten = new MatrixRecord(new byte[][]
            {
                { 1, 2 } }, new short[][]
            {
                { 1 },
                { 2 } }, new int[][]
            {
                { 1, 2 },
                { 3, 4 } }, new long[][]
            {
                { 1, 2 },
                { 3, 4 },
                { 5, 6 } }, new float[][]
            {
                { 1, 2 },
                { 3, 4 } }, new double[][]
            {
                { 1, 2, 3 },
                { 4, 5, 6 } });
        String name = "/testMatrixCompound";
        writer.compound().write(name, compoundType, recordWritten);
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(file);
        final HDF5CompoundMemberInformation[] memMemberInfo =
                HDF5CompoundMemberInformation.create(MatrixRecord.class, "",
                        MatrixRecord.getMapping());
        final HDF5CompoundMemberInformation[] diskMemberInfo =
                HDF5CompoundMemberInformation.create(MatrixRecord.class, "",
                        MatrixRecord.getMapping());
        assertEquals(memMemberInfo.length, diskMemberInfo.length);
        for (int i = 0; i < memMemberInfo.length; ++i)
        {
            assertEquals(memMemberInfo[i], diskMemberInfo[i]);
        }
        compoundType = reader.compound().getType(MatrixRecord.class, MatrixRecord.getMapping());
        final MatrixRecord recordRead =
                reader.compound().read(name,
                        reader.compound().getType(MatrixRecord.class, MatrixRecord.getMapping()));
        assertEquals(recordWritten, recordRead);
        reader.close();
    }

    @Test(expectedExceptions = IllegalArgumentException.class)
    public void testMatrixCompoundSizeMismatch()
    {
        final File file = new File(workingDirectory, "compoundWithSizeMismatchMatrix.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        try (final IHDF5Writer writer = HDF5FactoryProvider.get().open(file))
        {
            HDF5CompoundType<MatrixRecord> compoundType =
                    writer.compound().getType(MatrixRecord.class, MatrixRecord.getMapping());
            final MatrixRecord recordWritten = new MatrixRecord(new byte[][]
                {
                    { 1, 2 } }, new short[][]
                {
                    { 1 },
                    { 2 } }, new int[][]
                {
                    { 1, 2 },
                    { 3, 4 } }, new long[][]
                {
                    { 1, 2 },
                    { 3, 4 },
                    { 5, 6 } }, new float[][]
                {
                    { 1, 2 },
                    { 3, 4 } }, new double[][]
                {
                    { 1, 2, 3, 4 },
                    { 5, 6, 7, 8 },
                    { 9, 10, 11, 12, 13 } });
            String name = "/testMatrixCompound";
            writer.compound().write(name, compoundType, recordWritten);
        }
    }

    @Test(expectedExceptions = IllegalArgumentException.class)
    public void testMatrixCompoundDifferentNumberOfColumnsPerRow()
    {
        final File file =
                new File(workingDirectory, "compoundWithMatrixDifferentNumberOfColumnsPerRow.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        try (final IHDF5Writer writer = HDF5FactoryProvider.get().open(file))
        {
            HDF5CompoundType<MatrixRecord> compoundType =
                    writer.compound().getType(MatrixRecord.class, MatrixRecord.getMapping());
            final MatrixRecord recordWritten = new MatrixRecord(new byte[][]
                {
                    { 1, 2 } }, new short[][]
                {
                    { 1 },
                    { 2 } }, new int[][]
                {
                    { 1, 2 },
                    { 3, 4 } }, new long[][]
                {
                    { 1, 2 },
                    { 3, 4 },
                    { 5, 6 } }, new float[][]
                {
                    { 1, 2 },
                    { 3, 4 } }, new double[][]
                {
                    { 1, 2, 3 },
                    { 4, 5 } });
            String name = "/testMatrixCompound";
            writer.compound().write(name, compoundType, recordWritten);
        }
    }

    private static boolean equals(double[][] a, double[][] a2)
    {
        if (a == a2)
        {
            return true;
        }
        if (a == null || a2 == null)
        {
            return false;
        }

        int rows = a.length;
        if (a2.length != rows)
        {
            return false;
        }

        for (int i = 0; i < rows; i++)
        {
            int columns = a[i].length;
            if (a2[i].length != columns)
            {
                return false;
            }
            for (int j = 0; j < columns; j++)
            {
                if (Double.doubleToLongBits(a[i][j]) != Double.doubleToLongBits(a2[i][j]))
                {
                    return false;
                }
            }
        }

        return true;
    }

    private static boolean equals(byte[][] a, byte[][] a2)
    {
        if (a == a2)
        {
            return true;
        }
        if (a == null || a2 == null)
        {
            return false;
        }

        int rows = a.length;
        if (a2.length != rows)
        {
            return false;
        }

        for (int i = 0; i < rows; i++)
        {
            int columns = a[i].length;
            if (a2[i].length != columns)
            {
                return false;
            }
            for (int j = 0; j < columns; j++)
            {
                if (a[i][j] != a2[i][j])
                {
                    return false;
                }
            }
        }

        return true;
    }

    private static boolean equals(short[][] a, short[][] a2)
    {
        if (a == a2)
        {
            return true;
        }
        if (a == null || a2 == null)
        {
            return false;
        }

        int rows = a.length;
        if (a2.length != rows)
        {
            return false;
        }

        for (int i = 0; i < rows; i++)
        {
            int columns = a[i].length;
            if (a2[i].length != columns)
            {
                return false;
            }
            for (int j = 0; j < columns; j++)
            {
                if (a[i][j] != a2[i][j])
                {
                    return false;
                }
            }
        }

        return true;
    }

    private static boolean equals(int[][] a, int[][] a2)
    {
        if (a == a2)
        {
            return true;
        }
        if (a == null || a2 == null)
        {
            return false;
        }

        int rows = a.length;
        if (a2.length != rows)
        {
            return false;
        }

        for (int i = 0; i < rows; i++)
        {
            int columns = a[i].length;
            if (a2[i].length != columns)
            {
                return false;
            }
            for (int j = 0; j < columns; j++)
            {
                if (a[i][j] != a2[i][j])
                {
                    return false;
                }
            }
        }

        return true;
    }

    private static boolean equals(long[][] a, long[][] a2)
    {
        if (a == a2)
        {
            return true;
        }
        if (a == null || a2 == null)
        {
            return false;
        }

        int rows = a.length;
        if (a2.length != rows)
        {
            return false;
        }

        for (int i = 0; i < rows; i++)
        {
            int columns = a[i].length;
            if (a2[i].length != columns)
            {
                return false;
            }
            for (int j = 0; j < columns; j++)
            {
                if (a[i][j] != a2[i][j])
                {
                    return false;
                }
            }
        }

        return true;
    }

    @Test
    public void testCompoundOverflow()
    {
        final File file = new File(workingDirectory, "compoundOverflow.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(file);
        HDF5CompoundType<Record> compoundType = Record.getHDF5Type(writer);
        HDF5EnumerationType enumType = writer.enumeration().getType("someEnumType");
        final Record recordWritten =
                new Record(1, 2.0f, 100000000L, 3.0, (short) 4, true, "one",
                        new HDF5EnumerationValue(enumType, "THREE"), new int[]
                            { 1, 2, 3 }, new float[]
                            { 8.0f, -17.0f }, new long[]
                            { -10, -11, -12 }, new double[]
                            { 3.14159 }, new short[]
                            { 1000, 2000 }, new byte[]
                            { 11, 12, 13, 14, 0, 0, 0 }, new MDIntArray(new int[][]
                            {
                                { 5, 6 },
                                { 7, 8 } }), new char[]
                            { 'A', 'b', 'C' });
        try
        {
            writer.compound().write("/testCompound", compoundType, recordWritten);
            fail("Failed to detect overflow.");
        } catch (HDF5JavaException ex)
        {
            if (ex.getMessage().contains("must not exceed 4 bytes") == false)
            {
                throw ex;
            }
            // Expected.
        } finally
        {
            writer.close();
        }
    }

    static class BitFieldRecord
    {
        BitSet bs;

        BitFieldRecord(BitSet bs)
        {
            this.bs = bs;
        }

        BitFieldRecord()
        {
        }

        static HDF5CompoundMemberInformation[] getMemberInfo()
        {
            return HDF5CompoundMemberInformation.create(BitFieldRecord.class, "", mapping("bs")
                    .length(100));
        }

        static HDF5CompoundType<BitFieldRecord> getHDF5Type(IHDF5Reader reader)
        {
            return reader.compound().getType(BitFieldRecord.class, mapping("bs").length(100));
        }

        @Override
        public boolean equals(Object obj)
        {
            if (obj instanceof BitFieldRecord == false)
            {
                return false;
            }
            final BitFieldRecord that = (BitFieldRecord) obj;
            return this.bs.equals(that.bs);
        }

        @Override
        public int hashCode()
        {
            return bs.hashCode();
        }
    }

    @Test
    public void testBitFieldCompound()
    {
        final File file = new File(workingDirectory, "compoundWithBitField.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(file);
        HDF5CompoundType<BitFieldRecord> compoundType = BitFieldRecord.getHDF5Type(writer);
        final BitSet bs = new BitSet();
        bs.set(39);
        bs.set(100);
        final BitFieldRecord recordWritten = new BitFieldRecord(bs);
        writer.compound().write("/testCompound", compoundType, recordWritten);
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(file);
        final HDF5CompoundMemberInformation[] memMemberInfo = BitFieldRecord.getMemberInfo();
        final HDF5CompoundMemberInformation[] diskMemberInfo =
                reader.compound().getDataSetInfo("/testCompound");
        assertEquals(memMemberInfo.length, diskMemberInfo.length);
        for (int i = 0; i < memMemberInfo.length; ++i)
        {
            assertEquals(memMemberInfo[i], diskMemberInfo[i]);
        }
        compoundType = BitFieldRecord.getHDF5Type(reader);
        final BitFieldRecord recordRead = reader.compound().read("/testCompound", compoundType);
        assertEquals(recordWritten, recordRead);
        reader.close();
    }

    @Test
    public void testCompoundArray()
    {
        final File file = new File(workingDirectory, "compoundArray.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(file);
        HDF5CompoundType<Record> compoundType = Record.getHDF5Type(writer);
        HDF5EnumerationType enumType = writer.enumeration().getType("someEnumType", new String[]
            { "1", "Two", "THREE" }, false);
        Record[] arrayWritten =
                new Record[]
                    {
                            new Record(1, 2.0f, 100000000L, 3.0, (short) -1, true, "one",
                                    new HDF5EnumerationValue(enumType, "THREE"), new int[]
                                        { 1, 2, 3 }, new float[]
                                        { 8.0f, -17.0f }, new long[]
                                        { -10, -11, -12 }, new double[]
                                        { 3.14159 }, new short[]
                                        { 1000, 2000 }, new byte[]
                                        { 11, 12, 13, -14 }, new MDIntArray(new int[][]
                                        {
                                            { 1, 2 },
                                            { 3, 4 } }), new char[]
                                        { 'A', 'b', 'C' }),
                            new Record(2, 3.0f, 100000000L, 4.0, (short) 5, false, "two",
                                    new HDF5EnumerationValue(enumType, "1"), new int[]
                                        { 4, 5, 6 }, new float[]
                                        { 8.0f, -17.0f }, new long[]
                                        { -10, -11, -12 }, new double[]
                                        { 3.14159 }, new short[]
                                        { 1000, 2000 }, new byte[]
                                        { 11, 12, 13, 14 }, new MDIntArray(new int[][]
                                        {
                                            { 5, 6 },
                                            { 7, 8 } }), new char[]
                                        { 'A', 'b', 'C' }), };
        writer.compound().writeArray("/testCompound", compoundType, arrayWritten,
                HDF5GenericStorageFeatures.GENERIC_COMPACT);
        HDF5CompoundType<Record> inferredType = writer.compound().getNamedType(Record.class);
        // Write again, this time with inferred type.
        writer.compound().writeArray("/testCompound", inferredType, arrayWritten,
                HDF5GenericStorageFeatures.GENERIC_COMPACT);
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(file);
        final HDF5CompoundMemberInformation[] memberInfo =
                reader.compound().getDataSetInfo("/testCompound");
        assertEquals(16, memberInfo.length);
        assertEquals("a", memberInfo[0].getName());
        assertTrue(memberInfo[0].getType().isSigned());
        assertEquals("d", memberInfo[4].getName());
        assertFalse(memberInfo[4].getType().isSigned());
        compoundType = Record.getHDF5Type(reader);
        inferredType = reader.compound().getDataSetType("/testCompound", Record.class);
        Record[] arrayRead = reader.compound().readArray("/testCompound", inferredType);
        Record firstElementRead = reader.compound().read("/testCompound", compoundType);
        assertEquals(arrayRead[0], firstElementRead);
        for (int i = 0; i < arrayRead.length; ++i)
        {
            assertEquals("" + i, arrayWritten[i], arrayRead[i]);
        }
        reader.close();
    }

    @Test
    public void testCompoundArrayBlockWise()
    {
        final File file = new File(workingDirectory, "compoundVectorBlockWise.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(file);
        HDF5CompoundType<Record> compoundType = Record.getHDF5Type(writer);
        HDF5EnumerationType enumType = writer.enumeration().getType("someEnumType");
        writer.compound().createArray("/testCompound", compoundType, 6, 3);
        Record[] arrayWritten1 =
                new Record[]
                    {
                            new Record(1, 2.0f, 100000000L, 3.0, (short) 4, true, "one",
                                    new HDF5EnumerationValue(enumType, "THREE"), new int[]
                                        { 1, 2, 3 }, new float[]
                                        { 8.0f, -17.0f }, new long[]
                                        { -10, -11, -12 }, new double[]
                                        { 3.14159 }, new short[]
                                        { 1000, 2000 }, new byte[]
                                        { 11, 12, 13, 14 }, new MDIntArray(new int[][]
                                        {
                                            { 1, 2 },
                                            { 3, 4 } }), new char[]
                                        { 'A', 'b', 'C' }),
                            new Record(2, 3.0f, 100000000L, 4.0, (short) 5, false, "two",
                                    new HDF5EnumerationValue(enumType, "1"), new int[]
                                        { 4, 5, 6 }, new float[]
                                        { 8.0f, -17.0f }, new long[]
                                        { -10, -11, -12 }, new double[]
                                        { 3.14159 }, new short[]
                                        { 1000, 2000 }, new byte[]
                                        { 11, 12, 13, 14 }, new MDIntArray(new int[][]
                                        {
                                            { 1, 2 },
                                            { 3, 4 } }), new char[]
                                        { 'A', 'b', 'C' }),
                            new Record(3, 3.0f, 100000000L, 5.0, (short) 6, true, "two",
                                    new HDF5EnumerationValue(enumType, "Two"), new int[]
                                        { -1, -2, -3 }, new float[]
                                        { 8.0f, -17.0f }, new long[]
                                        { -10, -11, -12 }, new double[]
                                        { 3.14159 }, new short[]
                                        { 1000, 2000 }, new byte[]
                                        { 11, 12, 13, 14 }, new MDIntArray(new int[][]
                                        {
                                            { 1, 2 },
                                            { 3, 4 } }), new char[]
                                        { 'A', 'b', 'C' }), };
        Record[] arrayWritten2 =
                new Record[]
                    {
                            new Record(4, 4.0f, 100000000L, 6.0, (short) 7, false, "two",
                                    new HDF5EnumerationValue(enumType, "Two"), new int[]
                                        { 100, 200, 300 }, new float[]
                                        { 8.0f, -17.0f }, new long[]
                                        { -10, -11, -12 }, new double[]
                                        { 3.14159 }, new short[]
                                        { 1000, 2000 }, new byte[]
                                        { 11, 12, 13, 14 }, new MDIntArray(new int[][]
                                        {
                                            { 6, 7 },
                                            { 8, 9 } }), new char[]
                                        { 'A', 'b', 'C' }),
                            new Record(5, 5.0f, 100000000L, 7.0, (short) 8, true, "two",
                                    new HDF5EnumerationValue(enumType, "THREE"), new int[]
                                        { 400, 500, 600 }, new float[]
                                        { 8.0f, -17.0f }, new long[]
                                        { -10, -11, -12 }, new double[]
                                        { 3.14159 }, new short[]
                                        { 1000, 2000 }, new byte[]
                                        { 11, 12, 13, 14 }, new MDIntArray(new int[][]
                                        {
                                            { 6, 7 },
                                            { 8, 9 } }), new char[]
                                        { 'A', 'b', 'C' }),
                            new Record(6, 6.0f, 100000000L, 8.0, (short) 9, false, "x",
                                    new HDF5EnumerationValue(enumType, "1"), new int[]
                                        { -100, -200, -300 }, new float[]
                                        { 8.0f, -17.0f }, new long[]
                                        { -10, -11, -12 }, new double[]
                                        { 3.14159 }, new short[]
                                        { 1000, 2000 }, new byte[]
                                        { 11, 12, 13, 14 }, new MDIntArray(new int[][]
                                        {
                                            { 6, 7 },
                                            { 8, 9 } }), new char[]
                                        { 'A', 'b', 'C' }), };
        writer.compound().writeArrayBlock("/testCompound", compoundType, arrayWritten1, 0);
        writer.compound().writeArrayBlock("/testCompound", compoundType, arrayWritten2, 1);
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(file);
        compoundType = Record.getHDF5Type(reader);
        Record[] arrayRead = reader.compound().readArrayBlock("/testCompound", compoundType, 3, 0);
        for (int i = 0; i < arrayRead.length; ++i)
        {
            assertEquals("" + i, arrayWritten1[i], arrayRead[i]);
        }
        arrayRead = reader.compound().readArrayBlock("/testCompound", compoundType, 3, 1);
        for (int i = 0; i < arrayRead.length; ++i)
        {
            assertEquals("" + i, arrayWritten2[i], arrayRead[i]);
        }
        arrayRead = reader.compound().readArrayBlockWithOffset("/testCompound", compoundType, 3, 1);
        for (int i = 1; i < arrayRead.length; ++i)
        {
            assertEquals("" + i, arrayWritten1[i], arrayRead[i - 1]);
        }
        assertEquals("" + (arrayRead.length - 1), arrayWritten2[0], arrayRead[arrayRead.length - 1]);
        reader.close();
    }

    @Test
    public void testCompoundMDArray()
    {
        final File file = new File(workingDirectory, "compoundMDArray.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer writer = HDF5Factory.open(file);
        writer.compound().writeMDArray(
                "cpd",
                new MDArray<SimpleRecord>(
                        new SimpleRecord[]
                            { createSR(1), createSR(2), createSR(3), createSR(4), createSR(5),
                                    createSR(6) }, new int[]
                            { 2, 3 }));
        writer.close();

        final IHDF5Reader reader = HDF5Factory.openForReading(file);
        final MDArray<SimpleRecord> records =
                reader.compound().readMDArray("cpd", SimpleRecord.class);
        assertEquals(6, records.size());
        assertTrue(Objects.deepEquals(new int[]
            { 2, 3 }, records.dimensions()));
        assertEquals(createSR(1), records.get(0, 0));
        assertEquals(createSR(2), records.get(0, 1));
        assertEquals(createSR(3), records.get(0, 2));
        assertEquals(createSR(4), records.get(1, 0));
        assertEquals(createSR(5), records.get(1, 1));
        assertEquals(createSR(6), records.get(1, 2));
        reader.close();
    }

    private static SimpleRecord createSR(int i)
    {
        return new SimpleRecord(i, i, (short) i, Integer.toString(i));
    }

    @Test
    public void testCompoundMDArrayManualMapping()
    {
        final File file = new File(workingDirectory, "compoundMDArrayManualMapping.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(file);
        HDF5CompoundType<Record> compoundType = Record.getHDF5Type(writer);
        HDF5EnumerationType enumType = writer.enumeration().getType("someEnumType");
        final Record[] arrayWritten =
                new Record[]
                    {
                            new Record(1, 2.0f, 100000000L, 3.0, (short) 4, true, "one",
                                    new HDF5EnumerationValue(enumType, "THREE"), new int[]
                                        { 1, 2, 3 }, new float[]
                                        { 8.0f, -17.0f }, new long[]
                                        { -10, -11, -12 }, new double[]
                                        { 3.14159 }, new short[]
                                        { 1000, 2000 }, new byte[]
                                        { 11, 12, 13, 14 }, new MDIntArray(new int[][]
                                        {
                                            { 6, 7 },
                                            { 8, 9 } }), new char[]
                                        { 'A', 'b', 'C' }),
                            new Record(2, 3.0f, 100000000L, 4.0, (short) 5, false, "two",
                                    new HDF5EnumerationValue(enumType, "1"), new int[]
                                        { 4, 5, 6 }, new float[]
                                        { 8.0f, -17.0f }, new long[]
                                        { -10, -11, -12 }, new double[]
                                        { 3.14159 }, new short[]
                                        { 1000, 2000 }, new byte[]
                                        { 11, 12, 13, 14 }, new MDIntArray(new int[][]
                                        {
                                            { 6, 7 },
                                            { 8, 9 } }), new char[]
                                        { 'A', 'b', 'C' }),
                            new Record(3, 3.0f, 100000000L, 5.0, (short) 6, true, "two",
                                    new HDF5EnumerationValue(enumType, "Two"), new int[]
                                        { 7, 8, 9 }, new float[]
                                        { 8.0f, -17.0f }, new long[]
                                        { -10, -11, -12 }, new double[]
                                        { 3.14159 }, new short[]
                                        { 1000, 2000 }, new byte[]
                                        { 11, 12, 13, 14 }, new MDIntArray(new int[][]
                                        {
                                            { 6, 7 },
                                            { 8, 9 } }), new char[]
                                        { 'A', 'b', 'C' }),
                            new Record(4, 4.0f, 100000000L, 6.0, (short) 7, false, "two",
                                    new HDF5EnumerationValue(enumType, "Two"), new int[]
                                        { 10, 11, 12 }, new float[]
                                        { 8.0f, -17.0f }, new long[]
                                        { -10, -11, -12 }, new double[]
                                        { 3.14159 }, new short[]
                                        { 1000, 2000 }, new byte[]
                                        { 11, 12, 13, 14 }, new MDIntArray(new int[][]
                                        {
                                            { 6, 7 },
                                            { 8, 9 } }), new char[]
                                        { 'A', 'b', 'C' }), };
        final MDArray<Record> mdArrayWritten = new MDArray<Record>(arrayWritten, new int[]
            { 2, 2 });
        writer.compound().writeMDArray("/testCompound", compoundType, mdArrayWritten);
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(file);
        compoundType = Record.getHDF5Type(reader);
        final MDArray<Record> mdArrayRead =
                reader.compound().readMDArray("/testCompound", compoundType);
        assertEquals(mdArrayWritten, mdArrayRead);
        reader.close();
    }

    @Test
    public void testCompoundMDArrayBlockWise()
    {
        final File file = new File(workingDirectory, "compoundMDArrayBlockWise.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(file);
        HDF5CompoundType<Record> compoundType = Record.getHDF5Type(writer);
        HDF5EnumerationType enumType = writer.enumeration().getType("someEnumType");
        writer.compound().createMDArray("/testCompound", compoundType, new long[]
            { 2, 2 }, new int[]
            { 2, 1 });
        final Record[] arrayWritten1 =
                new Record[]
                    {
                            new Record(1, 2.0f, 100000000L, 3.0, (short) 4, true, "one",
                                    new HDF5EnumerationValue(enumType, "THREE"), new int[]
                                        { 1, 2, 3 }, new float[]
                                        { 8.0f, -17.0f }, new long[]
                                        { -10, -11, -12 }, new double[]
                                        { 3.14159 }, new short[]
                                        { 1000, 2000 }, new byte[]
                                        { 11, 12, 13, 14 }, new MDIntArray(new int[][]
                                        {
                                            { 6, 7 },
                                            { 8, 9 } }), new char[]
                                        { 'A', 'b', 'C' }),
                            new Record(2, 3.0f, 100000000L, 4.0, (short) 5, false, "two",
                                    new HDF5EnumerationValue(enumType, "1"), new int[]
                                        { 2, 3, 4 }, new float[]
                                        { 8.1f, -17.1f }, new long[]
                                        { -10, -13, -12 }, new double[]
                                        { 3.1415 }, new short[]
                                        { 1000, 2001 }, new byte[]
                                        { 11, 12, 13, 17 }, new MDIntArray(new int[][]
                                        {
                                            { 6, 7 },
                                            { 8, 9 } }), new char[]
                                        { 'A', 'b', 'C' }), };
        final Record[] arrayWritten2 =
                new Record[]
                    {
                            new Record(3, 3.0f, 100000000L, 5.0, (short) 6, true, "two",
                                    new HDF5EnumerationValue(enumType, "Two"), new int[]
                                        { 3, 4, 5 }, new float[]
                                        { 8.0f, -17.0f }, new long[]
                                        { -10, -11, -12 }, new double[]
                                        { 3.14159 }, new short[]
                                        { 1000, 2000 }, new byte[]
                                        { 11, 12, 13, 14 }, new MDIntArray(new int[][]
                                        {
                                            { 6, 7 },
                                            { 8, 9 } }), new char[]
                                        { 'A', 'b', 'C' }),
                            new Record(4, 4.0f, 100000000L, 6.0, (short) 7, false, "two",
                                    new HDF5EnumerationValue(enumType, "Two"), new int[]
                                        { 4, 5, 6 }, new float[]
                                        { 8.0f, -17.0f }, new long[]
                                        { -10, -11, -12 }, new double[]
                                        { 3.14159 }, new short[]
                                        { 1000, 2000 }, new byte[]
                                        { 11, 12, 13, 14 }, new MDIntArray(new int[][]
                                        {
                                            { 6, 7 },
                                            { 8, 9 } }), new char[]
                                        { 'A', 'b', 'C' }), };
        final MDArray<Record> mdArrayWritten1 = new MDArray<Record>(arrayWritten1, new int[]
            { 2, 1 });
        final MDArray<Record> mdArrayWritten2 = new MDArray<Record>(arrayWritten2, new int[]
            { 2, 1 });
        writer.compound().writeMDArrayBlock("/testCompound", compoundType, mdArrayWritten1,
                new long[]
                    { 0, 0 });
        writer.compound().writeMDArrayBlock("/testCompound", compoundType, mdArrayWritten2,
                new long[]
                    { 0, 1 });
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(file);
        compoundType = Record.getHDF5Type(reader);
        final MDArray<Record> mdArrayRead1 =
                reader.compound().readMDArrayBlock("/testCompound", compoundType, new int[]
                    { 2, 1 }, new long[]
                    { 0, 0 });
        final MDArray<Record> mdArrayRead2 =
                reader.compound().readMDArrayBlock("/testCompound", compoundType, new int[]
                    { 2, 1 }, new long[]
                    { 0, 1 });
        assertEquals(mdArrayWritten1, mdArrayRead1);
        assertEquals(mdArrayWritten2, mdArrayRead2);
        reader.close();
    }

    static class RecordA
    {
        int a;

        double b;

        RecordA(int a, float b)
        {
            this.a = a;
            this.b = b;
        }

        RecordA()
        {
        }

        static HDF5CompoundType<RecordA> getHDF5Type(IHDF5Reader reader)
        {
            return reader.compound().getType(RecordA.class, mapping("a"), mapping("b"));
        }
    }

    static class RecordB
    {
        float a;

        long b;

        RecordB(float a, int b)
        {
            this.a = a;
            this.b = b;
        }

        RecordB()
        {
        }

        static HDF5CompoundType<RecordB> getHDF5Type(IHDF5Reader reader)
        {
            return reader.compound().getType(RecordB.class, mapping("a"), mapping("b"));
        }
    }

    static class RecordC
    {
        float a;

        RecordC(float a)
        {
            this.a = a;
        }

        RecordC()
        {
        }

    }

    static class RecordD
    {
        @CompoundElement(memberName = "a")
        float b;

        RecordD(float b)
        {
            this.b = b;
        }

        RecordD()
        {
        }

    }

    static class RecordE
    {
        int a;

        RecordE(int a)
        {
            this.a = a;
        }

        RecordE()
        {
        }

    }

    static class MatrixElementRecord
    {
        int row;

        int col;

        MatrixElementRecord()
        {
        }

        MatrixElementRecord(int row, int col)
        {
            this.row = row;
            this.col = col;
        }

        boolean equals(int row, int col)
        {
            return this.row == row && this.col == col;
        }

        @Override
        public boolean equals(Object o)
        {
            if (o instanceof MatrixElementRecord == false)
            {
                return false;
            }
            final MatrixElementRecord m = (MatrixElementRecord) o;
            return equals(m.row, m.col);
        }

        @Override
        public String toString()
        {
            return "(" + row + "," + col + ")";
        }
    }

    @Test
    public void testIterateOverMDCompoundArrayInNaturalBlocks()
    {
        final File datasetFile =
                new File(workingDirectory, "iterateOverMDCompoundArrayInNaturalBlocks.h5");
        datasetFile.delete();
        assertFalse(datasetFile.exists());
        datasetFile.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(datasetFile);
        final String dsName = "ds";
        final HDF5CompoundType<MatrixElementRecord> typeW =
                writer.compound().getInferredType(MatrixElementRecord.class);
        assertEquals(HDF5Utils.getDataTypeGroup("") + "/Compound_MatrixElementRecord",
                typeW.tryGetDataTypePath());
        assertEquals("<MatrixElementRecord>COMPOUND(8)",
                typeW.getDataTypeInformation(HDF5DataTypeInformation.options().all()).toString());
        writer.compound().createMDArray(dsName, typeW, new long[]
            { 4, 4 }, new int[]
            { 2, 2 });
        writer.compound().writeMDArrayBlock(
                dsName,
                typeW,
                new MDArray<MatrixElementRecord>(new MatrixElementRecord[]
                    { new MatrixElementRecord(1, 1), new MatrixElementRecord(1, 2),
                            new MatrixElementRecord(2, 1), new MatrixElementRecord(2, 2) },
                        new int[]
                            { 2, 2 }), new long[]
                    { 0, 0 });
        writer.compound().writeMDArrayBlock(
                dsName,
                typeW,
                new MDArray<MatrixElementRecord>(new MatrixElementRecord[]
                    { new MatrixElementRecord(3, 1), new MatrixElementRecord(3, 2),
                            new MatrixElementRecord(4, 1), new MatrixElementRecord(4, 2) },
                        new int[]
                            { 2, 2 }), new long[]
                    { 1, 0 });
        writer.compound().writeMDArrayBlock(
                dsName,
                typeW,
                new MDArray<MatrixElementRecord>(new MatrixElementRecord[]
                    { new MatrixElementRecord(1, 3), new MatrixElementRecord(1, 4),
                            new MatrixElementRecord(2, 3), new MatrixElementRecord(2, 4) },
                        new int[]
                            { 2, 2 }), new long[]
                    { 0, 1 });
        writer.compound().writeMDArrayBlock(
                dsName,
                typeW,
                new MDArray<MatrixElementRecord>(new MatrixElementRecord[]
                    { new MatrixElementRecord(3, 3), new MatrixElementRecord(3, 4),
                            new MatrixElementRecord(4, 3), new MatrixElementRecord(4, 4) },
                        new int[]
                            { 2, 2 }), new long[]
                    { 1, 1 });
        writer.close();

        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(datasetFile);
        int i = 0;
        int j = 0;
        final HDF5CompoundType<MatrixElementRecord> typeR =
                reader.compound().getInferredType(MatrixElementRecord.class);
        for (HDF5MDDataBlock<MDArray<MatrixElementRecord>> block : reader.compound()
                .getMDArrayBlocks(dsName, typeR))
        {
            final String ij = new MatrixElementRecord(i, j).toString() + ": ";
            assertTrue(ij + Arrays.toString(block.getIndex()), Arrays.equals(new long[]
                { i, j }, block.getIndex()));
            assertTrue(ij + Arrays.toString(block.getData().dimensions()), Arrays.equals(new int[]
                { 2, 2 }, block.getData().dimensions()));
            assertTrue(ij + Arrays.toString(block.getData().getAsFlatArray()), Arrays.equals(
                    new MatrixElementRecord[]
                        { new MatrixElementRecord(1 + i * 2, 1 + j * 2),
                                new MatrixElementRecord(1 + i * 2, 2 + j * 2),
                                new MatrixElementRecord(2 + i * 2, 1 + j * 2),
                                new MatrixElementRecord(2 + i * 2, 2 + j * 2) }, block.getData()
                            .getAsFlatArray()));
            if (++j > 1)
            {
                j = 0;
                ++i;
            }
        }
        assertEquals(2, i);
        assertEquals(0, j);
        i = 0;
        j = 0;
        for (HDF5MDDataBlock<MDArray<MatrixElementRecord>> block : reader.compound()
                .getMDArrayBlocks(dsName, MatrixElementRecord.class))
        {
            final String ij = new MatrixElementRecord(i, j).toString() + ": ";
            assertTrue(ij + Arrays.toString(block.getIndex()), Arrays.equals(new long[]
                { i, j }, block.getIndex()));
            assertTrue(ij + Arrays.toString(block.getData().dimensions()), Arrays.equals(new int[]
                { 2, 2 }, block.getData().dimensions()));
            assertTrue(ij + Arrays.toString(block.getData().getAsFlatArray()), Arrays.equals(
                    new MatrixElementRecord[]
                        { new MatrixElementRecord(1 + i * 2, 1 + j * 2),
                                new MatrixElementRecord(1 + i * 2, 2 + j * 2),
                                new MatrixElementRecord(2 + i * 2, 1 + j * 2),
                                new MatrixElementRecord(2 + i * 2, 2 + j * 2) }, block.getData()
                            .getAsFlatArray()));
            if (++j > 1)
            {
                j = 0;
                ++i;
            }
        }
        reader.close();
    }

    @Test
    public void testConfusedCompound()
    {
        final File file = new File(workingDirectory, "confusedCompound.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(file);
        HDF5CompoundType<RecordA> compoundTypeInt = RecordA.getHDF5Type(writer);
        final RecordA recordWritten = new RecordA(17, 42.0f);
        writer.compound().write("/testCompound", compoundTypeInt, recordWritten);
        writer.close();
        final IHDF5Reader reader =
                HDF5FactoryProvider.get().configureForReading(file).performNumericConversions()
                        .reader();
        HDF5CompoundType<RecordB> compoundTypeFloat = RecordB.getHDF5Type(reader);
        try
        {
            reader.compound().read("/testCompound", compoundTypeFloat);
            fail("Unsuitable data set type not detected.");
        } catch (HDF5JavaException ex)
        {
            assertEquals(
                    "The compound type 'UNKNOWN' does not equal the compound type of data set '/testCompound'.",
                    ex.getMessage());
        }
        reader.close();
    }

    static class SimpleRecord
    {
        private float f;

        private int i;

        @CompoundElement(typeVariant = HDF5DataTypeVariant.TIME_DURATION_SECONDS)
        private short d;

        @CompoundElement(dimensions = 4)
        private String s;

        SimpleRecord()
        {
        }

        SimpleRecord(float f, int i, short d, String s)
        {
            this.f = f;
            this.i = i;
            this.d = d;
            this.s = s;
        }

        public float getF()
        {
            return f;
        }

        public int getI()
        {
            return i;
        }

        public short getD()
        {
            return d;
        }

        public String getS()
        {
            return s;
        }

        @Override
        public int hashCode()
        {
            final int prime = 31;
            int result = 1;
            result = prime * result + d;
            result = prime * result + Float.floatToIntBits(f);
            result = prime * result + i;
            result = prime * result + ((s == null) ? 0 : s.hashCode());
            return result;
        }

        @Override
        public boolean equals(Object obj)
        {
            if (this == obj)
            {
                return true;
            }
            if (obj == null)
            {
                return false;
            }
            if (getClass() != obj.getClass())
            {
                return false;
            }
            SimpleRecord other = (SimpleRecord) obj;
            if (d != other.d)
            {
                return false;
            }
            if (Float.floatToIntBits(f) != Float.floatToIntBits(other.f))
            {
                return false;
            }
            if (i != other.i)
            {
                return false;
            }
            if (s == null)
            {
                if (other.s != null)
                {
                    return false;
                }
            } else if (!s.equals(other.s))
            {
                return false;
            }
            return true;
        }

        @Override
        public String toString()
        {
            return "SimpleRecord [f=" + f + ", i=" + i + ", d=" + d + ", s=" + s + "]";
        }

    }

    static class SimpleInheretingRecord extends SimpleRecord
    {
        SimpleInheretingRecord()
        {
        }

        @CompoundElement(memberName = "ll", dimensions =
            { 2, 3 })
        private long[][] l;

        public SimpleInheretingRecord(float f, int i, short d, String s, long[][] l)
        {
            super(f, i, d, s);
            this.l = l;
        }

        public long[][] getL()
        {
            return l;
        }

        @Override
        public int hashCode()
        {
            final int prime = 31;
            int result = super.hashCode();
            result = prime * result + Arrays.hashCode(l);
            return result;
        }

        @Override
        public boolean equals(Object obj)
        {
            if (this == obj)
            {
                return true;
            }
            if (!super.equals(obj))
            {
                return false;
            }
            if (getClass() != obj.getClass())
            {
                return false;
            }
            SimpleInheretingRecord other = (SimpleInheretingRecord) obj;
            if (Objects.deepEquals(l, other.l) == false)
            {
                return false;
            }
            return true;
        }

        @Override
        public String toString()
        {
            return "SimpleInheretingRecord [l=" + ArrayUtils.toString(l) + ", getF()=" + getF()
                    + ", getI()=" + getI() + ", getD()=" + getD() + ", getS()=" + getS() + "]";
        }
    }

    static class SimpleInheretingRecord2 extends SimpleRecord
    {
        SimpleInheretingRecord2()
        {
        }

        private long[][] ll;

        public SimpleInheretingRecord2(float f, int i, short d, String s, long[][] l)
        {
            super(f, i, d, s);
            this.ll = l;
        }

        public long[][] getL()
        {
            return ll;
        }

        @Override
        public int hashCode()
        {
            final int prime = 31;
            int result = super.hashCode();
            result = prime * result + Arrays.hashCode(ll);
            return result;
        }

        @Override
        public boolean equals(Object obj)
        {
            if (this == obj)
            {
                return true;
            }
            if (!super.equals(obj))
            {
                return false;
            }
            if (getClass() != obj.getClass())
            {
                return false;
            }
            SimpleInheretingRecord2 other = (SimpleInheretingRecord2) obj;
            if (Objects.deepEquals(ll, other.ll) == false)
            {
                return false;
            }
            return true;
        }

        @Override
        public String toString()
        {
            return "SimpleInheretingRecord2 [l=" + ArrayUtils.toString(ll) + ", getF()=" + getF()
                    + ", getI()=" + getI() + ", getD()=" + getD() + ", getS()=" + getS() + "]";
        }
    }

    static class SimpleInheretingRecord3 extends SimpleRecord
    {
        SimpleInheretingRecord3()
        {
        }

        private MDLongArray ll;

        public SimpleInheretingRecord3(float f, int i, short d, String s, long[][] l)
        {
            super(f, i, d, s);
            this.ll = new MDLongArray(l);
        }

        public MDLongArray getL()
        {
            return ll;
        }

        @Override
        public int hashCode()
        {
            final int prime = 31;
            int result = super.hashCode();
            result = prime * result + ll.hashCode();
            return result;
        }

        @Override
        public boolean equals(Object obj)
        {
            if (this == obj)
            {
                return true;
            }
            if (!super.equals(obj))
            {
                return false;
            }
            if (getClass() != obj.getClass())
            {
                return false;
            }
            SimpleInheretingRecord3 other = (SimpleInheretingRecord3) obj;
            if (ll.equals(other.ll) == false)
            {
                return false;
            }
            return true;
        }

        @Override
        public String toString()
        {
            return "SimpleInheretingRecord3 [l=" + ll + ", getF()=" + getF() + ", getI()=" + getI()
                    + ", getD()=" + getD() + ", getS()=" + getS() + "]";
        }
    }

    enum FruitEnum
    {
        APPLE, ORANGE, CHERRY
    }

    enum ColorEnum
    {
        RED, GEEN, BLUE, BLACK
    }

    enum StateEnum
    {
        PREPARING, READY, ONGOING, DONE
    }

    static class JavaEnumCompoundType
    {
        FruitEnum fruit;

        JavaEnumCompoundType()
        {
        }

        JavaEnumCompoundType(FruitEnum fruit)
        {
            this.fruit = fruit;
        }

        @Override
        public int hashCode()
        {
            final int prime = 31;
            int result = 1;
            result = prime * result + ((fruit == null) ? 0 : fruit.hashCode());
            return result;
        }

        @Override
        public boolean equals(Object obj)
        {
            if (this == obj)
            {
                return true;
            }
            if (obj == null)
            {
                return false;
            }
            if (getClass() != obj.getClass())
            {
                return false;
            }
            JavaEnumCompoundType other = (JavaEnumCompoundType) obj;
            if (fruit != other.fruit)
            {
                return false;
            }
            return true;
        }
    }

    static class JavaMultipleEnumsCompoundType
    {
        int i; // Will be ignored, just to be sure a non-enum member doesn't hurt.

        FruitEnum fruit;

        ColorEnum color;

        StateEnum state;

        JavaMultipleEnumsCompoundType()
        {
        }

        JavaMultipleEnumsCompoundType(FruitEnum fruit, ColorEnum color, StateEnum state)
        {
            this.fruit = fruit;
            this.color = color;
            this.state = state;
        }

        @Override
        public int hashCode()
        {
            final int prime = 31;
            int result = 1;
            result = prime * result + ((color == null) ? 0 : color.hashCode());
            result = prime * result + ((fruit == null) ? 0 : fruit.hashCode());
            result = prime * result + ((state == null) ? 0 : state.hashCode());
            return result;
        }

        @Override
        public boolean equals(Object obj)
        {
            if (this == obj)
            {
                return true;
            }
            if (obj == null)
            {
                return false;
            }
            if (getClass() != obj.getClass())
            {
                return false;
            }
            JavaMultipleEnumsCompoundType other = (JavaMultipleEnumsCompoundType) obj;
            if (color != other.color)
            {
                return false;
            }
            if (fruit != other.fruit)
            {
                return false;
            }
            if (state != other.state)
            {
                return false;
            }
            return true;
        }
    }

    @Test
    public void testInferredCompoundType()
    {
        final File file = new File(workingDirectory, "inferredCompoundType.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(file);
        final HDF5CompoundType<SimpleRecord> typeW =
                writer.compound().getInferredType(SimpleRecord.class);
        writer.compound().write("sc", typeW, new SimpleRecord(2.2f, 17, (short) 10, "test"));
        long[][] arrayWritten = new long[][]
            {
                { 1, 2, 3 },
                { 4, 5, 6 } };
        final HDF5CompoundType<SimpleInheretingRecord> itype =
                writer.compound().getInferredType(SimpleInheretingRecord.class);
        writer.compound().write("sci", itype,
                new SimpleInheretingRecord(-3.1f, 42, (short) 17, "some", arrayWritten));
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().configureForReading(file).reader();
        final HDF5CompoundType<SimpleRecord> typeR =
                reader.compound().getInferredType(SimpleRecord.class);
        final SimpleRecord recordRead = reader.compound().read("sc", typeR);
        final HDF5CompoundType<SimpleInheretingRecord> inheritedTypeR =
                reader.compound().getInferredType(SimpleInheretingRecord.class);
        final SimpleInheretingRecord recordInheritedRead =
                reader.compound().read("sci", inheritedTypeR);
        final HDF5CompoundMemberInformation[] info =
                reader.compound().getMemberInfo(SimpleRecord.class);
        assertEquals("d", info[2].getName());
        assertEquals(HDF5DataTypeVariant.TIME_DURATION_SECONDS, info[2].getType()
                .tryGetTypeVariant());
        reader.close();

        assertEquals(2.2f, recordRead.getF());
        assertEquals(17, recordRead.getI());
        assertEquals("test", recordRead.getS());

        assertEquals(-3.1f, recordInheritedRead.getF());
        assertEquals(42, recordInheritedRead.getI());
        assertEquals("some", recordInheritedRead.getS());
        assertTrue(equals(arrayWritten, recordInheritedRead.getL()));
    }

    static class CompleteMappedCompound
    {
        @CompoundElement
        float a;

        @CompoundElement
        int b;

        @CompoundElement(variableLength = true)
        String c;

        public CompleteMappedCompound()
        {
        }

        public CompleteMappedCompound(float a, int b, String c)
        {
            this.a = a;
            this.b = b;
            this.c = c;
        }

    }

    @CompoundType(mapAllFields = false)
    static class IncompleteMappedCompound
    {
        @CompoundElement
        float a;

        @CompoundElement
        int b;

        // unmapped
        String c;

        public IncompleteMappedCompound()
        {
        }

        public IncompleteMappedCompound(float a, int b, String c)
        {
            this.a = a;
            this.b = b;
            this.c = c;
        }

    }

    @Test
    public void testInferredIncompletelyMappedCompoundType()
    {
        final File file = new File(workingDirectory, "inferredIncompletelyMappedCompoundType.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(file);
        writer.compound().write("cpd", new CompleteMappedCompound(-1.111f, 11, "Not mapped"));
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().configureForReading(file).reader();
        final IncompleteMappedCompound cpd =
                reader.compound().read("cpd", IncompleteMappedCompound.class);
        final HDF5CompoundType<IncompleteMappedCompound> type =
                reader.compound().getType("incomplete_mapped_compound",
                        IncompleteMappedCompound.class, false,
                        HDF5CompoundMemberMapping.inferMapping(IncompleteMappedCompound.class));
        final IncompleteMappedCompound cpd2 = reader.compound().read("cpd", type);
        reader.close();
        assertEquals(-1.111f, cpd.a);
        assertEquals(11, cpd.b);
        assertEquals("Not mapped", cpd.c);
        assertEquals(-1.111f, cpd2.a);
        assertEquals(11, cpd2.b);
        assertNull(cpd2.c);
    }

    @Test
    public void testNameChangeInCompoundMapping()
    {
        final File file = new File(workingDirectory, "nameChangeInCompoundMapping.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(file);
        final String typeName = "a_float";
        HDF5CompoundType<RecordC> compoundTypeInt =
                writer.compound().getInferredType(typeName, RecordC.class);
        final RecordC recordWritten = new RecordC(33.33333f);
        writer.compound().write("/testCompound", compoundTypeInt, recordWritten);
        writer.close();
        final IHDF5Reader reader =
                HDF5FactoryProvider.get().configureForReading(file).performNumericConversions()
                        .reader();
        HDF5CompoundType<RecordD> compoundTypeFloat =
                reader.compound().getNamedType(typeName, RecordD.class);
        final RecordD recordRead = reader.compound().read("/testCompound", compoundTypeFloat);
        assertEquals(recordWritten.a, recordRead.b);
        reader.close();
    }

    @Test
    public void testOverwriteCompound()
    {
        final File file = new File(workingDirectory, "overwriteCompound.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        IHDF5Writer writer = HDF5FactoryProvider.get().open(file);
        HDF5CompoundType<RecordC> compoundTypeFloat =
                writer.compound().getInferredType(RecordC.class);
        final RecordC recordWritten = new RecordC(33.33333f);
        writer.compound().write("/testCompound", compoundTypeFloat, recordWritten);
        writer.close();
        writer = HDF5FactoryProvider.get().open(file);
        final RecordE recordWritten2 = new RecordE(-1);
        HDF5CompoundType<RecordE> compoundTypeInt =
                writer.compound().getInferredType(RecordE.class);
        writer.compound().write("/testCompound", compoundTypeInt, recordWritten2);
        writer.close();

        final IHDF5Reader reader =
                HDF5FactoryProvider.get().configureForReading(file).performNumericConversions()
                        .reader();
        HDF5CompoundType<RecordE> compoundTypeInt2 = reader.compound().getNamedType(RecordE.class);
        assertEquals(1, compoundTypeInt2.getCompoundMemberInformation().length);
        assertEquals(HDF5DataClass.INTEGER, compoundTypeInt2.getCompoundMemberInformation()[0]
                .getType().getDataClass());
        assertEquals(-1, reader.compound().read("/testCompound", compoundTypeInt2).a);
        reader.close();
    }

    @Test
    public void testOverwriteCompoundKeepType()
    {
        final File file = new File(workingDirectory, "overwriteCompoundKeepType.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        IHDF5Writer writer = HDF5FactoryProvider.get().open(file);
        HDF5CompoundType<RecordC> compoundTypeFloat =
                writer.compound().getInferredType(RecordC.class);
        final RecordC recordWritten = new RecordC(33.33333f);
        writer.compound().write("/testCompound", compoundTypeFloat, recordWritten);
        writer.close();
        writer = HDF5FactoryProvider.get().configure(file).keepDataSetsIfTheyExist().writer();
        final RecordE recordWritten2 = new RecordE(-1);
        HDF5CompoundType<RecordE> compoundTypeInt =
                writer.compound().getInferredType(RecordE.class);
        writer.compound().write("/testCompound", compoundTypeInt, recordWritten2);
        writer.close();

        final IHDF5Reader reader =
                HDF5FactoryProvider.get().configureForReading(file).performNumericConversions()
                        .reader();
        HDF5CompoundType<RecordE> compoundTypeInt2 =
                reader.compound().getDataSetType("/testCompound", RecordE.class);
        assertEquals(1, compoundTypeInt2.getCompoundMemberInformation().length);
        assertEquals(HDF5DataClass.FLOAT, compoundTypeInt2.getCompoundMemberInformation()[0]
                .getType().getDataClass());
        assertEquals(-1, reader.compound().read("/testCompound", compoundTypeInt2).a);
        reader.close();
    }

    static class SimpleRecordWithEnum
    {
        HDF5EnumerationValue e;

        SimpleRecordWithEnum()
        {
        }

        public SimpleRecordWithEnum(HDF5EnumerationValue e)
        {
            this.e = e;
        }
    }

    @Test
    public void testInferredCompoundTypedWithEnum()
    {
        final File file = new File(workingDirectory, "inferredCompoundTypeWithEnum.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final String[] alternatives = new String[257];
        for (int i = 0; i < alternatives.length; ++i)
        {
            alternatives[i] = Integer.toString(i);
        }
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(file);
        final HDF5EnumerationType enumType = writer.enumeration().getType("type", alternatives);
        final SimpleRecordWithEnum r =
                new SimpleRecordWithEnum(new HDF5EnumerationValue(enumType, "3"));
        final HDF5CompoundType<SimpleRecordWithEnum> typeW = writer.compound().getInferredType(r);
        writer.compound().write("sce", typeW, r);
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().configureForReading(file).reader();
        final HDF5CompoundType<SimpleRecordWithEnum> typeR =
                reader.compound().getNamedType(SimpleRecordWithEnum.class);
        final SimpleRecordWithEnum recordRead = reader.compound().read("sce", typeR);
        assertEquals("3", recordRead.e.getValue());
        reader.close();

    }

    static class SimpleRecordWithEnumArray
    {
        @CompoundElement(dimensions = 5)
        HDF5EnumerationValueArray e;

        SimpleRecordWithEnumArray()
        {
        }

        public SimpleRecordWithEnumArray(HDF5EnumerationValueArray e)
        {
            this.e = e;
        }
    }

    @Test
    public void testInferredCompoundTypeWithEnumArray()
    {
        final File file = new File(workingDirectory, "inferredCompoundTypeWithEnumArray.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(file);
        final String[] alternatives = new String[512];
        for (int i = 0; i < alternatives.length; ++i)
        {
            alternatives[i] = Integer.toString(i);
        }
        final HDF5EnumerationType enumType = writer.enumeration().getType("type", alternatives);
        final SimpleRecordWithEnumArray r =
                new SimpleRecordWithEnumArray(new HDF5EnumerationValueArray(enumType, new String[]
                    { "3", "2", "1", "511", "3" }));
        final HDF5CompoundType<SimpleRecordWithEnumArray> cType =
                writer.compound().getInferredType(r);
        writer.compound().write("sce", cType, r);
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().configureForReading(file).reader();
        final HDF5CompoundType<SimpleRecordWithEnumArray> typeR =
                reader.compound().getNamedType(SimpleRecordWithEnumArray.class);
        final SimpleRecordWithEnumArray recordRead = reader.compound().read("sce", typeR);
        reader.close();

        assertEquals(5, recordRead.e.getLength());
        assertEquals("3", recordRead.e.getValue(0));
        assertEquals("2", recordRead.e.getValue(1));
        assertEquals("1", recordRead.e.getValue(2));
        assertEquals("511", recordRead.e.getValue(3));
        assertEquals("3", recordRead.e.getValue(4));
    }

    static class RecordWithMatrix
    {
        String s;

        MDFloatArray fm;

        public RecordWithMatrix()
        {
        }

        RecordWithMatrix(String s, MDFloatArray fm)
        {
            this.s = s;
            this.fm = fm;
        }

        static HDF5CompoundType<RecordWithMatrix> getHDF5Type(IHDF5Reader reader)
        {
            return reader.compound().getType(null, RecordWithMatrix.class, getMapping());
        }

        private static HDF5CompoundMemberMapping[] getMapping()
        {
            return new HDF5CompoundMemberMapping[]
                { mapping("s").length(5), mapping("fm").dimensions(2, 2) };
        }

    }

    @Test
    public void testMDArrayCompound()
    {
        final File file = new File(workingDirectory, "mdArrayCompound.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(file);
        HDF5CompoundType<RecordWithMatrix> compoundTypeMatrix =
                RecordWithMatrix.getHDF5Type(writer);
        final RecordWithMatrix recordWritten =
                new RecordWithMatrix("tag", new MDFloatArray(new float[][]
                    {
                        { 1, 2 },
                        { 3, 4 } }));
        writer.compound().write("/testCompound", compoundTypeMatrix, recordWritten);
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(file);
        HDF5CompoundType<RecordWithMatrix> compoundTypeMatrixRead =
                RecordWithMatrix.getHDF5Type(reader);
        final RecordWithMatrix recordRead =
                reader.compound().read("/testCompound", compoundTypeMatrixRead);
        assertEquals(recordWritten.s, recordRead.s);
        assertEquals(recordWritten.fm, recordRead.fm);
        reader.close();
    }

    @Test
    public void testMDArrayCompoundArray()
    {
        final File file = new File(workingDirectory, "mdArrayCompoundArray.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(file);
        HDF5CompoundType<RecordWithMatrix> compoundTypeMatrix =
                RecordWithMatrix.getHDF5Type(writer);
        final RecordWithMatrix[] recordArrayWritten = new RecordWithMatrix[]
            { new RecordWithMatrix("tag1", new MDFloatArray(new float[][]
                {
                    { 1, 2 },
                    { 3, 4 } })), new RecordWithMatrix("tag2", new MDFloatArray(new float[][]
                {
                    { 10, 20 },
                    { 30, 40 } })), new RecordWithMatrix("tag3", new MDFloatArray(new float[][]
                {
                    { 100, 200 },
                    { 300, 400 } })), };
        writer.compound().writeArray("/testCompoundArray", compoundTypeMatrix, recordArrayWritten);
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(file);
        HDF5CompoundType<RecordWithMatrix> compoundTypeMatrixRead =
                RecordWithMatrix.getHDF5Type(reader);
        final RecordWithMatrix[] recordReadArray =
                reader.compound().readArray("/testCompoundArray", compoundTypeMatrixRead);
        assertEquals(3, recordReadArray.length);
        for (int i = 0; i < recordArrayWritten.length; ++i)
        {
            assertEquals("" + i, recordArrayWritten[i].s, recordReadArray[i].s);
            assertEquals("" + i, recordArrayWritten[i].fm, recordReadArray[i].fm);
        }
        reader.close();
    }

    @Test
    public void testSetDataSetSize()
    {
        final File file = new File(workingDirectory, "testSetDataSetSize.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(file);
        writer.int8().createArray("ds", 0, 10);
        writer.object().setDataSetSize("ds", 20);
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(file);
        final HDF5DataSetInformation dsInfo = reader.getDataSetInformation("ds");
        assertEquals(20, dsInfo.getSize());
        assertTrue(dsInfo.isSigned());
        int idx = 0;
        for (byte b : reader.int8().readArray("ds"))
        {
            assertEquals("Position " + (idx++), 0, b);
        }
        reader.close();
    }

    @Test
    public void testNumericConversion()
    {
        final File file = new File(workingDirectory, "numericConversions.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(file);
        writer.float32().write("pi", 3.14159f);
        writer.float32().write("INFINITY", Float.POSITIVE_INFINITY);
        writer.float64().write("DINFINITY", Double.NEGATIVE_INFINITY);
        writer.float64().write("verySmallFloat", 1e-100);
        writer.float64().write("veryLargeFloat", 1e+100);
        writer.float64().setAttr("pi", "eps", 1e-5);
        writer.int64().write("smallInteger", 17L);
        writer.int64().write("largeInteger", Long.MAX_VALUE);
        writer.close();
        final IHDF5ReaderConfigurator config =
                HDF5FactoryProvider.get().configureForReading(file).performNumericConversions();
        // If this platform doesn't support numeric conversions, the test would fail.
        if (config.platformSupportsNumericConversions() == false)
        {
            return;
        }
        final IHDF5Reader reader = config.reader();
        assertEquals(3.14159, reader.float64().read("pi"), 1e-5);
        assertEquals(3, reader.int32().read("pi"));
        assertEquals(1e-5f, reader.float32().getAttr("pi", "eps"), 1e-9);
        assertEquals(17, reader.int8().read("smallInteger"));
        assertEquals(0.0f, reader.float32().read("verySmallFloat"));
        assertEquals(Double.POSITIVE_INFINITY, reader.float64().read("INFINITY"));
        try
        {
            reader.int32().read("largeInteger");
            fail("Failed to detect overflow");
        } catch (HDF5DatatypeInterfaceException ex)
        {
            assertEquals(HDF5Constants.H5E_CANTCONVERT, ex.getMinorErrorNumber());
        }
        try
        {
            reader.float32().read("veryLargeFloat");
            fail("Failed to detect overflow");
        } catch (HDF5DatatypeInterfaceException ex)
        {
            assertEquals(HDF5Constants.H5E_CANTCONVERT, ex.getMinorErrorNumber());
        }
        try
        {
            reader.int64().read("veryLargeFloat");
            fail("Failed to detect overflow");
        } catch (HDF5DatatypeInterfaceException ex)
        {
            assertEquals(HDF5Constants.H5E_CANTCONVERT, ex.getMinorErrorNumber());
        }
        // In HDF5 up to 1.8.10, numeric conversions on sparc don't detect overflows
        // for INFINITY and DINFINITY values.
        if (OSUtilities.getCPUArchitecture().startsWith("sparc"))
        {
            return;
        }
        try
        {
            reader.float32().read("DINFINITY");
            fail("Failed to detect overflow");
        } catch (HDF5DatatypeInterfaceException ex)
        {
            assertEquals(HDF5Constants.H5E_CANTCONVERT, ex.getMinorErrorNumber());
        }
        try
        {
            reader.int64().read("INFINITY");
            fail("Failed to detect overflow");
        } catch (HDF5DatatypeInterfaceException ex)
        {
            assertEquals(HDF5Constants.H5E_CANTCONVERT, ex.getMinorErrorNumber());
        }
        reader.close();
    }

    @Test
    public void testNumericConversionWithNumericConversionsSwitchedOff()
    {
        final File file =
                new File(workingDirectory, "numericConversionWithNumericConversionsSwitchedOff.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(file);
        writer.float32().write("pi", 3.14159f);
        writer.float32().write("one", 1.0f);
        writer.float32().write("INFINITY", Float.POSITIVE_INFINITY);
        writer.float64().write("DINFINITY", Double.NEGATIVE_INFINITY);
        writer.float64().write("verySmallFloat", 1e-100);
        writer.float64().write("veryLargeFloat", 1e+100);
        writer.float64().setAttr("pi", "eps", 1e-5);
        writer.int64().write("smallInteger", 17L);
        writer.int64().write("largeInteger", Long.MAX_VALUE);
        writer.close();
        final IHDF5Reader reader = HDF5Factory.openForReading(file);
        // <<< Don't try this at home - it is not clean: START
        assertEquals(3.14159, reader.float64().read("pi"), 1e-5);
        // SPARC CPUs need numeric conversion to be switched on for this to work.
        if (OSUtilities.getCPUArchitecture().startsWith("sparc") == false)
        {
            assertEquals(1, reader.int32().read("one"));
            assertEquals(Double.POSITIVE_INFINITY, reader.float64().read("INFINITY"));
        }
        assertEquals(1e-5f, reader.float32().getAttr("pi", "eps"), 1e-9);
        assertEquals(17, reader.int8().read("smallInteger"));
        assertEquals(0.0f, reader.float32().read("verySmallFloat"));
        // Don't try this at home - it is not clean: END >>>
        try
        {
            reader.int32().read("largeInteger");
            fail("Failed to detect overflow");
        } catch (HDF5DatatypeInterfaceException ex)
        {
            assertEquals(HDF5Constants.H5E_CANTCONVERT, ex.getMinorErrorNumber());
        }
        try
        {
            reader.float32().read("veryLargeFloat");
            fail("Failed to detect overflow");
        } catch (HDF5DatatypeInterfaceException ex)
        {
            assertEquals(HDF5Constants.H5E_CANTCONVERT, ex.getMinorErrorNumber());
        }
        try
        {
            reader.int64().read("veryLargeFloat");
            fail("Failed to detect overflow");
        } catch (HDF5DatatypeInterfaceException ex)
        {
            assertEquals(HDF5Constants.H5E_CANTCONVERT, ex.getMinorErrorNumber());
        }
        // In HDF5 up to 1.8.10, numeric conversions on sparc don't detect overflows
        // for INFINITY and DINFINITY values.
        if (OSUtilities.getCPUArchitecture().startsWith("sparc"))
        {
            return;
        }
        try
        {
            reader.float32().read("DINFINITY");
            fail("Failed to detect overflow");
        } catch (HDF5DatatypeInterfaceException ex)
        {
            assertEquals(HDF5Constants.H5E_CANTCONVERT, ex.getMinorErrorNumber());
        }
        try
        {
            reader.int64().read("INFINITY");
            fail("Failed to detect overflow");
        } catch (HDF5DatatypeInterfaceException ex)
        {
            assertEquals(HDF5Constants.H5E_CANTCONVERT, ex.getMinorErrorNumber());
        }
        reader.close();
    }

    @Test
    public void testObjectReferenceOverwriteWithKeep()
    {
        final File file = new File(workingDirectory, "testObjectReferenceOverwriteWithKeep.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer writer =
                HDF5FactoryProvider.get().configure(file).keepDataSetsIfTheyExist().writer();
        writer.string().write("a", "TestA");
        writer.string().write("aa", "TestAA");
        writer.reference().write("b", "aa");
        writer.delete("a");
        // If keepDataSetsIfTheyExist() was not given above, the dataset would be deleted, the
        // header of the new dataset would be written at the old position of "a" and the object
        // reference "b" would be dangling.
        writer.string().write("aa", "TestX");
        assertEquals("/aa", writer.reference().read("/b"));
        writer.object().move("/aa", "/C");
        assertEquals("/C", writer.reference().read("/b"));
        writer.close();
    }

    @Test
    public void testObjectReference()
    {
        final File file = new File(workingDirectory, "testObjectReference.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(file);
        writer.string().write("a", "TestA");
        writer.reference().write("b", "a");
        assertEquals("/a", writer.reference().read("/b"));
        writer.object().move("/a", "/C");
        assertEquals("/C", writer.reference().read("/b"));
        assertEquals("TestA", writer.readString(writer.reference().read("/b", false)));
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(file);
        assertEquals("/C", reader.reference().read("/b"));
        assertEquals("TestA", reader.readString(reader.reference().read("/b", false)));
        reader.close();
    }

    @Test
    public void testObjectReferenceArray()
    {
        final File file = new File(workingDirectory, "testObjectReferenceArray.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(file);
        writer.string().write("a1", "TestA");
        writer.string().write("a2", "TestB");
        writer.string().write("a3", "TestC");
        writer.reference().writeArray("b", new String[]
            { "a1", "a2", "a3" });
        String[] arrayBVal = writer.reference().readArray("/b");
        assertTrue(Objects.deepEquals(new String[]
            { "/a1", "/a2", "/a3" }, arrayBVal));
        writer.object().move("/a1", "/C");
        arrayBVal = writer.reference().readArray("/b");
        assertTrue(Objects.deepEquals(new String[]
            { "/C", "/a2", "/a3" }, arrayBVal));
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(file);
        assertTrue(Objects.deepEquals(new String[]
            { "/C", "/a2", "/a3" }, reader.reference().readArray("/b")));
        final String[] refs = reader.reference().readArray("/b", false);
        assertEquals("TestA", reader.string().read(refs[0]));
        assertEquals("/C", reader.reference().resolvePath(refs[0]));
        assertEquals("TestB", reader.string().read(refs[1]));
        assertEquals("/a2", reader.reference().resolvePath(refs[1]));
        assertEquals("TestC", reader.string().read(refs[2]));
        assertEquals("/a3", reader.reference().resolvePath(refs[2]));
        reader.close();
    }

    @Test
    public void testObjectReferenceArrayBlockWise()
    {
        final File file = new File(workingDirectory, "testObjectReferenceArrayBlockWise.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(file);
        final String[] completeArray = new String[16];
        for (int i = 0; i < completeArray.length; ++i)
        {
            writer.string().write("a" + (i + 1), "TestA" + i);
            completeArray[i] = "/a" + (i + 1);
        }
        writer.reference().createArray("b", completeArray.length, completeArray.length / 4,
                HDF5IntStorageFeatures.INT_NO_COMPRESSION);
        final String[][] chunk = new String[4][4];
        System.arraycopy(completeArray, 0, chunk[0], 0, 4);
        System.arraycopy(completeArray, 4, chunk[1], 0, 4);
        System.arraycopy(completeArray, 8, chunk[2], 0, 4);
        System.arraycopy(completeArray, 12, chunk[3], 0, 4);
        writer.reference().writeArrayBlock("b", chunk[0], 0);
        writer.reference().writeArrayBlock("b", chunk[2], 2);
        writer.reference().writeArrayBlock("b", chunk[1], 1);
        writer.reference().writeArrayBlock("b", chunk[3], 3);
        assertTrue(Objects.deepEquals(completeArray, writer.reference().readArray("/b")));
        writer.object().move("/a1", "/C");
        completeArray[0] = "/C";
        chunk[0][0] = "/C";
        assertTrue(Objects.deepEquals(completeArray, writer.reference().readArray("/b")));
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(file);
        int idx = 0;
        for (HDF5DataBlock<String[]> block : reader.reference().getArrayNaturalBlocks("b"))
        {
            assertTrue("" + idx, Objects.deepEquals(chunk[idx++], block.getData()));
        }
        reader.close();
    }

    @Test
    public void testObjectReferenceMDArray()
    {
        final File file = new File(workingDirectory, "testObjectReferenceMDArray.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(file);
        writer.string().write("a1", "TestA");
        writer.string().write("a2", "TestA");
        writer.string().write("a3", "TestA");
        writer.string().write("a4", "TestA");
        writer.reference().writeMDArray("b", new MDArray<String>(new String[]
            { "a1", "a2", "a3", "a4" }, new int[]
            { 2, 2 }));
        assertEquals(new MDArray<String>(new String[]
            { "/a1", "/a2", "/a3", "/a4" }, new int[]
            { 2, 2 }), writer.reference().readMDArray("/b"));
        writer.object().move("/a1", "/C");
        assertEquals(new MDArray<String>(new String[]
            { "/C", "/a2", "/a3", "/a4" }, new int[]
            { 2, 2 }), writer.reference().readMDArray("/b"));
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(file);
        assertEquals(new MDArray<String>(new String[]
            { "/C", "/a2", "/a3", "/a4" }, new int[]
            { 2, 2 }), reader.reference().readMDArray("/b"));
        reader.close();
    }

    @Test
    public void testObjectReferenceMDArrayBlockWise()
    {
        final File file = new File(workingDirectory, "testObjectReferenceMDArrayBlockWise.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(file);
        final String[] completeArray = new String[16];
        for (int i = 0; i < completeArray.length; ++i)
        {
            writer.string().write("a" + (i + 1), "TestA" + i);
            completeArray[i] = "/a" + (i + 1);
        }
        writer.reference().createMDArray("b", new long[]
            { 4, 4 }, new int[]
            { 1, 4 }, HDF5IntStorageFeatures.INT_NO_COMPRESSION);
        final String[][] chunk = new String[4][4];
        System.arraycopy(completeArray, 0, chunk[0], 0, 4);
        System.arraycopy(completeArray, 4, chunk[1], 0, 4);
        System.arraycopy(completeArray, 8, chunk[2], 0, 4);
        System.arraycopy(completeArray, 12, chunk[3], 0, 4);
        writer.reference().writeMDArrayBlock("b", new MDArray<String>(chunk[0], new int[]
            { 1, 4 }), new long[]
            { 0, 0 });
        writer.reference().writeMDArrayBlock("b", new MDArray<String>(chunk[2], new int[]
            { 1, 4 }), new long[]
            { 2, 0 });
        writer.reference().writeMDArrayBlock("b", new MDArray<String>(chunk[1], new int[]
            { 1, 4 }), new long[]
            { 1, 0 });
        writer.reference().writeMDArrayBlock("b", new MDArray<String>(chunk[3], new int[]
            { 1, 4 }), new long[]
            { 3, 0 });
        assertEquals(new MDArray<String>(completeArray, new int[]
            { 4, 4 }), writer.reference().readMDArray("/b"));
        writer.object().move("/a1", "/C");
        completeArray[0] = "/C";
        chunk[0][0] = "/C";
        assertEquals(new MDArray<String>(completeArray, new int[]
            { 4, 4 }), writer.reference().readMDArray("/b"));
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(file);
        int idx = 0;
        for (HDF5MDDataBlock<MDArray<String>> block : reader.reference().getMDArrayNaturalBlocks(
                "b"))
        {
            assertEquals("" + idx, new MDArray<String>(chunk[idx++], new int[]
                { 1, 4 }), block.getData());
        }
        reader.close();
    }

    @Test
    public void testObjectReferenceToMDFloatArray()
    {
        final File file = new File(workingDirectory, "testObjectReferenceToMDFloatArray.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(file);
        final String floatDatasetName = "/floatMatrix";
        final String referenceDatasetName = "/refToFloatMatrix";
        final MDFloatArray arrayWritten = new MDFloatArray(new int[]
            { 3, 2, 4 });
        int count = 0;
        for (int i = 0; i < arrayWritten.size(0); ++i)
        {
            for (int j = 0; j < arrayWritten.size(1); ++j)
            {
                for (int k = 0; k < arrayWritten.size(2); ++k)
                {
                    arrayWritten.set(++count, new int[]
                        { i, j, k });
                }
            }
        }
        writer.float32().createMDArray(floatDatasetName, arrayWritten.dimensions());
        writer.reference().write(referenceDatasetName, floatDatasetName);
        writer.float32().writeMDArray(writer.reference().read(referenceDatasetName, false), arrayWritten);
        writer.close();

        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(file);
        final MDFloatArray arrayRead = reader.float32().readMDArray(floatDatasetName);
        assertEquals(arrayWritten, arrayRead);
        reader.close();
    }

    @Test
    public void testObjectReferenceAttribute()
    {
        final File file = new File(workingDirectory, "testObjectReferenceAttribute.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(file);
        writer.string().write("a", "TestA");
        writer.string().write("b", "TestB");
        writer.reference().setAttr("a", "partner", "b");
        assertEquals("/b", writer.reference().getAttr("/a", "partner"));
        writer.object().move("/b", "/C");
        assertEquals("/C", writer.reference().getAttr("/a", "partner"));
        writer.close();
    }

    @Test
    public void testObjectReferenceArrayAttribute()
    {
        final File file = new File(workingDirectory, "testObjectReferenceArrayAttribute.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(file);
        writer.string().write("a1", "TestA1");
        writer.string().write("a2", "TestA2");
        writer.string().write("a3", "TestA3");
        writer.string().write("b", "TestB");
        writer.reference().setArrayAttr("b", "partner", new String[]
            { "a1", "a2", "a3" });
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(file);
        final String[] referencesRead = reader.reference().getArrayAttr("b", "partner");
        assertEquals(3, referencesRead.length);
        assertEquals("/a1", referencesRead[0]);
        assertEquals("/a2", referencesRead[1]);
        assertEquals("/a3", referencesRead[2]);
        reader.close();
    }

    @Test
    public void testObjectReferenceMDArrayAttribute()
    {
        final File file = new File(workingDirectory, "testObjectReferenceMDArrayAttribute.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        final IHDF5Writer writer = HDF5FactoryProvider.get().open(file);
        writer.string().write("a1", "TestA1");
        writer.string().write("a2", "TestA2");
        writer.string().write("a3", "TestA3");
        writer.string().write("a4", "TestA4");
        writer.string().write("b", "TestB");
        writer.reference().setMDArrayAttr("b", "partner", new MDArray<String>(new String[]
            { "a1", "a2", "a3", "a4" }, new int[]
            { 2, 2 }));
        writer.close();
        final IHDF5Reader reader = HDF5FactoryProvider.get().openForReading(file);
        final MDArray<String> referencesRead = reader.reference().getMDArrayAttr("b", "partner");
        assertTrue(Objects.deepEquals(new int[]
            { 2, 2 }, referencesRead.dimensions()));
        assertEquals("/a1", referencesRead.get(0, 0));
        assertEquals("/a2", referencesRead.get(0, 1));
        assertEquals("/a3", referencesRead.get(1, 0));
        assertEquals("/a4", referencesRead.get(1, 1));
        reader.close();
    }

    @Test
    public void testHDF5FileDetection() throws IOException
    {
        final File hdf5File = new File(workingDirectory, "testHDF5FileDetection.h5");
        hdf5File.delete();
        assertFalse(hdf5File.exists());
        hdf5File.deleteOnExit();
        final IHDF5Writer writer = HDF5Factory.open(hdf5File);
        writer.string().write("a", "someString");
        writer.close();
        assertTrue(HDF5Factory.isHDF5File(hdf5File));

        final File noHdf5File = new File(workingDirectory, "testHDF5FileDetection.h5");
        noHdf5File.delete();
        assertFalse(noHdf5File.exists());
        noHdf5File.deleteOnExit();
        FileUtils.writeByteArrayToFile(noHdf5File, new byte[]
            { 1, 2, 3, 4 });
        assertFalse(HDF5Factory.isHDF5File(noHdf5File));
    }

    @Test
    public void testHDFJavaLowLevel()
    {
        final File file = new File(workingDirectory, "testHDFJavaLowLevel.h5");
        file.delete();
        assertFalse(file.exists());
        file.deleteOnExit();
        long fileId =
                hdf.hdf5lib.H5.H5Fcreate(file.getAbsolutePath(),
                        hdf.hdf5lib.HDF5Constants.H5F_ACC_TRUNC,
                        hdf.hdf5lib.HDF5Constants.H5P_DEFAULT,
                        hdf.hdf5lib.HDF5Constants.H5P_DEFAULT);
        long groupId =
                hdf.hdf5lib.H5.H5Gcreate(fileId, "constants",
                        hdf.hdf5lib.HDF5Constants.H5P_DEFAULT,
                        hdf.hdf5lib.HDF5Constants.H5P_DEFAULT,
                        hdf.hdf5lib.HDF5Constants.H5P_DEFAULT);
        long spcId =
                hdf.hdf5lib.H5.H5Screate(hdf.hdf5lib.HDF5Constants.H5S_SCALAR);
        long dsId =
                hdf.hdf5lib.H5.H5Dcreate(groupId, "pi",
                        hdf.hdf5lib.HDF5Constants.H5T_IEEE_F32LE, spcId,
                        hdf.hdf5lib.HDF5Constants.H5P_DEFAULT,
                        hdf.hdf5lib.HDF5Constants.H5P_DEFAULT,
                        hdf.hdf5lib.HDF5Constants.H5P_DEFAULT);
        hdf.hdf5lib.H5.H5Dwrite(dsId,
                hdf.hdf5lib.HDF5Constants.H5T_NATIVE_FLOAT,
                hdf.hdf5lib.HDF5Constants.H5S_SCALAR,
                hdf.hdf5lib.HDF5Constants.H5S_SCALAR,
                hdf.hdf5lib.HDF5Constants.H5P_DEFAULT, new float[]
                    { 3.14159f });
        hdf.hdf5lib.H5.H5Dclose(dsId);
        hdf.hdf5lib.H5.H5Sclose(spcId);
        hdf.hdf5lib.H5.H5Gclose(groupId);
        hdf.hdf5lib.H5.H5Fclose(fileId);

        fileId =
                hdf.hdf5lib.H5.H5Fopen(file.getAbsolutePath(),
                        hdf.hdf5lib.HDF5Constants.H5F_ACC_RDONLY,
                        hdf.hdf5lib.HDF5Constants.H5P_DEFAULT);
        spcId =
                hdf.hdf5lib.H5.H5Screate(hdf.hdf5lib.HDF5Constants.H5S_SCALAR);
        dsId =
                hdf.hdf5lib.H5.H5Dopen(fileId, "/constants/pi",
                        hdf.hdf5lib.HDF5Constants.H5P_DEFAULT);
        final float[] data = new float[1];
        hdf.hdf5lib.H5.H5Dread(dsId,
                hdf.hdf5lib.HDF5Constants.H5T_NATIVE_FLOAT,
                hdf.hdf5lib.HDF5Constants.H5S_ALL,
                hdf.hdf5lib.HDF5Constants.H5S_ALL,
                hdf.hdf5lib.HDF5Constants.H5P_DEFAULT, data);
        assertEquals(3.14159f, data[0], 0f);

        hdf.hdf5lib.H5.H5Dclose(dsId);
        hdf.hdf5lib.H5.H5Sclose(spcId);
        hdf.hdf5lib.H5.H5Fclose(fileId);
    }
    
    @Test
    public void testMDCImageGeneration()
    {
        final File hdf5File = new File(workingDirectory, "testMDCImageGeneration.h5");
        hdf5File.delete();
        assertFalse(hdf5File.exists());
        hdf5File.deleteOnExit();
        
        IHDF5Writer writer = HDF5Factory.configure(hdf5File).generateMDCImage().writer();
        assertFalse(writer.file().hasMDCImage());
        assertTrue(writer.file().isMDCImageGenerationEnabled());
        writer.writeIntArray("a", new int[] { 1, 10, 100, 1000});
        writer.close();

        IHDF5Reader reader = HDF5Factory.configureForReading(hdf5File).reader();
        assertTrue(reader.file().hasMDCImage());
        assertTrue(reader.file().isMDCImageGenerationEnabled());
        reader.close();

        writer = HDF5Factory.configure(hdf5File).keepMDCImage().writer();
        assertTrue(writer.file().hasMDCImage());
        assertTrue(writer.file().isMDCImageGenerationEnabled());
        writer.close();

        reader = HDF5Factory.configureForReading(hdf5File).reader();
        assertTrue(reader.file().hasMDCImage());
        assertTrue(reader.file().isMDCImageGenerationEnabled());
        reader.close();

        // Default is to keep an MDC.
        writer = HDF5Factory.configure(hdf5File).writer();
        assertTrue(writer.file().hasMDCImage());
        assertTrue(writer.file().isMDCImageGenerationEnabled());
        writer.close();

        writer = HDF5Factory.configure(hdf5File).generateMDCImage().writer();
        assertTrue(writer.file().hasMDCImage());
        assertTrue(writer.file().isMDCImageGenerationEnabled());
        writer.close();

        reader = HDF5Factory.configureForReading(hdf5File).reader();
        assertTrue(reader.file().hasMDCImage());
        assertTrue(reader.file().isMDCImageGenerationEnabled());
        reader.close();

        // Default is to keep an MDC.
        writer = HDF5Factory.configure(hdf5File).noGenerateMDCImage().writer();
        assertTrue(writer.file().hasMDCImage());
        assertFalse(writer.file().isMDCImageGenerationEnabled());
        writer.close();

        reader = HDF5Factory.configureForReading(hdf5File).reader();
        assertFalse(reader.file().hasMDCImage());
        assertFalse(reader.file().isMDCImageGenerationEnabled());
        reader.close();
    }

    @Test
    public void testMDCImageGenerationBug()
    {
        final File hdf5File = new File(workingDirectory, "testMDCImageGenerationBug.h5");
        hdf5File.delete();
        assertFalse(hdf5File.exists());
        hdf5File.deleteOnExit();
        
        IHDF5Writer writer = HDF5Factory.configure(hdf5File).writer();
        assertFalse(writer.file().hasMDCImage());
        assertFalse(writer.file().isMDCImageGenerationEnabled());
        writer.writeIntArray("a", new int[] { 1, 10, 100, 1000});
        writer.close();
        
        writer = HDF5Factory.configure(hdf5File).generateMDCImage().writer();
        assertFalse(writer.file().hasMDCImage());
        assertTrue(writer.file().isMDCImageGenerationEnabled());
        writer.close();
        
        // Note: this is not really expected but documents the behavior as of HDF5 1.10.3-pre1.
        assertFalse(HDF5Factory.hasMDCImage(hdf5File));
        
        // This would be the expected behavior as the default library version boundaries are (EARLIEST, LATEST)
        // and thus HDF5 should be able to generate an MDC image!
        /* assertTrue(HDF5Factory.hasMDCImage(hdf5File)); */
    }
}