+ * Like a dataset, an attribute has a name, datatype and dataspace. + * + *
+ * For more details on attributes, HDF5 + * User's Guide + *
+ * + * The following code is an example of an attribute with 1D integer array of two elements. + * + *
+ * // Example of creating a new attribute + * // The name of the new attribute + * String name = "Data range"; + * // Creating an unsigned 1-byte integer datatype + * Datatype type = new Datatype(Datatype.CLASS_INTEGER, // class + * 1, // size in bytes + * Datatype.ORDER_LE, // byte order + * Datatype.SIGN_NONE); // unsigned + * // 1-D array of size two + * long[] dims = {2}; + * // The value of the attribute + * int[] value = {0, 255}; + * // Create a new attribute + * Attribute dataRange = new Attribute(name, type, dims); + * // Set the attribute value + * dataRange.setValue(value); + * // See FileFormat.writeAttribute() for how to attach an attribute to an object, + * @see hdf.object.FileFormat#writeAttribute(HObject, Attribute, boolean) + *+ * + * + * For an atomic datatype, the value of an Attribute will be a 1D array of integers, floats and + * strings. For a compound datatype, it will be a 1D array of strings with field members separated + * by a comma. For example, "{0, 10.5}, {255, 20.0}, {512, 30.0}" is a compound attribute of {int, + * float} of three data points. + * + * @see hdf.object.Datatype + * + * @version 2.0 4/2/2018 + * @author Peter X. Cao, Jordan T. Henderson + */ +public class Attribute extends Dataset implements DataFormat, CompoundDataFormat { + + private static final long serialVersionUID = 2072473407027648309L; + + private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(Attribute.class); + + /** The HObject to which this Attribute is attached */ + protected HObject parentObject; + + /** additional information and properties for the attribute */ + private transient Map
+ * The nested names are separated by CompoundDS.SEPARATOR. For example, if + * compound attribute "A" has the following nested structure, + * + *
+ * A --> m01 + * A --> m02 + * A --> nest1 --> m11 + * A --> nest1 --> m12 + * A --> nest1 --> nest2 --> m21 + * A --> nest1 --> nest2 --> m22 + * i.e. + * A = { m01, m02, nest1{m11, m12, nest2{ m21, m22}}} + *+ * + * The flatNameList of compound attribute "A" will be {m01, m02, nest1[m11, + * nest1[m12, nest1[nest2[m21, nest1[nest2[m22} + * + */ + private List
+ * For example, a compound attribute COMP has members of A, B and C as + * + *
+ * COMP { + * int A; + * float B[5]; + * double C[2][3]; + * } + *+ * + * memberOrders is an integer array of {1, 5, 6} to indicate that member A has + * one element, member B has 5 elements, and member C has 6 elements. + */ + protected int[] memberOrders = null; + + /** + * The dimension sizes of each member. + *
+ * The i-th element of the Object[] is an integer array (int[]) that contains + * the dimension sizes of the i-th member. + */ + protected transient Object[] memberDims = null; + + /** + * The datatypes of the compound attribute's members. + */ + protected Datatype[] memberTypes = null; + + /** + * The array to store flags to indicate if a member of this compound attribute + * is selected for read/write. + *
+ * If a member is selected, the read/write will perform on the member. + * Applications such as HDFView will only display the selected members of the + * compound attribute. + * + *
+ * For example, if a compound attribute has four members + * String[] memberNames = {"X", "Y", "Z", "TIME"}; + * and + * boolean[] isMemberSelected = {true, false, false, true}; + * members "X" and "TIME" are selected for read and write. + *+ */ + protected boolean[] isMemberSelected = null; + + /** + * Create an attribute with specified name, data type and dimension sizes. + * + * For scalar attribute, the dimension size can be either an array of size one + * or null, and the rank can be either 1 or zero. Attribute is a general class + * and is independent of file format, e.g., the implementation of attribute + * applies to both HDF4 and HDF5. + *
+ * The following example creates a string attribute with the name "CLASS" and + * value "IMAGE". + * + *
+ * long[] attrDims = { 1 }; + * String attrName = "CLASS"; + * String[] classValue = { "IMAGE" }; + * Datatype attrType = null; + * try { + * attrType = new H5Datatype(Datatype.CLASS_STRING, classValue[0].length() + 1, Datatype.NATIVE, Datatype.NATIVE); + * } + * catch (Exception ex) {} + * Attribute attr = new Attribute(attrName, attrType, attrDims); + * attr.setValue(classValue); + *+ * + * @param parentObj + * the HObject to which this Attribute is attached. + * @param attrName + * the name of the attribute. + * @param attrType + * the datatype of the attribute. + * @param attrDims + * the dimension sizes of the attribute, null for scalar attribute + * + * @see hdf.object.Datatype + */ + public Attribute(HObject parentObj, String attrName, Datatype attrType, long[] attrDims) { + this(parentObj, attrName, attrType, attrDims, null); + } + + /** + * Create an attribute with specific name and value. + * + * For scalar attribute, the dimension size can be either an array of size one + * or null, and the rank can be either 1 or zero. Attribute is a general class + * and is independent of file format, e.g., the implementation of attribute + * applies to both HDF4 and HDF5. + *
+ * The following example creates a string attribute with the name "CLASS" and + * value "IMAGE". + * + *
+ * long[] attrDims = { 1 }; + * String attrName = "CLASS"; + * String[] classValue = { "IMAGE" }; + * Datatype attrType = null; + * try { + * attrType = new H5Datatype(Datatype.CLASS_STRING, classValue[0].length() + 1, Datatype.NATIVE, Datatype.NATIVE); + * } + * catch (Exception ex) {} + * Attribute attr = new Attribute(attrName, attrType, attrDims, classValue); + *+ * + * @param parentObj + * the HObject to which this Attribute is attached. + * @param attrName + * the name of the attribute. + * @param attrType + * the datatype of the attribute. + * @param attrDims + * the dimension sizes of the attribute, null for scalar attribute + * @param attrValue + * the value of the attribute, null if no value + * + * @see hdf.object.Datatype + */ + @SuppressWarnings({ "rawtypes", "unchecked", "deprecation" }) + public Attribute(HObject parentObj, String attrName, Datatype attrType, long[] attrDims, Object attrValue) { + super((parentObj == null) ? null : parentObj.getFileFormat(), attrName, + (parentObj == null) ? null : parentObj.getFullName(), null); + + this.parentObject = parentObj; + + datatype = attrType; + if (attrDims == null) { + rank = 1; + dims = new long[] { 1 }; + isScalar = true; + } + else { + dims = attrDims; + rank = dims.length; + isScalar = false; + } + + data = attrValue; + properties = new HashMap(); + + unsignedConverted = false; + + selectedDims = new long[rank]; + startDims = new long[rank]; + selectedStride = new long[rank]; + + log.trace("attrName={}, attrType={}, attrValue={}, rank={}, isUnsigned={}, isScalar={}", + attrName, getDatatype().getDescription(), data, rank, getDatatype().isUnsigned(), isScalar); + + resetSelection(); + } + + /* + * (non-Javadoc) + * + * @see hdf.object.HObject#open() + */ + @Override + public long open() { + log.trace("open(): start"); + + if (parentObject == null) { + log.debug("open(): attribute's parent object is null"); + log.trace("open(): exit"); + return -1; + } + + long aid = -1; + long pObjID = -1; + + try { + pObjID = parentObject.open(); + if (pObjID >= 0) { + if (this.getFileFormat().isThisType(FileFormat.getFileFormat(FileFormat.FILE_TYPE_HDF5))) { + log.trace("open(): FILE_TYPE_HDF5"); + if (H5.H5Aexists(pObjID, getName())) + aid = H5.H5Aopen(pObjID, getName(), HDF5Constants.H5P_DEFAULT); + } + else if (this.getFileFormat().isThisType(FileFormat.getFileFormat(FileFormat.FILE_TYPE_HDF4))) { + log.trace("open(): FILE_TYPE_HDF4"); + /* + * TODO: Get type of HDF4 object this is attached to and retrieve attribute info. + */ + } + else if (this.getFileFormat().isThisType(FileFormat.getFileFormat(FileFormat.FILE_TYPE_NC3))) { + log.trace("open(): FILE_TYPE_NC3"); + /* + * TODO: Get type of netcdf3 object this is attached to and retrieve attribute info. + */ + } + } + + log.trace("open(): aid={}", aid); + } + catch (Exception ex) { + log.debug("open(): Failed to open attribute {}: ", getName(), ex); + aid = -1; + } + finally { + parentObject.close(pObjID); + } + + log.trace("open(): finish"); + + return aid; + } + + /* + * (non-Javadoc) + * + * @see hdf.object.HObject#close(int) + */ + @Override + public void close(long aid) { + log.trace("close(): start"); + + if (aid >= 0) { + if (this.getFileFormat().isThisType(FileFormat.getFileFormat(FileFormat.FILE_TYPE_HDF5))) { + log.trace("close(): FILE_TYPE_HDF5"); + try { + H5.H5Aclose(aid); + } + catch (HDF5Exception ex) { + log.debug("close(): H5Aclose({}) failure: ", aid, ex); + } + } + else if (this.getFileFormat().isThisType(FileFormat.getFileFormat(FileFormat.FILE_TYPE_HDF4))) { + log.trace("close(): FILE_TYPE_HDF4"); + /* + * TODO: Get type of HDF4 object this is attached to and close attribute. + */ + } + else if (this.getFileFormat().isThisType(FileFormat.getFileFormat(FileFormat.FILE_TYPE_NC3))) { + log.trace("close(): FILE_TYPE_NC3"); + /* + * TODO: Get type of netcdf3 object this is attached to and close attribute. + */ + } + } + + log.trace("close(): finish"); + } + + @Override + public void init() { + log.trace("init(): start"); + + if (inited) { + resetSelection(); + log.trace("init(): Attribute already inited"); + log.trace("init(): finish"); + return; + } + + if (this.getFileFormat().isThisType(FileFormat.getFileFormat(FileFormat.FILE_TYPE_HDF5))) { + long aid = -1; + long tid = -1; + int tclass = -1; + flatNameList = new Vector<>(); + flatTypeList = new Vector<>(); + long[] memberTIDs = null; + + log.trace("init(): FILE_TYPE_HDF5"); + aid = open(); + if (aid >= 0) { + try { + tid = H5.H5Aget_type(aid); + tclass = H5.H5Tget_class(tid); + + long tmptid = 0; + + // Handle ARRAY and VLEN types by getting the base type + if (tclass == HDF5Constants.H5T_ARRAY || tclass == HDF5Constants.H5T_VLEN) { + try { + tmptid = tid; + tid = H5.H5Tget_super(tmptid); + log.trace("init(): H5T_ARRAY or H5T_VLEN class old={}, new={}", tmptid, tid); + } + catch (Exception ex) { + log.debug("init(): H5T_ARRAY or H5T_VLEN H5Tget_super({}) failure: ", tmptid, ex); + tid = -1; + } + finally { + try { + H5.H5Tclose(tmptid); + } + catch (HDF5Exception ex) { + log.debug("init(): H5Tclose({}) failure: ", tmptid, ex); + } + } + } + + if (H5.H5Tget_class(tid) == HDF5Constants.H5T_COMPOUND) { + // initialize member information + H5Datatype.extractCompoundInfo((H5Datatype) getDatatype(), "", flatNameList, flatTypeList); + numberOfMembers = flatNameList.size(); + log.trace("init(): numberOfMembers={}", numberOfMembers); + + memberNames = new String[numberOfMembers]; + memberTIDs = new long[numberOfMembers]; + memberTypes = new Datatype[numberOfMembers]; + memberOrders = new int[numberOfMembers]; + isMemberSelected = new boolean[numberOfMembers]; + memberDims = new Object[numberOfMembers]; + + for (int i = 0; i < numberOfMembers; i++) { + isMemberSelected[i] = true; + memberTIDs[i] = flatTypeList.get(i).createNative(); + + try { + memberTypes[i] = flatTypeList.get(i); + } + catch (Exception ex) { + log.debug("init(): failed to create datatype for member[{}]: ", i, ex); + memberTypes[i] = null; + } + + memberNames[i] = flatNameList.get(i); + memberOrders[i] = 1; + memberDims[i] = null; + log.trace("init()[{}]: memberNames[{}]={}, memberTIDs[{}]={}, memberTypes[{}]={}", i, i, + memberNames[i], i, memberTIDs[i], i, memberTypes[i]); + + try { + tclass = H5.H5Tget_class(memberTIDs[i]); + } + catch (HDF5Exception ex) { + log.debug("init(): H5Tget_class({}) failure: ", memberTIDs[i], ex); + } + + if (tclass == HDF5Constants.H5T_ARRAY) { + int n = H5.H5Tget_array_ndims(memberTIDs[i]); + long mdim[] = new long[n]; + H5.H5Tget_array_dims(memberTIDs[i], mdim); + int idim[] = new int[n]; + for (int j = 0; j < n; j++) + idim[j] = (int) mdim[j]; + memberDims[i] = idim; + tmptid = H5.H5Tget_super(memberTIDs[i]); + memberOrders[i] = (int) (H5.H5Tget_size(memberTIDs[i]) / H5.H5Tget_size(tmptid)); + try { + H5.H5Tclose(tmptid); + } + catch (HDF5Exception ex) { + log.debug("init(): memberTIDs[{}] H5Tclose(tmptid {}) failure: ", i, tmptid, ex); + } + } + } // (int i=0; i
+ * For example, in a compound datatype of {int A, float B, char[] C}, users can + * choose to retrieve only {A, C} from the attribute. In this case, + * getSelectedMemberCount() returns two. + * + * @return the number of selected members. + */ + @Override + public int getSelectedMemberCount() { + int count = 0; + + if (isMemberSelected != null) { + for (int i = 0; i < isMemberSelected.length; i++) { + if (isMemberSelected[i]) { + count++; + } + } + } + + log.trace("getSelectedMemberCount(): count of selected members={}", count); + + return count; + } + + /** + * Returns the names of the members of the compound attribute. The names of + * compound members are stored in an array of Strings. + *
+ * For example, for a compound datatype of {int A, float B, char[] C} + * getMemberNames() returns ["A", "B", "C"}. + * + * @return the names of compound members. + */ + @Override + public String[] getMemberNames() { + return memberNames; + } + + /** + * Returns an array of the names of the selected members of the compound dataset. + * + * @return an array of the names of the selected members of the compound dataset. + */ + public final String[] getSelectedMemberNames() { + if (isMemberSelected == null) { + log.debug("getSelectedMemberNames(): isMemberSelected array is null"); + log.trace("getSelectedMemberNames(): finish"); + return memberNames; + } + + int idx = 0; + String[] names = new String[getSelectedMemberCount()]; + for (int i = 0; i < isMemberSelected.length; i++) { + if (isMemberSelected[i]) { + names[idx++] = memberNames[i]; + } + } + + return names; + } + + /** + * Checks if a member of the compound attribute is selected for read/write. + * + * @param idx + * the index of compound member. + * + * @return true if the i-th memeber is selected; otherwise returns false. + */ + @Override + public boolean isMemberSelected(int idx) { + if ((isMemberSelected != null) && (isMemberSelected.length > idx)) { + return isMemberSelected[idx]; + } + + return false; + } + + /** + * Selects the i-th member for read/write. + * + * @param idx + * the index of compound member. + */ + @Override + public void selectMember(int idx) { + if ((isMemberSelected != null) && (isMemberSelected.length > idx)) { + isMemberSelected[idx] = true; + } + } + + /** + * Selects/deselects all members. + * + * @param selectAll + * The indicator to select or deselect all members. If true, all + * members are selected for read/write. If false, no member is + * selected for read/write. + */ + @Override + public void setAllMemberSelection(boolean selectAll) { + if (isMemberSelected == null) { + return; + } + + for (int i = 0; i < isMemberSelected.length; i++) { + isMemberSelected[i] = selectAll; + } + } + + /** + * Returns array containing the total number of elements of the members of the + * compound attribute. + *
+ * For example, a compound attribute COMP has members of A, B and C as + * + *
+ * COMP { + * int A; + * float B[5]; + * double C[2][3]; + * } + *+ * + * getMemberOrders() will return an integer array of {1, 5, 6} to indicate that + * member A has one element, member B has 5 elements, and member C has 6 + * elements. + * + * @return the array containing the total number of elements of the members of + * the compound attribute. + */ + @Override + public int[] getMemberOrders() { + return memberOrders; + } + + /** + * Returns array containing the total number of elements of the selected members + * of the compound attribute. + * + *
+ * For example, a compound attribute COMP has members of A, B and C as + * + *
+ * COMP { + * int A; + * float B[5]; + * double C[2][3]; + * } + *+ * + * If A and B are selected, getSelectedMemberOrders() returns an array of {1, 5} + * + * @return array containing the total number of elements of the selected members + * of the compound attribute. + */ + @Override + public int[] getSelectedMemberOrders() { + log.trace("getSelectedMemberOrders(): start"); + + if (isMemberSelected == null) { + log.debug("getSelectedMemberOrders(): isMemberSelected array is null"); + log.trace("getSelectedMemberOrders(): finish"); + return memberOrders; + } + + int idx = 0; + int[] orders = new int[getSelectedMemberCount()]; + for (int i = 0; i < isMemberSelected.length; i++) { + if (isMemberSelected[i]) { + orders[idx++] = memberOrders[i]; + } + } + + log.trace("getSelectedMemberOrders(): finish"); + + return orders; + } + + /** + * Returns the dimension sizes of the i-th member. + *
+ * For example, a compound attribute COMP has members of A, B and C as + * + *
+ * COMP { + * int A; + * float B[5]; + * double C[2][3]; + * } + *+ * + * getMemberDims(2) returns an array of {2, 3}, while getMemberDims(1) returns + * an array of {5}, and getMemberDims(0) returns null. + * + * @param i + * the i-th member + * + * @return the dimension sizes of the i-th member, null if the compound member + * is not an array. + */ + @Override + public int[] getMemberDims(int i) { + if (memberDims == null) { + return null; + } + + return (int[]) memberDims[i]; + } + + /** + * Returns an array of datatype objects of compound members. + *
+ * Each member of a compound attribute has its own datatype. The datatype of a + * member can be atomic or other compound datatype (nested compound). The + * datatype objects are setup at init(). + *
+ * + * @return the array of datatype objects of the compound members. + */ + @Override + public Datatype[] getMemberTypes() { + return memberTypes; + } + + /** + * Returns an array of datatype objects of selected compound members. + * + * @return an array of datatype objects of selected compound members. + */ + @Override + public Datatype[] getSelectedMemberTypes() { + log.trace("getSelectedMemberTypes(): start"); + + if (isMemberSelected == null) { + log.debug("getSelectedMemberTypes(): isMemberSelected array is null"); + log.trace("getSelectedMemberTypes(): finish"); + return memberTypes; + } + + int idx = 0; + Datatype[] types = new Datatype[getSelectedMemberCount()]; + for (int i = 0; i < isMemberSelected.length; i++) { + if (isMemberSelected[i]) { + types[idx++] = memberTypes[i]; + } + } + + log.trace("getSelectedMemberTypes(): finish"); + + return types; + } + + + @SuppressWarnings("rawtypes") + @Override + public List getMetadata() throws Exception { + throw new UnsupportedOperationException("Attribute:getMetadata Unsupported operation."); + } + + @Override + public void writeMetadata(Object metadata) throws Exception { + throw new UnsupportedOperationException("Attribute:writeMetadata Unsupported operation."); + } + + @Override + public void removeMetadata(Object metadata) throws Exception { + throw new UnsupportedOperationException("Attribute:removeMetadata Unsupported operation."); + } + + @Override + public void updateMetadata(Object metadata) throws Exception { + throw new UnsupportedOperationException("Attribute:updateMetadata Unsupported operation."); + } + + @Override + public boolean hasAttribute() { + return false; + } + + @Override + public final Datatype getDatatype() { + return datatype; + } + + @Override + public byte[] readBytes() throws Exception { + throw new UnsupportedOperationException("Attribute:readBytes Unsupported operation."); + } + + @Override + public Dataset copy(Group pgroup, String name, long[] dims, Object data) throws Exception { + throw new UnsupportedOperationException("Attribute:copy Unsupported operation."); + } + + /** + * Returns whether this Attribute is equal to the specified HObject by comparing + * various properties. + * + * @param obj + * The object + * + * @return true if the object is equal + */ + @Override + public boolean equals(Object obj) { + if (obj == null) + return false; + + // checking if both the object references are + // referring to the same object. + if (this == obj) + return true; + if (obj instanceof Attribute) { + if (!this.getFullName().equals(((Attribute) obj).getFullName())) + return false; + + if (!this.getFileFormat().equals(((Attribute) obj).getFileFormat())) + return false; + + if (!Arrays.equals(this.getDims(), ((DataFormat) obj).getDims())) + return false; + + return (this.getParentObject().equals(((Attribute) obj).getParentObject())); + } + return false; + } + + @Override + public int hashCode() { + + // We are returning the OID as a hashcode value. + return super.hashCode(); + } + + /** + * Returns a string representation of the data value of the attribute. For + * example, "0, 255". + *
+ * For a compound datatype, it will be a 1D array of strings with field + * members separated by the delimiter. For example, + * "{0, 10.5}, {255, 20.0}, {512, 30.0}" is a compound attribute of {int, + * float} of three data points. + *
+ * + * @param delimiter + * The delimiter used to separate individual data points. It + * can be a comma, semicolon, tab or space. For example, + * toString(",") will separate data by commas. + * + * @return the string representation of the data values. + */ + public String toString(String delimiter) { + return toString(delimiter, -1); + } + + /** + * Returns a string representation of the data value of the attribute. For + * example, "0, 255". + *
+ * For a compound datatype, it will be a 1D array of strings with field + * members separated by the delimiter. For example, + * "{0, 10.5}, {255, 20.0}, {512, 30.0}" is a compound attribute of {int, + * float} of three data points. + *
+ *
+ * @param delimiter
+ * The delimiter used to separate individual data points. It
+ * can be a comma, semicolon, tab or space. For example,
+ * toString(",") will separate data by commas.
+ * @param maxItems
+ * The maximum number of Array values to return
+ *
+ * @return the string representation of the data values.
+ */
+ public String toString(String delimiter, int maxItems) {
+ log.trace("toString(): start");
+
+ if (data == null) {
+ log.debug("toString(): value is null");
+ log.trace("toString(): finish");
+ return null;
+ }
+
+ Class extends Object> valClass = data.getClass();
+
+ if (!valClass.isArray()) {
+ log.trace("toString(): finish - not array");
+ String strValue = data.toString();
+ if (maxItems > 0 && strValue.length() > maxItems) {
+ // truncate the extra characters
+ strValue = strValue.substring(0, maxItems);
+ }
+ return strValue;
+ }
+
+ // attribute value is an array
+ StringBuilder sb = new StringBuilder();
+ int n = Array.getLength(data);
+ if ((maxItems > 0) && (n > maxItems))
+ n = maxItems;
+
+ log.trace("toString: is_enum={} is_unsigned={} Array.getLength={}", getDatatype().isEnum(),
+ getDatatype().isUnsigned(), n);
+
+ if (getDatatype().isEnum()) {
+ String cname = valClass.getName();
+ char dname = cname.charAt(cname.lastIndexOf('[') + 1);
+ log.trace("toString: is_enum with cname={} dname={}", cname, dname);
+
+ Map
+ * A compound datatype is an aggregation of one or more datatypes. Each member
+ * of a compound type has a name which is unique within that type, and a
+ * datatype of that member in a compound datum. Compound datatypes can be nested,
+ * i.e. members of a compound datatype can be some other compound datatype.
+ *
+ * For more details on compound datatypes,
+ * see HDF5 User's Guide
+ *
+ * Since Java cannot handle C-structured compound data, data in a compound dataset
+ * is loaded in to an Java List. Each element of the list is a data array that
+ * corresponds to a compound field. The data is read/written by compound field.
+ *
+ * For example, if compound dataset "comp" has the following nested structure,
+ * and member datatypes
+ *
+ *
+ * For example, a compound dataset COMP has members of A, B and C as
+ *
+ *
+ * The i-th element of the Object[] is an integer array (int[]) that
+ * contains the dimension sizes of the i-th member.
+ */
+ protected transient Object[] memberDims;
+
+ /**
+ * The datatypes of compound members.
+ */
+ protected Datatype[] memberTypes;
+
+ /**
+ * The array to store flags to indicate if a member of this compound
+ * dataset is selected for read/write.
+ *
+ * If a member is selected, the read/write will perform on the member.
+ * Applications such as HDFView will only display the selected members of
+ * the compound dataset.
+ *
+ *
+ * The dataset object represents an existing dataset in the file. For
+ * example, new H5CompoundDS(file, "dset1", "/g0/") constructs a dataset
+ * object that corresponds to the dataset, "dset1", at group "/g0/".
+ *
+ * This object is usually constructed at FileFormat.open(), which loads the
+ * file structure and object information into memory. It is rarely used
+ * elsewhere.
+ *
+ * @param theFile
+ * the file that contains the dataset.
+ * @param dsName
+ * the name of the CompoundDS, e.g. "compDS".
+ * @param dsPath
+ * the full path of the CompoundDS, e.g. "/g1".
+ */
+ public CompoundDS(FileFormat theFile, String dsName, String dsPath) {
+ this(theFile, dsName, dsPath, null);
+ }
+
+ /**
+ * @deprecated Not for public use in the future.
+ * For example, in a compound datatype of {int A, float B, char[] C},
+ * users can choose to retrieve only {A, C} from the dataset. In this
+ * case, getSelectedMemberCount() returns two.
+ *
+ * @return the number of selected members.
+ */
+ @Override
+ public final int getSelectedMemberCount() {
+ int count = 0;
+
+ if (isMemberSelected != null) {
+ for (int i = 0; i < isMemberSelected.length; i++) {
+ if (isMemberSelected[i]) {
+ count++;
+ }
+ }
+ }
+ log.trace("count of selected members={}", count);
+
+ return count;
+ }
+
+ /**
+ * Returns the names of the members of the compound dataset. The names of
+ * compound members are stored in an array of Strings.
+ *
+ * For example, for a compound datatype of {int A, float B, char[] C}
+ * getMemberNames() returns ["A", "B", "C"}.
+ *
+ * @return the names of compound members.
+ */
+ @Override
+ public final String[] getMemberNames() {
+ return memberNames;
+ }
+
+ /**
+ * Returns an array of the names of the selected members of the compound dataset.
+ *
+ * @return an array of the names of the selected members of the compound dataset.
+ */
+ public final String[] getSelectedMemberNames() {
+ if (isMemberSelected == null) {
+ log.debug("getSelectedMemberNames(): isMemberSelected array is null");
+ log.trace("getSelectedMemberNames(): finish");
+ return memberNames;
+ }
+
+ int idx = 0;
+ String[] names = new String[getSelectedMemberCount()];
+ for (int i = 0; i < isMemberSelected.length; i++) {
+ if (isMemberSelected[i]) {
+ names[idx++] = memberNames[i];
+ }
+ }
+
+ return names;
+ }
+
+ /**
+ * Checks if a member of the compound dataset is selected for read/write.
+ *
+ * @param idx
+ * the index of compound member.
+ *
+ * @return true if the i-th memeber is selected; otherwise returns false.
+ */
+ @Override
+ public final boolean isMemberSelected(int idx) {
+ if ((isMemberSelected != null) && (isMemberSelected.length > idx)) {
+ return isMemberSelected[idx];
+ }
+ else {
+ return false;
+ }
+ }
+
+ /**
+ * Selects the i-th member for read/write.
+ *
+ * @param idx
+ * the index of compound member.
+ */
+ @Override
+ public final void selectMember(int idx) {
+ if ((isMemberSelected != null) && (isMemberSelected.length > idx)) {
+ isMemberSelected[idx] = true;
+ }
+ }
+
+ /**
+ * Selects/deselects all members.
+ *
+ * @param selectAll
+ * The indicator to select or deselect all members. If true, all
+ * members are selected for read/write. If false, no member is
+ * selected for read/write.
+ */
+ @Override
+ public final void setAllMemberSelection(boolean selectAll) {
+ if (isMemberSelected == null) {
+ return;
+ }
+
+ for (int i = 0; i < isMemberSelected.length; i++) {
+ isMemberSelected[i] = selectAll;
+ }
+ }
+
+ /**
+ * Returns array containing the total number of elements of the members of
+ * the compound dataset.
+ *
+ * For example, a compound dataset COMP has members of A, B and C as
+ *
+ *
+ * For example, a compound dataset COMP has members of A, B and C as
+ *
+ *
+ * For example, a compound dataset COMP has members of A, B and C as
+ *
+ *
+ * Each member of a compound dataset has its own datatype. The datatype of a
+ * member can be atomic or other compound datatype (nested compound).
+ * Sub-classes set up the datatype objects at init().
+ *
+ *
+ * @return the array of datatype objects of the compound members.
+ */
+ @Override
+ public final Datatype[] getMemberTypes() {
+ return memberTypes;
+ }
+
+ /**
+ * Returns an array of datatype objects of selected compound members.
+ *
+ * @return an array of datatype objects of selected compound members.
+ */
+ @Override
+ public final Datatype[] getSelectedMemberTypes() {
+ log.trace("getSelectedMemberTypes(): start");
+
+ if (isMemberSelected == null) {
+ log.debug("getSelectedMemberTypes(): isMemberSelected array is null");
+ log.trace("getSelectedMemberTypes(): finish");
+ return memberTypes;
+ }
+
+ int idx = 0;
+ Datatype[] types = new Datatype[getSelectedMemberCount()];
+ for (int i = 0; i < isMemberSelected.length; i++) {
+ if (isMemberSelected[i]) {
+ types[idx++] = memberTypes[i];
+ }
+ }
+
+ log.trace("getSelectedMemberTypes(): finish");
+
+ return types;
+ }
+
+ /**
+ * @deprecated Not implemented for compound dataset.
+ */
+ @Deprecated
+ @Override
+ public Dataset copy(Group pgroup, String name, long[] dims, Object data)
+ throws Exception {
+ throw new UnsupportedOperationException(
+ "Writing a subset of a compound dataset to a new dataset is not implemented.");
+ }
+}
diff --git a/src/main/java/hdf/object/CompoundDataFormat.java b/src/main/java/hdf/object/CompoundDataFormat.java
new file mode 100644
index 0000000..a42cad2
--- /dev/null
+++ b/src/main/java/hdf/object/CompoundDataFormat.java
@@ -0,0 +1,184 @@
+/*****************************************************************************
+ * Copyright by The HDF Group. *
+ * Copyright by the Board of Trustees of the University of Illinois. *
+ * All rights reserved. *
+ * *
+ * This file is part of the HDF Java Products distribution. *
+ * The full copyright notice, including terms governing use, modification, *
+ * and redistribution, is contained in the files COPYING and Copyright.html. *
+ * COPYING can be found at the root of the source code distribution tree. *
+ * Or, see https://support.hdfgroup.org/products/licenses.html *
+ * If you do not have access to either file, you may request a copy from *
+ * help@hdfgroup.org. *
+ ****************************************************************************/
+
+package hdf.object;
+
+/**
+ * An interface that provides general operations for data with a Compound
+ * datatype. For example, getting the names, dataspaces or datatypes of the
+ * members of the Compound datatype.
+ *
+ *
+ * @see hdf.object.HObject
+ *
+ * @version 1.0 5/3/2018
+ * @author Jordan T. Henderson
+ */
+public interface CompoundDataFormat extends DataFormat {
+
+ /**
+ * Returns the number of members of the compound data object.
+ *
+ * @return the number of members of the compound data object.
+ */
+ public abstract int getMemberCount();
+
+ /**
+ * Returns the number of selected members of the compound data object.
+ *
+ * Selected members are the compound fields which are selected for read/write.
+ *
+ * For example, in a compound datatype of {int A, float B, char[] C}, users can
+ * choose to retrieve only {A, C} from the data object. In this case,
+ * getSelectedMemberCount() returns two.
+ *
+ * @return the number of selected members.
+ */
+ public abstract int getSelectedMemberCount();
+
+ /**
+ * Returns the names of the members of the compound data object. The names of
+ * compound members are stored in an array of Strings.
+ *
+ * For example, for a compound datatype of {int A, float B, char[] C}
+ * getMemberNames() returns ["A", "B", "C"}.
+ *
+ * @return the names of compound members.
+ */
+ public abstract String[] getMemberNames();
+
+ /**
+ * Returns an array of the names of the selected compound members.
+ *
+ * @return an array of the names of the selected compound members.
+ */
+ public abstract String[] getSelectedMemberNames();
+
+ /**
+ * Checks if a member of the compound data object is selected for read/write.
+ *
+ * @param idx
+ * the index of compound member.
+ *
+ * @return true if the i-th memeber is selected; otherwise returns false.
+ */
+ public abstract boolean isMemberSelected(int idx);
+
+ /**
+ * Selects the i-th member for read/write.
+ *
+ * @param idx
+ * the index of compound member.
+ */
+ public abstract void selectMember(int idx);
+
+ /**
+ * Selects/deselects all members.
+ *
+ * @param selectAll
+ * The indicator to select or deselect all members. If true, all
+ * members are selected for read/write. If false, no member is
+ * selected for read/write.
+ */
+ public abstract void setAllMemberSelection(boolean selectAll);
+
+ /**
+ * Returns array containing the total number of elements of the members of the
+ * compound data object.
+ *
+ * For example, a compound dataset COMP has members of A, B and C as
+ *
+ *
+ * For example, a compound dataset COMP has members of A, B and C as
+ *
+ *
+ * For example, a compound dataset COMP has members of A, B and C as
+ *
+ *
+ * Each member of a compound data object has its own datatype. The datatype of a
+ * member can be atomic or other compound datatype (nested compound). The
+ * datatype objects are setup at init().
+ *
+ *
+ * @return the array of datatype objects of the compound members.
+ */
+ public abstract Datatype[] getMemberTypes();
+
+ /**
+ * Returns an array of datatype objects of the selected compound members.
+ *
+ * @return an array of datatype objects of the selected compound members.
+ */
+ public abstract Datatype[] getSelectedMemberTypes();
+
+}
diff --git a/src/main/java/hdf/object/DataFormat.java b/src/main/java/hdf/object/DataFormat.java
new file mode 100644
index 0000000..40704f8
--- /dev/null
+++ b/src/main/java/hdf/object/DataFormat.java
@@ -0,0 +1,366 @@
+/*****************************************************************************
+ * Copyright by The HDF Group. *
+ * Copyright by the Board of Trustees of the University of Illinois. *
+ * All rights reserved. *
+ * *
+ * This file is part of the HDF Java Products distribution. *
+ * The full copyright notice, including terms governing use, modification, *
+ * and redistribution, is contained in the files COPYING and Copyright.html. *
+ * COPYING can be found at the root of the source code distribution tree. *
+ * Or, see https://support.hdfgroup.org/products/licenses.html *
+ * If you do not have access to either file, you may request a copy from *
+ * help@hdfgroup.org. *
+ ****************************************************************************/
+
+package hdf.object;
+
+/**
+ * An interface that provides general I/O operations for object data. For
+ * example, reading data content from the file into memory or writing data
+ * content from memory into the file.
+ *
+ *
+ * @see hdf.object.HObject
+ *
+ * @version 1.0 4/2/2018
+ * @author Jordan T. Henderson
+ */
+public interface DataFormat {
+ public abstract boolean isInited();
+
+ public abstract void init();
+
+ /**
+ * Retrieves the object's data from the file.
+ *
+ * @return the object's data.
+ *
+ * @throws Exception
+ * if the data can not be retrieved
+ */
+ public abstract Object getData() throws Exception, OutOfMemoryError;
+
+ /**
+ *
+ *
+ * @param data
+ * the data to write.
+ */
+ public abstract void setData(Object data);
+
+ /**
+ * Clears the current data buffer in memory and forces the next read() to load
+ * the data from file.
+ *
+ * The function read() loads data from file into memory only if the data is not
+ * read. If data is already in memory, read() just returns the memory buffer.
+ * Sometimes we want to force read() to re-read data from file. For example,
+ * when the selection is changed, we need to re-read the data.
+ *
+ * @see #getData()
+ * @see #read()
+ */
+ public abstract void clearData();
+
+ /**
+ * Reads the data from file.
+ *
+ * read() reads the data from file to a memory buffer and returns the memory
+ * buffer. The dataset object does not hold the memory buffer. To store the
+ * memory buffer in the dataset object, one must call getData().
+ *
+ * By default, the whole dataset is read into memory. Users can also select
+ * a subset to read. Subsetting is done in an implicit way.
+ *
+ * @return the data read from file.
+ *
+ * @see #getData()
+ *
+ * @throws Exception
+ * if object can not be read
+ * @throws OutOfMemoryError
+ * if memory is exhausted
+ */
+ public abstract Object read() throws Exception, OutOfMemoryError;
+
+ /**
+ * Writes a memory buffer to the object in the file.
+ *
+ * @param buf
+ * the data to write
+ *
+ * @throws Exception
+ * if data can not be written
+ */
+ public abstract void write(Object buf) throws Exception;
+
+ /**
+ * Writes the current memory buffer to the object in the file.
+ *
+ * @throws Exception
+ * if data can not be written
+ */
+ public abstract void write() throws Exception;
+
+ /**
+ * Converts the data values of this data object to appropriate Java integers if
+ * they are unsigned integers.
+ *
+ * @see hdf.object.Dataset#convertToUnsignedC(Object)
+ * @see hdf.object.Dataset#convertFromUnsignedC(Object, Object)
+ *
+ * @return the converted data buffer.
+ */
+ public Object convertFromUnsignedC();
+
+ /**
+ * Converts Java integer data values of this data object back to unsigned C-type
+ * integer data if they are unsigned integers.
+ *
+ * @see hdf.object.Dataset#convertToUnsignedC(Object)
+ * @see hdf.object.Dataset#convertToUnsignedC(Object, Object)
+ *
+ * @return the converted data buffer.
+ */
+ public Object convertToUnsignedC();
+
+ /**
+ * Returns the fill values for the data object.
+ *
+ * @return the fill values for the data object.
+ */
+ public abstract Object getFillValue();
+
+ /**
+ * Returns the datatype of the data object.
+ *
+ * @return the datatype of the data object.
+ */
+ public abstract Datatype getDatatype();
+
+ /**
+ * Returns the rank (number of dimensions) of the data object. It returns a
+ * negative number if it failed to retrieve the dimension information from
+ * the file.
+ *
+ * @return the number of dimensions of the data object.
+ */
+ public abstract int getRank();
+
+ /**
+ * Returns the array that contains the dimension sizes of the data value of
+ * the data object. It returns null if it failed to retrieve the dimension
+ * information from the file.
+ *
+ * @return the dimension sizes of the data object.
+ */
+ public abstract long[] getDims();
+
+
+ /****************************************************************
+ * * The following four definitions are used for data subsetting. * *
+ ****************************************************************/
+
+ /**
+ * Returns the dimension sizes of the selected subset.
+ *
+ * The SelectedDims is the number of data points of the selected subset.
+ * Applications can use this array to change the size of selected subset.
+ *
+ * The selected size must be less than or equal to the current dimension size.
+ * Combined with the starting position, selected sizes and stride, the subset of
+ * a rectangle selection is fully defined.
+ *
+ * For example, if a 4 X 5 dataset is as follows:
+ *
+ *
+ * Applications can use this array to change the starting position of a
+ * selection. Combined with the selected dimensions, selected sizes and stride,
+ * the subset of a rectangle selection is fully defined.
+ *
+ * For example, if a 4 X 5 dataset is as follows:
+ *
+ *
+ * Applications can use this array to change how many elements to move in each
+ * dimension.
+ *
+ * Combined with the starting position and selected sizes, the subset of a
+ * rectangle selection is defined.
+ *
+ * For example, if a 4 X 5 dataset is as follows:
+ *
+ *
+ *
+ * selectedIndex[] is provided for two purposes:
+ *
+ * For example, for a four dimension dataset, if selectedIndex[] = {1, 2, 3},
+ * then dim[1] is selected as row index, dim[2] is selected as column index and
+ * dim[3] is selected as depth index.
+ *
+ * This function is used by GUI applications such as HDFView. GUI applications
+ * display a dataset in a 2D table or 2D image. The display order is specified
+ * by the index array of selectedIndex as follow:
+ *
+ * This function is used by GUI applications such as HDFView. GUI applications
+ * display a dataset in 2D Table or 2D Image. The display order is specified by
+ * the index array of selectedIndex as follow:
+ *
+ * For example, "SZIP: Pixels per block = 8: H5Z_FILTER_CONFIG_DECODE_ENABLED".
+ *
+ * @return the string representation of compression information.
+ */
+ public abstract String getCompression();
+
+ /**
+ * Get runtime Class of the original data buffer if converted.
+ *
+ * @return the Class of the original data buffer
+ */
+ @SuppressWarnings("rawtypes")
+ public abstract Class getOriginalClass();
+}
diff --git a/src/main/java/hdf/object/Dataset.java b/src/main/java/hdf/object/Dataset.java
new file mode 100644
index 0000000..0e6f487
--- /dev/null
+++ b/src/main/java/hdf/object/Dataset.java
@@ -0,0 +1,1297 @@
+/*****************************************************************************
+ * Copyright by The HDF Group. *
+ * Copyright by the Board of Trustees of the University of Illinois. *
+ * All rights reserved. *
+ * *
+ * This file is part of the HDF Java Products distribution. *
+ * The full copyright notice, including terms governing use, modification, *
+ * and redistribution, is contained in the files COPYING and Copyright.html. *
+ * COPYING can be found at the root of the source code distribution tree. *
+ * Or, see https://support.hdfgroup.org/products/licenses.html *
+ * If you do not have access to either file, you may request a copy from *
+ * help@hdfgroup.org. *
+ ****************************************************************************/
+
+package hdf.object;
+
+import java.lang.reflect.Array;
+import java.util.List;
+
+/**
+ * The abstract class provides general APIs to create and manipulate dataset
+ * objects, and retrieve dataset properties, datatype and dimension sizes.
+ *
+ * This class provides two convenient functions, read()/write(), to read/write
+ * data values. Reading/writing data may take many library calls if we use the
+ * library APIs directly. The read() and write functions hide all the details of
+ * these calls from users.
+ *
+ * For more details on dataset,
+ * see HDF5 User's Guide
+ *
+ *
+ * @see hdf.object.ScalarDS
+ * @see CompoundDS
+ *
+ * @version 1.1 9/4/2007
+ * @author Peter X. Cao
+ */
+public abstract class Dataset extends HObject implements MetaDataContainer, DataFormat {
+ private static final long serialVersionUID = -3360885430038261178L;
+
+ private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(Dataset.class);
+
+ /**
+ * The memory buffer that holds the raw data array of the dataset.
+ */
+ protected transient Object data;
+
+ /**
+ * The number of dimensions of the dataset.
+ */
+ protected int rank;
+
+ /**
+ * The current dimension sizes of the dataset
+ */
+ protected long[] dims;
+
+ /**
+ * The max dimension sizes of the dataset
+ */
+ protected long[] maxDims;
+
+ /**
+ * Array that contains the number of data points selected (for read/write)
+ * in each dimension.
+ *
+ * The selected size must be less than or equal to the current dimension size.
+ * A subset of a rectangle selection is defined by the starting position and
+ * selected sizes.
+ *
+ * For example, if a 4 X 5 dataset is as follows:
+ *
+ *
+ * selectedIndex[] is provided for two purposes:
+ *
+ * For example, Suppose that the original data is an array of unsigned
+ * 16-bit short integers. Since Java does not support unsigned integer, the
+ * data is converted to an array of 32-bit singed integer. In that case, the
+ * converted buffer is the array of 32-bit singed integer.
+ */
+ protected transient Object convertedBuf = null;
+
+ /**
+ * Constructs a Dataset object with a given file, name and path.
+ *
+ * @param theFile
+ * the file that contains the dataset.
+ * @param dsName
+ * the name of the Dataset, e.g. "dset1".
+ * @param dsPath
+ * the full group path of this Dataset, e.g. "/arrays/".
+ */
+ public Dataset(FileFormat theFile, String dsName, String dsPath) {
+ this(theFile, dsName, dsPath, null);
+ }
+
+ /**
+ * @deprecated Not for public use in the future.
+ * The SelectedDims is the number of data points of the selected subset.
+ * Applications can use this array to change the size of selected subset.
+ *
+ * The selected size must be less than or equal to the current dimension size.
+ * Combined with the starting position, selected sizes and stride, the
+ * subset of a rectangle selection is fully defined.
+ *
+ * For example, if a 4 X 5 dataset is as follows:
+ *
+ *
+ * Applications can use this array to change the starting position of a
+ * selection. Combined with the selected dimensions, selected sizes and
+ * stride, the subset of a rectangle selection is fully defined.
+ *
+ * For example, if a 4 X 5 dataset is as follows:
+ *
+ *
+ * Applications can use this array to change how many elements to move in
+ * each dimension.
+ *
+ * Combined with the starting position and selected sizes, the subset of a
+ * rectangle selection is defined.
+ *
+ * For example, if a 4 X 5 dataset is as follows:
+ *
+ *
+ * In a string dataset, the raw data from file is stored in a byte array. By
+ * default, this byte array is converted to an array of strings. For a large
+ * dataset (e.g. more than one million strings), the conversion takes a long
+ * time and requires a lot of memory space to store the strings. In some
+ * applications, such a conversion can be delayed. For example, A GUI
+ * application may convert only the part of the strings that is visible to the
+ * users, not the entire data array.
+ *
+ * setConvertByteToString(boolean b) allows users to set the flag so that
+ * applications can choose to perform the byte-to-string conversion or not.
+ * If the flag is set to false, the getData() returns an array of byte
+ * instead of an array of strings.
+ *
+ * @param b
+ * convert bytes to strings if b is true; otherwise, if false, do
+ * not convert bytes to strings.
+ */
+ public final void setConvertByteToString(boolean b) {
+ convertByteToString = b;
+ }
+
+ /**
+ * Returns the flag that indicates if a byte array is converted to a string
+ * array.
+ *
+ * @return true if byte array is converted to string; otherwise, returns
+ * false if there is no conversion.
+ */
+ public final boolean getConvertByteToString() {
+ return convertByteToString;
+ }
+
+ /**
+ * Reads the raw data of the dataset from file to a byte array.
+ *
+ * readBytes() reads raw data to an array of bytes instead of array of its
+ * datatype. For example, for a one-dimension 32-bit integer dataset of
+ * size 5, readBytes() returns a byte array of size 20 instead of an
+ * int array of 5.
+ *
+ * readBytes() can be used to copy data from one dataset to another
+ * efficiently because the raw data is not converted to its native type, it
+ * saves memory space and CPU time.
+ *
+ * @return the byte array of the raw data.
+ *
+ * @throws Exception if data can not be read
+ */
+ public abstract byte[] readBytes() throws Exception;
+
+ /**
+ * Writes the memory buffer of this dataset to file.
+ *
+ * @throws Exception if buffer can not be written
+ */
+ @Override
+ public final void write() throws Exception {
+ if (data != null) {
+ write(data);
+ }
+ }
+
+ /**
+ * Creates a new dataset and writes the data buffer to the new dataset.
+ *
+ * This function allows applications to create a new dataset for a given
+ * data buffer. For example, users can select a specific interesting part
+ * from a large image and create a new image with the selection.
+ *
+ * The new dataset retains the datatype and dataset creation properties of
+ * this dataset.
+ *
+ * @param pgroup
+ * the group which the dataset is copied to.
+ * @param name
+ * the name of the new dataset.
+ * @param dims
+ * the dimension sizes of the the new dataset.
+ * @param data
+ * the data values of the subset to be copied.
+ *
+ * @return the new dataset.
+ *
+ * @throws Exception if dataset can not be copied
+ */
+ public abstract Dataset copy(Group pgroup, String name, long[] dims, Object data) throws Exception;
+
+ @Override
+ public final boolean isInited() {
+ return inited;
+ }
+
+ /**
+ * Returns the data buffer of the dataset in memory.
+ *
+ * If data is already loaded into memory, returns the data; otherwise, calls
+ * read() to read data from file into a memory buffer and returns the memory
+ * buffer.
+ *
+ * By default, the whole dataset is read into memory. Users can also select
+ * a subset to read. Subsetting is done in an implicit way.
+ *
+ * How to Select a Subset
+ *
+ * A selection is specified by three arrays: start, stride and count.
+ *
+ * The following example shows how to make a subset. In the example, the
+ * dataset is a 4-dimensional array of [200][100][50][10], i.e. dims[0]=200;
+ * dims[1]=100; dims[2]=50; dims[3]=10;
+ * For ScalarDS, the memory data buffer is a one-dimensional array of byte,
+ * short, int, float, double or String type based on the datatype of the
+ * dataset.
+ *
+ * For CompoundDS, the memory data object is an java.util.List object. Each
+ * element of the list is a data array that corresponds to a compound field.
+ *
+ * For example, if compound dataset "comp" has the following nested
+ * structure, and member datatypes
+ *
+ *
+ * setData() is not safe to use because it changes memory buffer
+ * of the dataset object. Dataset operations such as write/read
+ * will fail if the buffer type or size is changed.
+ *
+ * @param d the object data -must be an array of Objects
+ */
+ @Override
+ public final void setData(Object d) {
+ if (!(this instanceof Attribute))
+ throw new UnsupportedOperationException("setData: unsupported for non-Attribute objects");
+
+ log.trace("setData");
+ data = d;
+ }
+
+ /**
+ * Clears the current data buffer in memory and forces the next read() to load
+ * the data from file.
+ *
+ * The function read() loads data from file into memory only if the data is
+ * not read. If data is already in memory, read() just returns the memory
+ * buffer. Sometimes we want to force read() to re-read data from file. For
+ * example, when the selection is changed, we need to re-read the data.
+ *
+ * @see #getData()
+ * @see #read()
+ */
+ @Override
+ public void clearData() {
+ isDataLoaded = false;
+ }
+
+ /**
+ * Returns the dimension size of the vertical axis.
+ *
+ *
+ * This function is used by GUI applications such as HDFView. GUI
+ * applications display a dataset in a 2D table or 2D image. The display
+ * order is specified by the index array of selectedIndex as follow:
+ *
+ * This function is used by GUI applications such as HDFView. GUI
+ * applications display a dataset in 2D Table or 2D Image. The display order is
+ * specified by the index array of selectedIndex as follow:
+ *
+ *
+ * selectedIndex[] is provided for two purposes:
+ *
+ * For example, for a four dimension dataset, if selectedIndex[] = {1, 2, 3},
+ * then dim[1] is selected as row index, dim[2] is selected as column index
+ * and dim[3] is selected as depth index.
+ *
+ * For example,
+ * "SZIP: Pixels per block = 8: H5Z_FILTER_CONFIG_DECODE_ENABLED".
+ *
+ * @return the string representation of compression information.
+ */
+ @Override
+ public final String getCompression() {
+ if (!inited) init();
+
+ return compression.toString();
+ }
+
+ /**
+ * Returns the string representation of filter information.
+ *
+ * @return the string representation of filter information.
+ */
+ public final String getFilters() {
+ if (!inited) init();
+
+ return filters.toString();
+ }
+
+ /**
+ * Returns the string representation of storage layout information.
+ *
+ * @return the string representation of storage layout information.
+ */
+ public final String getStorageLayout() {
+ if (!inited) init();
+
+ return storageLayout.toString();
+ }
+
+ /**
+ * Returns the string representation of storage information.
+ *
+ * @return the string representation of storage information.
+ */
+ public final String getStorage() {
+ if (!inited) init();
+
+ return storage.toString();
+ }
+
+ /**
+ * Returns the array that contains the dimension sizes of the chunk of the
+ * dataset. Returns null if the dataset is not chunked.
+ *
+ * @return the array of chunk sizes or returns null if the dataset is not
+ * chunked.
+ */
+ public final long[] getChunkSize() {
+ if (!inited) init();
+
+ return chunkSize;
+ }
+
+ @Override
+ public Datatype getDatatype() {
+ return datatype;
+ }
+
+ /**
+ * @deprecated Not for public use in the future.
+ * Since Java does not support unsigned integer, values of unsigned C-type
+ * integers must be converted into its appropriate Java integer. Otherwise,
+ * the data value will not displayed correctly. For example, if an unsigned
+ * C byte, x = 200, is stored into an Java byte y, y will be -56 instead of
+ * the correct value of 200.
+ *
+ * Unsigned C integers are upgrade to Java integers according to the
+ * following table:
+ *
+ * If memory data of unsigned integers is converted by
+ * convertFromUnsignedC(), convertToUnsignedC() must be called to convert
+ * the data back to unsigned C before data is written into file.
+ *
+ * @see #convertToUnsignedC(Object, Object)
+ *
+ * @param dataIN
+ * the input 1D array of the unsigned C-type integers.
+ * @param dataOUT
+ * the output converted (or upgraded) 1D array of Java integers.
+ *
+ * @return the upgraded 1D array of Java integers.
+ */
+ @SuppressWarnings("rawtypes")
+ public static Object convertFromUnsignedC(Object dataIN, Object dataOUT) {
+ log.trace("convertFromUnsignedC(): start");
+
+ if (dataIN == null) {
+ log.debug("convertFromUnsignedC(): data_in is null");
+ log.trace("convertFromUnsignedC(): finish");
+ return null;
+ }
+
+ Class dataClass = dataIN.getClass();
+ if (!dataClass.isArray()) {
+ log.debug("convertFromUnsignedC(): data_in not an array");
+ log.trace("convertFromUnsignedC(): finish");
+ return null;
+ }
+
+ if (dataOUT != null) {
+ Class dataClassOut = dataOUT.getClass();
+ if (!dataClassOut.isArray() || (Array.getLength(dataIN) != Array.getLength(dataOUT))) {
+ log.debug("convertFromUnsignedC(): data_out not an array or does not match data_in size");
+ dataOUT = null;
+ }
+ }
+
+ String cname = dataClass.getName();
+ char dname = cname.charAt(cname.lastIndexOf('[') + 1);
+ int size = Array.getLength(dataIN);
+ log.trace("convertFromUnsignedC(): cname={} dname={} size={}", cname, dname, size);
+
+ if (dname == 'B') {
+ log.debug("convertFromUnsignedC(): Java convert byte to short");
+ short[] sdata = null;
+ if (dataOUT == null) {
+ sdata = new short[size];
+ }
+ else {
+ sdata = (short[]) dataOUT;
+ }
+
+ byte[] bdata = (byte[]) dataIN;
+ for (int i = 0; i < size; i++) {
+ sdata[i] = (short) ((bdata[i] + 256) & 0xFF);
+ }
+
+ dataOUT = sdata;
+ }
+ else if (dname == 'S') {
+ log.debug("convertFromUnsignedC(): Java convert short to int");
+ int[] idata = null;
+ if (dataOUT == null) {
+ idata = new int[size];
+ }
+ else {
+ idata = (int[]) dataOUT;
+ }
+
+ short[] sdata = (short[]) dataIN;
+ for (int i = 0; i < size; i++) {
+ idata[i] = (sdata[i] + 65536) & 0xFFFF;
+ }
+
+ dataOUT = idata;
+ }
+ else if (dname == 'I') {
+ log.debug("convertFromUnsignedC(): Java convert int to long");
+ long[] ldata = null;
+ if (dataOUT == null) {
+ ldata = new long[size];
+ }
+ else {
+ ldata = (long[]) dataOUT;
+ }
+
+ int[] idata = (int[]) dataIN;
+ for (int i = 0; i < size; i++) {
+ ldata[i] = (idata[i] + 4294967296L) & 0xFFFFFFFFL;
+ }
+
+ dataOUT = ldata;
+ }
+ else {
+ dataOUT = dataIN;
+ log.debug("convertFromUnsignedC(): Java does not support unsigned long");
+ }
+
+ log.trace("convertFromUnsignedC(): finish");
+ return dataOUT;
+ }
+
+ /**
+ * @deprecated Not for public use in the future.
+ * If memory data of unsigned integers is converted by
+ * convertFromUnsignedC(), convertToUnsignedC() must be called to convert
+ * the data back to unsigned C before data is written into file.
+ *
+ * @see #convertFromUnsignedC(Object, Object)
+ *
+ * @param dataIN
+ * the input array of the Java integer.
+ * @param dataOUT
+ * the output array of the unsigned C-type integer.
+ *
+ * @return the converted data of unsigned C-type integer array.
+ */
+ @SuppressWarnings("rawtypes")
+ public static Object convertToUnsignedC(Object dataIN, Object dataOUT) {
+ log.trace("convertToUnsignedC(): start");
+
+ if (dataIN == null) {
+ log.debug("convertToUnsignedC(): data_in is null");
+ log.trace("convertToUnsignedC(): finish");
+ return null;
+ }
+
+ Class dataClass = dataIN.getClass();
+ if (!dataClass.isArray()) {
+ log.debug("convertToUnsignedC(): data_in not an array");
+ log.trace("convertToUnsignedC(): finish");
+ return null;
+ }
+
+ if (dataOUT != null) {
+ Class dataClassOut = dataOUT.getClass();
+ if (!dataClassOut.isArray() || (Array.getLength(dataIN) != Array.getLength(dataOUT))) {
+ log.debug("convertToUnsignedC(): data_out not an array or does not match data_in size");
+ dataOUT = null;
+ }
+ }
+
+ String cname = dataClass.getName();
+ char dname = cname.charAt(cname.lastIndexOf('[') + 1);
+ int size = Array.getLength(dataIN);
+ log.trace("convertToUnsignedC(): cname={} dname={} size={}", cname, dname, size);
+
+ if (dname == 'S') {
+ log.debug("convertToUnsignedC(): Java convert short to byte");
+ byte[] bdata = null;
+ if (dataOUT == null) {
+ bdata = new byte[size];
+ }
+ else {
+ bdata = (byte[]) dataOUT;
+ }
+ short[] sdata = (short[]) dataIN;
+ for (int i = 0; i < size; i++) {
+ bdata[i] = (byte) sdata[i];
+ }
+ dataOUT = bdata;
+ }
+ else if (dname == 'I') {
+ log.debug("convertToUnsignedC(): Java convert int to short");
+ short[] sdata = null;
+ if (dataOUT == null) {
+ sdata = new short[size];
+ }
+ else {
+ sdata = (short[]) dataOUT;
+ }
+ int[] idata = (int[]) dataIN;
+ for (int i = 0; i < size; i++) {
+ sdata[i] = (short) idata[i];
+ }
+ dataOUT = sdata;
+ }
+ else if (dname == 'J') {
+ log.debug("convertToUnsignedC(): Java convert long to int");
+ int[] idata = null;
+ if (dataOUT == null) {
+ idata = new int[size];
+ }
+ else {
+ idata = (int[]) dataOUT;
+ }
+ long[] ldata = (long[]) dataIN;
+ for (int i = 0; i < size; i++) {
+ idata[i] = (int) ldata[i];
+ }
+ dataOUT = idata;
+ }
+ else {
+ dataOUT = dataIN;
+ log.debug("convertToUnsignedC(): Java does not support unsigned long");
+ }
+
+ log.trace("convertToUnsignedC(): finish");
+ return dataOUT;
+ }
+
+ /**
+ * Converts an array of bytes into an array of Strings for a fixed string
+ * dataset.
+ *
+ * A C-string is an array of chars while an Java String is an object. When a
+ * string dataset is read into a Java application, the data is stored in an
+ * array of Java bytes. byteToString() is used to convert the array of bytes
+ * into an array of Java strings so that applications can display and modify
+ * the data content.
+ *
+ * For example, the content of a two element C string dataset is {"ABC",
+ * "abc"}. Java applications will read the data into a byte array of {65,
+ * 66, 67, 97, 98, 99). byteToString(bytes, 3) returns an array of Java
+ * String of strs[0]="ABC", and strs[1]="abc".
+ *
+ * If memory data of strings is converted to Java Strings, stringToByte()
+ * must be called to convert the memory data back to byte array before data
+ * is written to file.
+ *
+ * @see #stringToByte(String[], int)
+ *
+ * @param bytes
+ * the array of bytes to convert.
+ * @param length
+ * the length of string.
+ *
+ * @return the array of Java String.
+ */
+ public static final String[] byteToString(byte[] bytes, int length) {
+ log.trace("byteToString(): start");
+
+ if (bytes == null) {
+ log.debug("byteToString(): input is null");
+ log.trace("byteToString(): finish");
+ return null;
+ }
+
+ int n = bytes.length / length;
+ log.trace("byteToString(): n={} from length of {}", n, length);
+ String[] strArray = new String[n];
+ String str = null;
+ int idx = 0;
+ for (int i = 0; i < n; i++) {
+ str = new String(bytes, i * length, length);
+ idx = str.indexOf('\0');
+ if (idx >= 0) {
+ str = str.substring(0, idx);
+ }
+
+ // trim only the end
+ int end = str.length();
+ while (end > 0 && str.charAt(end - 1) <= '\u0020')
+ end--;
+
+ strArray[i] = (end <= 0) ? "" : str.substring(0, end);
+ }
+
+ log.trace("byteToString(): finish");
+ return strArray;
+ }
+
+ /**
+ * Converts a string array into an array of bytes for a fixed string
+ * dataset.
+ *
+ * If memory data of strings is converted to Java Strings, stringToByte()
+ * must be called to convert the memory data back to byte array before data
+ * is written to file.
+ *
+ * @see #byteToString(byte[] bytes, int length)
+ *
+ * @param strings
+ * the array of string.
+ * @param length
+ * the length of string.
+ *
+ * @return the array of bytes.
+ */
+ public static final byte[] stringToByte(String[] strings, int length) {
+ log.trace("stringToByte(): start");
+
+ if (strings == null) {
+ log.debug("stringToByte(): input is null");
+ log.trace("stringToByte(): finish");
+ return null;
+ }
+
+ int size = strings.length;
+ byte[] bytes = new byte[size * length];
+ log.trace("stringToByte(): size={} length={}", size, length);
+ StringBuilder strBuff = new StringBuilder(length);
+ for (int i = 0; i < size; i++) {
+ // initialize the string with spaces
+ strBuff.replace(0, length, " ");
+
+ if (strings[i] != null) {
+ if (strings[i].length() > length) {
+ strings[i] = strings[i].substring(0, length);
+ }
+ strBuff.replace(0, length, strings[i]);
+ }
+
+ strBuff.setLength(length);
+ System.arraycopy(strBuff.toString().getBytes(), 0, bytes, length * i, length);
+ }
+
+ log.trace("stringToByte(): finish");
+
+ return bytes;
+ }
+
+ /**
+ * Returns the array of strings that represent the dimension names. Returns
+ * null if there is no dimension name.
+ *
+ * Some datasets have pre-defined names for each dimension such as
+ * "Latitude" and "Longitude". getDimNames() returns these pre-defined
+ * names.
+ *
+ * @return the names of dimensions, or null if there is no dimension name.
+ */
+ public final String[] getDimNames() {
+ if (!inited) init();
+
+ return dimNames;
+ }
+
+ /**
+ * Checks if a given datatype is a string. Sub-classes must replace this
+ * default implementation.
+ *
+ * @param tid
+ * The data type identifier.
+ *
+ * @return true if the datatype is a string; otherwise returns false.
+ */
+ public boolean isString(long tid) {
+ return false;
+ }
+
+ /**
+ * Returns the size in bytes of a given datatype. Sub-classes must replace
+ * this default implementation.
+ *
+ * @param tid
+ * The data type identifier.
+ *
+ * @return The size of the datatype
+ */
+ public long getSize(long tid) {
+ return -1;
+ }
+
+ /**
+ * Get Class of the original data buffer if converted.
+ *
+ * @return the Class of originalBuf
+ */
+ @Override
+ @SuppressWarnings("rawtypes")
+ public final Class getOriginalClass() {
+ return originalBuf.getClass();
+ }
+
+ /*
+ * Checks if dataset is virtual. Sub-classes must replace
+ * this default implementation.
+ *
+ * @return true if the dataset is virtual; otherwise returns false.
+ */
+ public boolean isVirtual() {
+ return false;
+ }
+
+ /*
+ * Gets the source file name at index if dataset is virtual. Sub-classes must replace
+ * this default implementation.
+ *
+ * @return filename if the dataset is virtual; otherwise returns null.
+ */
+ public String getVirtualFilename(int index) {
+ return null;
+ }
+
+ /*
+ * Gets the number of source files if dataset is virtual. Sub-classes must replace
+ * this default implementation.
+ *
+ * @return the list size if the dataset is virtual; otherwise returns negative.
+ */
+ public int getVirtualMaps() {
+ return -1;
+ }
+}
diff --git a/src/main/java/hdf/object/Datatype.java b/src/main/java/hdf/object/Datatype.java
new file mode 100644
index 0000000..31b8567
--- /dev/null
+++ b/src/main/java/hdf/object/Datatype.java
@@ -0,0 +1,933 @@
+/*****************************************************************************
+ * Copyright by The HDF Group. *
+ * Copyright by the Board of Trustees of the University of Illinois. *
+ * All rights reserved. *
+ * *
+ * This file is part of the HDF Java Products distribution. *
+ * The full copyright notice, including terms governing use, modification, *
+ * and redistribution, is contained in the files COPYING and Copyright.html. *
+ * COPYING can be found at the root of the source code distribution tree. *
+ * Or, see https://support.hdfgroup.org/products/licenses.html *
+ * If you do not have access to either file, you may request a copy from *
+ * help@hdfgroup.org. *
+ ****************************************************************************/
+
+package hdf.object;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+/**
+ * Datatype is an abstract class that defines datatype characteristics and APIs for a data type.
+ *
+ * A datatype has four basic characteristics: class, size, byte order and sign. These
+ * characteristics are defined in the
+ * HDF5 User's Guide.
+ *
+ * These characteristics apply to all the sub-classes. The sub-classes may have different ways to
+ * describe a datatype. We here define the native datatype to the datatype used by
+ * the sub-class. For example, H5Datatype uses a datatype identifier (hid_t) to specify a datatype.
+ * NC2Datatype uses ucar.nc2.DataType object to describe its datatype. "Native" here is different
+ * from the "native" definition in the HDF5 library.
+ *
+ * Two functions, createNative() and fromNative(), are defined to convert the general
+ * characteristics to/from the native datatype. Sub-classes must implement these functions so that
+ * the conversion will be done correctly. The values of the CLASS member are not identical to HDF5
+ * values for a datatype class.
+ *
+ *
+ * @version 1.1 9/4/2007
+ * @author Peter X. Cao
+ */
+public abstract class Datatype extends HObject implements MetaDataContainer {
+
+ private static final long serialVersionUID = -581324710549963177L;
+
+ private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(Datatype.class);
+
+ /**
+ * The default definition for datatype size, order, and sign.
+ */
+ public static final int NATIVE = -1;
+
+ /**
+ * See HDF5 User's Guide
+ */
+ public static final int CLASS_NO_CLASS = -1;
+
+ /**
+ * See HDF5 User's Guide
+ */
+ public static final int CLASS_INTEGER = 0;
+
+ /**
+ * See HDF5 User's Guide
+ */
+ public static final int CLASS_FLOAT = 1;
+
+ /**
+ * See HDF5 User's Guide
+ */
+ public static final int CLASS_CHAR = 2;
+
+ /**
+ * See HDF5 User's Guide
+ */
+ public static final int CLASS_STRING = 3;
+
+ /**
+ * See HDF5 User's Guide
+ */
+ public static final int CLASS_BITFIELD = 4;
+
+ /**
+ * See HDF5 User's Guide
+ */
+ public static final int CLASS_OPAQUE = 5;
+
+ /**
+ * See HDF5 User's Guide
+ */
+ public static final int CLASS_COMPOUND = 6;
+
+ /**
+ * See HDF5 User's Guide
+ */
+ public static final int CLASS_REFERENCE = 7;
+
+ /**
+ * See HDF5 User's Guide
+ */
+ public static final int CLASS_ENUM = 8;
+
+ /**
+ * See HDF5 User's Guide
+ */
+ public static final int CLASS_VLEN = 9;
+
+ /**
+ * See HDF5 User's Guide
+ */
+ public static final int CLASS_ARRAY = 10;
+
+ /**
+ * See HDF5 User's Guide
+ */
+ public static final int CLASS_TIME = 11;
+
+ /**
+ * See HDF5 User's Guide
+ */
+ public static final int ORDER_LE = 0;
+
+ /**
+ * See HDF5 User's Guide
+ */
+ public static final int ORDER_BE = 1;
+
+ /**
+ * See HDF5 User's Guide
+ */
+ public static final int ORDER_VAX = 2;
+
+ /**
+ * See HDF5 User's Guide
+ */
+ public static final int ORDER_NONE = 3;
+
+ /**
+ * See HDF5 User's Guide
+ */
+ public static final int SIGN_NONE = 0;
+
+ /**
+ * See HDF5 User's Guide
+ */
+ public static final int SIGN_2 = 1;
+
+ /**
+ * See HDF5 User's Guide
+ */
+ public static final int NSGN = 2;
+
+ protected String datatypeDescription = null;
+
+ /**
+ * The class of the datatype.
+ */
+ protected int datatypeClass;
+
+ /**
+ * The size (in bytes) of the datatype.
+ */
+ protected long datatypeSize;
+
+ /**
+ * The byte order of the datatype. Valid values are ORDER_LE, ORDER_BE, and
+ * ORDER_VAX.
+ */
+ protected int datatypeOrder;
+
+ /**
+ * The sign of the datatype.
+ */
+ protected int datatypeSign;
+
+ /**
+ * The base datatype of this datatype (null if this datatype is atomic).
+ */
+ protected Datatype baseType;
+
+ /**
+ * The dimensions of the ARRAY element of an ARRAY datatype.
+ */
+ protected long[] arrayDims;
+
+ /**
+ * Determines whether this datatype is a variable-length type.
+ */
+ protected boolean isVLEN = false;
+ protected boolean isVariableStr = false;
+
+ /**
+ * The (name, value) pairs of enum members.
+ */
+ protected Map
+ * The following is a list of a few examples of Datatype.
+ *
+ * The following is a list of a few examples of Datatype.
+ *
+ * The following is a list of a few examples of Datatype.
+ *
+ * For example, if the datatype identifier is a 32-bit unsigned integer created from HDF5,
+ *
+ *
+ * For example, if the datatype identifier is a 32-bit unsigned integer created from HDF5,
+ *
+ *
+ * Sub-classes must replace this default implementation.
+ *
+ * @param id
+ * the datatype identifier to close.
+ */
+ @Override
+ public abstract void close(long id);
+
+ /**
+ * Returns the class of the datatype. Valid values are:
+ *
+ * For example, in a dataset of type ARRAY of integer, the datatype of the dataset is ARRAY. The
+ * datatype of the base type is integer.
+ *
+ * @return the datatype of the contained basetype.
+ */
+ public Datatype getDatatypeBase() {
+ return baseType;
+ }
+
+ /**
+ * Sets the (key, value) pairs of enum members for enum datatype.
+ *
+ * For Example,
+ *
+ * For Example,
+ *
+ * For example, a HDF5 datatype created from
+ * Sub-classes must implement it so that this datatype will be converted accordingly.
+ *
+ * For example, if the type identifier is a 64-bit unsigned integer created from HDF5,
+ *
+ *
+ * FileFormat is a pluggable component. New implementing classes of FileFormat
+ * can be added to the list of supported file formats. Current implementing
+ * classes include H5File and H4File. By default, H5File and H4File are added to
+ * the list of supported file formats maintained by the static FileFormat
+ * instance.
+ *
+ *
+ * A FileFormat instance may exist without being associated with a given file. A
+ * FileFormat instance may be associated with a file that is not open for
+ * access. Most typically, a FileFormat instance is used to open the associated
+ * file and perform operations such as retrieval and manipulation (if the file
+ * access is read-write) of the file structure and objects.
+ *
+ * @author Peter X. Cao
+ * @version 2.4 9/4/2007
+ */
+public abstract class FileFormat extends File {
+ private static final long serialVersionUID = -4700692313888420796L;
+
+ private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(FileFormat.class);
+
+ /***************************************************************************
+ * File access flags used in calls to createInstance( String, flag );
+ **************************************************************************/
+
+ /**
+ * File first time access flag for open file. With this access flag, added
+ * to the regular value, indicates this file has no existing state.
+ *
+ */
+ public static final int OPEN_NEW = 1;
+
+ /**
+ * File access flag for read-only permission. With this access flag,
+ * modifications to the file will not be allowed.
+ *
+ * @see #createInstance(String, int )
+ */
+ public static final int READ = 2;
+
+ /**
+ * File access flag for read/write permission. With this access flag,
+ * modifications to the file will be allowed. Behavior if the file does not
+ * exist or cannot be opened for read/write access depends on the
+ * implementing class.
+ *
+ * @see #createInstance(String, int)
+ */
+ public static final int WRITE = 4;
+
+ /**
+ * File access flag for creating/truncating with read-write permission. If
+ * the file already exists, it will be truncated when opened. With this
+ * access flag, modifications to the file will be allowed. Behavior if file
+ * can't be created, or if it exists but can't be opened for read/write
+ * access, depends on the implementing class.
+ *
+ * @see #createInstance(String, int )
+ */
+ public static final int CREATE = 8;
+
+ /***************************************************************************
+ * File creation flags used in calls to createFile( String, flag );
+ **************************************************************************/
+
+ /**
+ * Flag for creating/truncating a file. If the file already exists, it will
+ * be truncated when opened. If the file does not exist, it will be created.
+ * Modifications to the file will be allowed.
+ *
+ * @see #createFile(String, int )
+ */
+ public static final int FILE_CREATE_DELETE = 10;
+
+ /**
+ * Flag for creating/opening a file. If the file already exists, it will be
+ * opened without changing the existing contents. If the file does not
+ * exist, it will be created. Modifications to the file will be allowed.
+ *
+ * @see #createFile(String, int )
+ */
+ public static final int FILE_CREATE_OPEN = 20;
+
+ /**
+ * Flag to indicate if the earliest version of library is used when creating
+ * a new file.
+ *
+ * @see #createFile(String, int )
+ */
+ public static final int FILE_CREATE_EARLY_LIB = 40;
+
+
+ /***************************************************************************
+ * Keys and fields related to supported file formats.
+ **************************************************************************/
+
+ /** Key for HDF4 file format. */
+ public static final String FILE_TYPE_HDF4 = "HDF4";
+
+ /** Key for HDF5 file format. */
+ public static final String FILE_TYPE_HDF5 = "HDF5";
+
+ /** Key for NetCDF file format. */
+ public static final String FILE_TYPE_NC3 = "NetCDF3";
+
+ /**
+ * A separator that separates file name and object name.
+ *
+ * @see FileFormat#getHObject(String)
+ */
+ public static final String FILE_OBJ_SEP = "://";
+
+ /**
+ * FileList keeps a list of supported FileFormats. This list can be updated
+ * and queried at runtime.
+ *
+ * @see #addFileFormat(String,FileFormat)
+ * @see #getFileFormat(String)
+ * @see #getFileFormatKeys()
+ * @see #getFileFormats()
+ * @see #removeFileFormat(String)
+ */
+ private static final Map
+ * The filename in this method call is equivalent to the pathname in the
+ * java.io.File class. The filename is converted into an abstract pathname
+ * by the File class.
+ *
+ * Typically this constructor is not called directly, but is called by a
+ * constructor of an implementing class. Applications most frequently use
+ * the createFile(), createInstance(), or getInstance()
+ * methods to generate a FileFormat instance with an associated filename.
+ *
+ * The file is not opened by this call. The read-only flag is set to false
+ * by this call.
+ *
+ * @param filename
+ * The filename; a pathname string.
+ * @throws NullPointerException
+ * If the
+ * This method allows a new FileFormat, tagged with an identifying key, to
+ * be added dynamically to the list of supported File Formats. Using it,
+ * applications can add new File Formats at runtime.
+ *
+ * For example, to add a new File Format with the key "xyz" that is
+ * implemented by the class xyzFile in the package companyC.files, an
+ * application would make the following calls:
+ *
+ *
+ * If either
+ * This method returns a FileFormat instance, as identified by an
+ * identifying key, from the list of supported File Formats.
+ *
+ * If the specified key is in the list of supported formats, the instance of
+ * the associated FileFormat object is returned. If the specified key is not
+ * in the list of supported formats,
+ * This method returns an Enumeration containing the unique keys (Strings)
+ * for the all File Formats in the list of supported File Formats.
+ *
+ * @return An Enumeration of keys that are in the list of supported formats.
+ * @see #addFileFormat(String,FileFormat)
+ * @see #getFileFormat(String)
+ * @see #getFileFormats()
+ * @see #removeFileFormat(String)
+ */
+ @SuppressWarnings("rawtypes")
+ public static final Enumeration getFileFormatKeys() {
+ return ((Hashtable) FileList).keys();
+ }
+
+ /**
+ * Returns an array of supported FileFormat instances.
+ *
+ * This method returns an array of FileFormat instances that appear in the
+ * list of supported File Formats.
+ *
+ * If the list of supported formats is empty,
+ * This method removes a FileFormat, as identified by the specified key,
+ * from the list of supported File Formats.
+ *
+ * If the specified key is in the list of supported formats, the instance of
+ * the FileFormat object that is being removed from the list is returned. If
+ * the key is not in the list of supported formats,
+ * Multiple extensions can be included in the single parameter if they are
+ * separated by commas.
+ *
+ * The list of file extensions updated by this call is not linked with
+ * supported formats that implement FileFormat objects. The file extension
+ * list is maintained for the benefit of applications that may choose to
+ * recognize only those files with extensions that appear in the list of
+ * file extensions for supported file formats.
+ *
+ * By default, the file extensions list includes: "hdf, h4, hdf5, h5"
+ *
+ * @param extension
+ * The file extension(s) to add.
+ * @see #addFileFormat(String,FileFormat)
+ * @see #getFileExtensions()
+ */
+ public static final void addFileExtension(String extension) {
+ if ((extensions == null) || (extensions.length() <= 0)) {
+ extensions = extension;
+ }
+
+ StringTokenizer currentExt = new StringTokenizer(extensions, ",");
+ Vector
+ * The extensions in the returned String are separates by commas:
+ * "hdf, h4, hdf5, h5"
+ *
+ * It is the responsibility of the application to update the file extension
+ * list using {@link #addFileExtension(String)} when new FileFormat
+ * implementations are added.
+ *
+ * @return A list of file extensions for all supported file formats.
+ * @see #addFileExtension(String)
+ */
+ public static final String getFileExtensions() {
+ return extensions;
+ }
+
+ /**
+ * Creates a FileFormat instance for the specified file.
+ *
+ * This method checks the list of supported file formats to find one that
+ * matches the format of the specified file. If a match is found, the method
+ * returns an instance of the associated FileFormat object. If no match is
+ * found,
+ * For example, if "test_hdf5.h5" is an HDF5 file,
+ * FileFormat.getInstance("test_hdf5.h5") will return an instance of H5File.
+ *
+ * The file is not opened as part of this call. Read/write file access is
+ * associated with the FileFormat instance if the matching file format
+ * supports read/write access. Some file formats only support read access.
+ *
+ * @param filename
+ * A valid file name, with a relative or absolute path.
+ * @return An instance of the matched FileFormat;
+ * The implementing FileFormat classes have freedom in how they obtain or
+ * generate the version number that is returned by this method. The H5File
+ * and H4File implementations query the underlying HDF libraries and return
+ * the reported version numbers. Other implementing classes may generate the
+ * version string directly within the called method.
+ *
+ * @return The library version.
+ */
+ public abstract String getLibversion();
+
+ /**
+ * Checks if the class implements the specified FileFormat.
+ *
+ * The Java "instanceof" operation is unable to check if an object is an
+ * instance of a FileFormat that is loaded at runtime. This method provides
+ * the "instanceof" functionality, and works for implementing classes that
+ * are loaded at runtime.
+ *
+ * This method lets applications that only access the abstract object layer
+ * determine the format of a given instance of the abstract class.
+ *
+ * For example, HDFView uses the following code to determine if a file is an
+ * HDF5 file:
+ *
+ *
+ * For example, if "test.h5" is an HDF5 file, the first call to isThisType()
+ * in the code fragment shown will return
+ * This method creates a file whose format is the same as that of the
+ * implementing class. An instance of the FileFormat implementing class is
+ * created and associated with the file. That instance is returned by the
+ * method.
+ *
+ * The filename in this method call is equivalent to the pathname in the
+ * java.io.File class. The filename is converted into an abstract pathname
+ * by the File class.
+ *
+ * A flag controls the behavior if the named file already exists. The flag
+ * values and corresponding behaviors are:
+ *
+ * If the flag is FILE_CREATE_DELETE, the method will create a new file or
+ * truncate an existing file. If the flag is FILE_CREATE_OPEN and the file
+ * does not exist, the method will create a new file.
+ *
+ * This method does not open the file for access, nor does it confirm that
+ * the file can later be opened read/write. The file open is carried out by
+ * the open() call.
+ *
+ * @param filename
+ * The filename; a pathname string.
+ * @param createFlag
+ * The creation flag, which determines behavior when the file
+ * already exists. Acceptable values are
+ *
+ * This method creates an instance of the FileFormat implementing class and
+ * sets the filename and file access parameters.
+ *
+ * The filename in this method call is equivalent to the pathname in the
+ * java.io.File class. The filename is converted into an abstract pathname
+ * by the File class.
+ *
+ * The access parameter values and corresponding behaviors at file open:
+ *
+ * Some FileFormat implementing classes may only support READ access and
+ * will use READ regardless of the value specified in the call. Refer to the
+ * implementing class documentation for details.
+ *
+ * This method does not open the file for access, nor does it confirm that
+ * the file can later be opened read/write or created. The file open is
+ * carried out by the open() call.
+ *
+ * Example (without exception handling):
+ *
+ *
+ * For example, "/samples/hdf5_test.h5". If there is no file associated with
+ * this FileFormat instance,
+ * This method returns true if the file access is read-only. If the file
+ * access is read-write, or if there is no file associated with the
+ * FileFormat instance, false will be returned.
+ *
+ * Note that this method may return true even if the file is not open for
+ * access when the method is called. The file access is set by the
+ * createFile(), createInstance(), or getInstance()
+ * call, and the file is opened for access by the open() call.
+ *
+ * @return True if the file access is read-only, otherwise returns false.
+ * @see #createFile(String, int)
+ * @see #createInstance(String, int)
+ * @see #getInstance(String)
+ * @see #open()
+ */
+ public final boolean isReadOnly() {
+ return isReadOnly;
+ }
+
+ /**
+ * Sets the maximum number of objects to be loaded into memory.
+ *
+ * Current Java applications, such as HDFView, cannot handle files with
+ * large numbers of objects due to JVM memory limitations. The maximum
+ * number limits the number of objects that will be loaded for a given
+ * FileFormat instance.
+ *
+ * The implementing FileFormat class has freedom in how it interprets the
+ * maximum number. H5File, for example, will load the maximum number of
+ * objects for each group in the file.
+ *
+ * @param n
+ * The maximum number of objects to be loaded into memory.
+ * @see #getMaxMembers()
+ * @see #setStartMembers(int)
+ */
+ public final void setMaxMembers(int n) {
+ max_members = n;
+ }
+
+ /**
+ * Returns the maximum number of objects that can be loaded into memory.
+ *
+ * @return The maximum number of objects that can be loaded into memory.
+ * @see #setMaxMembers(int)
+ */
+ public final int getMaxMembers() {
+ if (max_members<0)
+ return Integer.MAX_VALUE; // load the whole file
+
+ return max_members;
+ }
+
+ /**
+ * Sets the starting index of objects to be loaded into memory.
+ *
+ * The implementing FileFormat class has freedom in how it indexes objects
+ * in the file.
+ *
+ * @param idx
+ * The starting index of the object to be loaded into memory
+ * @see #getStartMembers()
+ * @see #setMaxMembers(int)
+ */
+ public final void setStartMembers(int idx) {
+ start_members = idx;
+ }
+
+ /**
+ * Returns the index of the starting object to be loaded into memory.
+ *
+ * @return The index of the starting object to be loaded into memory.
+ * @see #setStartMembers(int)
+ */
+ public final int getStartMembers() {
+ return start_members;
+ }
+
+ /**
+ * Returns the number of objects in memory.
+ *
+ * This method returns the total number of objects loaded into memory for
+ * this FileFormat instance. The method counts the objects that are loaded,
+ * which can take some time for a large number of objects.
+ *
+ * It is worth noting that the total number of objects in memory may be
+ * different than the total number of objects in the file.
+ *
+ * Since implementing classes have freedom in how they interpret and use the
+ * maximum number of members value, there may be differing numbers of
+ * objects in memory in different implementation instances, even with the
+ * same "use case".
+ *
+ * For example, say the use case is a file that contains 20,000 objects, the
+ * maximum number of members for an instance is 10,000, and the start member
+ * index is 1. There are 2 groups in the file. The root group contains
+ * 10,500 objects and the group "/g1" contains 9,500 objects.
+ *
+ * In an implementation that limits the total number of objects loaded to
+ * the maximum number of members, this method will return 10,000.
+ *
+ * In contrast, the H5File implementation loads up to the maximum number of
+ * members objects for each group in the file. So, with our use case 10,000
+ * objects will be loaded in the root group and 9,500 objects will be loaded
+ * into group "/g1". This method will return the value 19,500, which exceeds
+ * the maximum number of members value.
+ *
+ * @return The number of objects in memory.
+ * @see #getMaxMembers()
+ * @see #setMaxMembers(int)
+ * @see #getStartMembers()
+ * @see #setStartMembers(int)
+ */
+ public final int getNumberOfMembers() {
+ HObject rootObject = getRootObject();
+
+ // Account for root object
+ if (rootObject != null) return ((Group) rootObject).depthFirstMemberList().size() + 1;
+
+ return 0;
+ }
+
+ /***************************************************************************
+ * Abstract Instance methods
+ *
+ * These methods are related to the Implementing FileFormat class and to
+ * particular instances of objects with those classes.
+ **************************************************************************/
+
+ /**
+ * Opens file and returns a file identifier.
+ *
+ * This method uses the
+ * The method also loads the file structure and basic information (name,
+ * type) for data objects in the file into the FileFormat instance. It does
+ * not load the contents of any data object.
+ *
+ * The structure of the file is stored in a tree starting from the root
+ * object.
+ *
+ * @return File identifier if successful; otherwise -1.
+ * @throws Exception
+ * If the file cannot be opened. The exceptions thrown vary
+ * depending on the implementing class.
+ * @see #createFile(String, int)
+ * @see #createInstance(String, int)
+ * @see #getInstance(String)
+ * @see #getRootObject()
+ */
+ public abstract long open() throws Exception;
+
+ /**
+ * Closes file associated with this instance.
+ *
+ * This method closes the file associated with this FileFormat instance, as
+ * well as all objects associated with the file.
+ *
+ * @throws Exception
+ * If the file or associated objects cannot be closed. The
+ * exceptions thrown vary depending on the implementing class.
+ * @see #open()
+ */
+ public abstract void close() throws Exception;
+
+ // REVIEW DOCS for close()
+ // What if we try to close a file whose fid is -1? Does this set fid to -1?
+ // What if it's not open? What if no file? are structures & root object
+ // still loaded?
+ // Can we doc exceptions better or in implementation methods?
+
+ /**
+ * Returns the root object for the file associated with this instance.
+ *
+ * The root object is an HObject that represents the root group of a
+ * file. If the file has not yet been opened, or if there is no file
+ * associated with this instance,
+ * Starting from the root, applications can descend through the tree
+ * structure and navigate among the file's objects. In the tree structure,
+ * internal items represent non-empty groups. Leaf items represent datasets,
+ * named datatypes, or empty groups.
+ *
+ * @return The root object of the file, or
+ * This method returns the specified object from the file associated with
+ * this FileFormat instance.
+ *
+ * If the specified object is a group, groups and datasets that are members
+ * of the group will be accessible via the returned HObject instance. The
+ * exact contents of the returned HObject instance depends on whether or not
+ * {@link #open()} was called previously for this file.
+ *
+ * The decision to have different behaviors was made to give users some
+ * control over the "cost" of the method. In many cases, a user wants only
+ * one level of a tree, and the performance penalty for loading the entire
+ * hierarchy of objects in a large and complex file can be significant. In
+ * the case where open() has already been called, the HObject
+ * instances have already been created in memory and can be returned
+ * quickly. If open() has not been called, this method creates the
+ * HObject instances before returning the requested HObject.
+ *
+ * For example, say we have the following structure in our file:
+ *
+ *
+ * The following code creates a named datatype in a file.
+ *
+ *
+ * The following code creates a named datatype in a file.
+ *
+ *
+ * The following code creates an instance of H5Datatype in memory.
+ *
+ *
+ * The following code creates an instance of H5Datatype in memory.
+ *
+ *
+ * The following example creates a 2D integer dataset of size 100X50 at the root group in an HDF5
+ * file.
+ *
+ *
+ * The following example creates a compressed 2D compound dataset with size
+ * of 100X50 in a root group. The compound dataset has two members, x and y.
+ * Member x is an interger, member y is an 1-D float array of size 10.
+ *
+ *
+ * The following example creates a 2D image of size 100X50 in a root group.
+ *
+ *
+ * If the parent group is null, the new group will be created in the root
+ * group.
+ *
+ * @param name
+ * The name of the new group.
+ * @param parentGroup
+ * The parent group, or null.
+ *
+ * @return The new group if successful; otherwise returns null.
+ *
+ * @throws Exception
+ * The exceptions thrown vary depending on the implementing
+ * class.
+ */
+ public abstract Group createGroup(String name, Group parentGroup) throws Exception;
+
+ // REVIEW DOCS for createLink().
+ // Verify Implementing classes document these and also
+ // 'do the right thing' if fid is -1, currentObj is non-null, if
+ // object is null, or the root group then what? document & verify!
+
+ /**
+ * Creates a soft, hard or external link to an existing object in the open
+ * file.
+ *
+ * If parentGroup is null, the new link is created in the root group.
+ *
+ * @param parentGroup
+ * The group where the link is created.
+ * @param name
+ * The name of the link.
+ * @param currentObj
+ * The existing object the new link will reference.
+ * @param type
+ * The type of link to be created. It can be a hard link, a soft
+ * link or an external link.
+ *
+ * @return The object pointed to by the new link if successful; otherwise
+ * returns null.
+ *
+ * @throws Exception
+ * The exceptions thrown vary depending on the implementing
+ * class.
+ */
+ public HObject createLink(Group parentGroup, String name, HObject currentObj, int type) throws Exception {
+ return createLink(parentGroup, name, currentObj);
+ }
+
+ /**
+ * Creates a soft or external link to an object in a file that does not exist
+ * at the time the link is created.
+ *
+ * @param parentGroup
+ * The group where the link is created.
+ * @param name
+ * The name of the link.
+ * @param currentObj
+ * The name of the object the new link will reference. The object
+ * doesn't have to exist.
+ * @param type
+ * The type of link to be created.
+ *
+ * @return The H5Link object pointed to by the new link if successful;
+ * otherwise returns null.
+ *
+ * @throws Exception
+ * The exceptions thrown vary depending on the implementing
+ * class.
+ */
+ public HObject createLink(Group parentGroup, String name, String currentObj, int type) throws Exception {
+ return createLink(parentGroup, name, currentObj);
+ }
+
+ /**
+ * Copies the source object to a new destination.
+ *
+ * This method copies the source object to a destination group, and assigns
+ * the specified name to the new object.
+ *
+ * The copy may take place within a single file or across files. If the source
+ * object and destination group are in different files, the files must have
+ * the same file format (both HDF5 for example).
+ *
+ * The source object can be a group, a dataset, or a named datatype. This
+ * method copies the object along with all of its attributes and other
+ * properties. If the source object is a group, this method also copies all
+ * objects and sub-groups below the group.
+ *
+ * The following example shows how to use the copy method to create two
+ * copies of an existing HDF5 file structure in a new HDF5 file. One copy
+ * will be under /copy1 and the other under /copy2 in the new file.
+ *
+ *
+ * If an HDF(4&5) attribute exists in file, the method updates its value. If
+ * the attribute does not exist in file, it creates the attribute in file
+ * and attaches it to the object. It will fail to write a new attribute to
+ * the object where an attribute with the same name already exists. To
+ * update the value of an existing attribute in file, one needs to get the
+ * instance of the attribute by getMetadata(), change its values, and use
+ * writeAttribute() to write the value.
+ *
+ * @param obj
+ * The object to which the attribute is attached to.
+ * @param attr
+ * The atribute to attach.
+ * @param attrExisted
+ * The indicator if the given attribute exists.
+ *
+ * @throws Exception
+ * The exceptions thrown vary depending on the implementing class.
+ */
+ public abstract void writeAttribute(HObject obj, Attribute attr, boolean attrExisted) throws Exception;
+
+ // REVIEW DOCS for writeAttribute(). Check and document exceptions.
+
+ /***************************************************************************
+ * Deprecated methods.
+ **************************************************************************/
+
+ /**
+ * @deprecated As of 2.4, replaced by {@link #createFile(String, int)}
+ *
+ * The replacement method has an additional parameter that
+ * controls the behavior if the file already exists. Use
+ *
+ * The replacement method has additional parameters:
+ *
+ * To mimic the behavior originally provided by this method,
+ * call the replacement method with
+ * This static method, which as been deprecated, causes two
+ * problems:
+ *
+ * This static method, which as been deprecated, causes two
+ * problems:
+ *
+ * If the parent group is null, the new group will be created in the root
+ * group.
+ *
+ * @param name
+ * The name of a new group.
+ * @param pgroup
+ * The parent group object.
+ * @param gplist
+ * The group creation properties, in which the order of the
+ * properties conforms the HDF5 library API, H5Gcreate(), i.e.
+ * lcpl, gcpl and gapl, where
+ *
+ * If linkGroup is null, the new link is created in the root group.
+ *
+ * @param linkGroup
+ * The group where the link is created.
+ * @param name
+ * The name of the link.
+ * @param currentObj
+ * The existing object the new link will reference.
+ *
+ * @return The object pointed to by the new link if successful; otherwise
+ * returns null.
+ *
+ * @throws Exception
+ * The exceptions thrown vary depending on the implementing class.
+ */
+ public HObject createLink(Group linkGroup, String name, Object currentObj) throws Exception {
+ throw new UnsupportedOperationException("Unsupported operation. Subclasses must implement FileFormat:createLink.");
+ }
+
+ /**
+ * Export dataset.
+ *
+ * @param file_export_name
+ * The file name to export data into.
+ * @param file_name
+ * The name of the HDF5 file containing the dataset.
+ * @param object_path
+ * The full path of the dataset to be exported.
+ * @param binary_order
+ * The data byte order
+ *
+ * @throws Exception
+ * The exceptions thrown vary depending on the implementing class.
+ */
+ public void exportDataset(String file_export_name, String file_name, String object_path, int binary_order) throws Exception {
+ throw new UnsupportedOperationException("Unsupported operation. Subclasses must implement FileFormat:exportDataset.");
+ }
+
+ /**
+ * Renames an attribute.
+ *
+ * @param obj
+ * The object whose attribute is to be renamed.
+ * @param oldAttrName
+ * The current name of the attribute.
+ * @param newAttrName
+ * The new name of the attribute.
+ *
+ * @throws Exception
+ * The exceptions thrown vary depending on the implementing class.
+ */
+ public void renameAttribute(HObject obj, String oldAttrName, String newAttrName) throws Exception {
+ throw new UnsupportedOperationException("Unsupported operation. Subclasses must implement FileFormat:renameAttribute.");
+ }
+
+ /**
+ * Sets the bounds of new library versions.
+ *
+ * @param lowStr
+ * The earliest version of the library.
+ * @param highStr
+ * The latest version of the library.
+ *
+ * @throws Exception
+ * The exceptions thrown vary depending on the implementing class.
+ */
+ public void setNewLibBounds(String lowStr, String highStr) throws Exception {
+ throw new UnsupportedOperationException("Unsupported operation. Subclasses must implement FileFormat:setNewLibBounds.");
+ }
+
+ /**
+ * Sets the bounds of library versions.
+ *
+ * @param lowStr
+ * The earliest version of the library.
+ * @param highStr
+ * The latest version of the library.
+ *
+ * @throws Exception
+ * The exceptions thrown vary depending on the implementing class.
+ */
+ public void setLibBounds(String lowStr, String highStr) throws Exception {
+ throw new UnsupportedOperationException("Unsupported operation. Subclasses must implement FileFormat:setLibBounds.");
+ }
+
+ /**
+ * Gets the bounds of library versions
+ *
+ * @return The earliest and latest library versions in an int array.
+ *
+ * @throws Exception
+ * The exceptions thrown vary depending on the implementing class.
+ */
+ public int[] getLibBounds() throws Exception {
+ throw new UnsupportedOperationException("Unsupported operation. Subclasses must implement FileFormat:getLibBounds.");
+ }
+
+ public String getLibBoundsDescription() {
+ throw new UnsupportedOperationException("Unsupported operation. Subclasses must implement FileFormat:getLibBoundsDescription.");
+ }
+
+ public static int getIndexTypeValue(String strtype) {
+ throw new UnsupportedOperationException("Unsupported operation. Subclasses must implement FileFormat:getIndexTypeValue.");
+ }
+
+ public int getIndexType(String strtype) {
+ throw new UnsupportedOperationException("Unsupported operation. Subclasses must implement FileFormat:getIndexType.");
+ }
+
+ public void setIndexType(int indexType) {
+ throw new UnsupportedOperationException("Unsupported operation. Subclasses must implement FileFormat:setIndexType.");
+ }
+
+ public static int getIndexOrderValue(String strorder) {
+ throw new UnsupportedOperationException("Unsupported operation. Subclasses must implement FileFormat:getIndexOrderValue.");
+ }
+
+ public int getIndexOrder(String strorder) {
+ throw new UnsupportedOperationException("Unsupported operation. Subclasses must implement FileFormat:getIndexOrder.");
+ }
+
+ public void setIndexOrder(int indexOrder) {
+ throw new UnsupportedOperationException("Unsupported operation. Subclasses must implement FileFormat:setIndexOrder.");
+ }
+}
diff --git a/src/main/java/hdf/object/Group.java b/src/main/java/hdf/object/Group.java
new file mode 100644
index 0000000..016da33
--- /dev/null
+++ b/src/main/java/hdf/object/Group.java
@@ -0,0 +1,326 @@
+/*****************************************************************************
+ * Copyright by The HDF Group. *
+ * Copyright by the Board of Trustees of the University of Illinois. *
+ * All rights reserved. *
+ * *
+ * This file is part of the HDF Java Products distribution. *
+ * The full copyright notice, including terms governing use, modification, *
+ * and redistribution, is contained in the files COPYING and Copyright.html. *
+ * COPYING can be found at the root of the source code distribution tree. *
+ * Or, see https://support.hdfgroup.org/products/licenses.html *
+ * If you do not have access to either file, you may request a copy from *
+ * help@hdfgroup.org. *
+ ****************************************************************************/
+
+package hdf.object;
+
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Queue;
+import java.util.Stack;
+import java.util.Vector;
+
+/**
+ * Group is an abstract class. Current implementing classes are the H4Group and
+ * H5Group. This class includes general information of a group object such as
+ * members of a group and common operations on groups.
+ *
+ * Members of a group may include other groups, datasets or links.
+ *
+ * @version 1.1 9/4/2007
+ * @author Peter X. Cao
+ */
+public abstract class Group extends HObject implements MetaDataContainer {
+
+ private static final long serialVersionUID = 3913174542591568052L;
+
+ /**
+ * The list of members (Groups and Datasets) of this group in memory.
+ */
+ private List
+ * For example, in H5Group(h5file, "grp", "/groups/", pgroup), "grp" is the
+ * name of the group, "/groups/" is the group path of the group, and pgroup
+ * is the group where "grp" is located.
+ *
+ * @param theFile
+ * the file containing the group.
+ * @param grpName
+ * the name of this group, e.g. "grp01".
+ * @param grpPath
+ * the full path of this group, e.g. "/groups/".
+ * @param grpParent
+ * the parent of this group.
+ */
+ public Group(FileFormat theFile, String grpName, String grpPath, Group grpParent) {
+ this(theFile, grpName, grpPath, grpParent, null);
+ }
+
+ /**
+ * @deprecated Not for public use in the future.
+ * setName (String newName) changes the name of the group in memory and
+ * file.
+ *
+ * setName() updates the path in memory for all the objects that are under
+ * the group with the new name.
+ *
+ * @param newName
+ * The new name of the group.
+ *
+ * @throws Exception if the name can not be set
+ */
+ @Override
+ public void setName(String newName) throws Exception {
+ super.setName(newName);
+
+ if (memberList != null) {
+ int n = memberList.size();
+ HObject theObj = null;
+ for (int i = 0; i < n; i++) {
+ theObj = memberList.get(i);
+ theObj.setPath(this.getPath() + newName + HObject.SEPARATOR);
+ }
+ }
+ }
+
+ /** @return the parent group. */
+ public final Group getParent() {
+ return parent;
+ }
+
+ /**
+ * Checks if it is a root group.
+ *
+ * @return true if the group is a root group; otherwise, returns false.
+ */
+ public final boolean isRoot() {
+ return (parent == null);
+ }
+
+ /**
+ * Returns the total number of members of this group in file.
+ *
+ * Current Java applications such as HDFView cannot handle files with large
+ * numbers of objects (1,000,000 or more objects) due to JVM memory
+ * limitation. The max_members is used so that applications such as HDFView
+ * will load up to max_members number of objects. If the number of
+ * objects in file is larger than max_members, only
+ * max_members are loaded in memory.
+ *
+ * getNumberOfMembersInFile() returns the number of objects in this group.
+ * The number of objects in memory is obtained by getMemberList().size().
+ *
+ * @return Total number of members of this group in the file.
+ */
+ public int getNumberOfMembersInFile() {
+ return nMembersInFile;
+ }
+
+ /**
+ * Get the HObject at the specified index in this Group's member list.
+ * @param idx The index of the HObject to get.
+ * @return The HObject at the specified index.
+ */
+ public HObject getMember(int idx) {
+ if(memberList.size() <= 0 || idx >= memberList.size()) return null;
+
+ return memberList.get(idx);
+ }
+}
diff --git a/src/main/java/hdf/object/HObject.java b/src/main/java/hdf/object/HObject.java
new file mode 100644
index 0000000..e9a74da
--- /dev/null
+++ b/src/main/java/hdf/object/HObject.java
@@ -0,0 +1,562 @@
+/*****************************************************************************
+ * Copyright by The HDF Group. *
+ * Copyright by the Board of Trustees of the University of Illinois. *
+ * All rights reserved. *
+ * *
+ * This file is part of the HDF Java Products distribution. *
+ * The full copyright notice, including terms governing use, modification, *
+ * and redistribution, is contained in the files COPYING and Copyright.html. *
+ * COPYING can be found at the root of the source code distribution tree. *
+ * Or, see https://support.hdfgroup.org/products/licenses.html *
+ * If you do not have access to either file, you may request a copy from *
+ * help@hdfgroup.org. *
+ ****************************************************************************/
+
+package hdf.object;
+
+import java.io.Serializable;
+
+/**
+ * The HObject class is the root class of all the HDF data objects. Every data
+ * class has HObject as a superclass. All objects implement the methods of this
+ * class. The following is the inherited structure of HDF Objects.
+ *
+ *
+ * Warning: HDF4 and HDF5 may have multiple links to the same object. Data
+ * objects in this model do not deal with multiple links. Users may create
+ * duplicate copies of the same data object with different paths. Applications
+ * should check the OID of the data object to avoid duplicate copies of the same
+ * object.
+ *
+ * HDF4 objects are uniquely identified by the OID (tag_id, ref_id) pair. The
+ * ref_id is the object reference count. The tag_id is a pre-defined number to
+ * identify the type of object. For example, DFTAG_RI is for raster image,
+ * DFTAG_SD is for scientific dataset, and DFTAG_VG is for Vgroup.
+ *
+ * HDF5 objects are uniquely identified by the OID containing just the object
+ * reference. The OID is usually obtained by H5Rcreate(). The following example
+ * shows how to retrieve an object ID from a file:
+ *
+ *
+ * HDF4 objects are uniquely identified by a (tag_id, ref_id) pair. i.e.
+ * oid[0] = tag, oid[1] = ref_id.
+ * For example, in H5ScalarDS(h5file, "dset", "/arrays"), "dset" is the name
+ * of the dataset, "/arrays" is the group path of the dataset.
+ *
+ * @param theFile
+ * the file that contains the data object.
+ * @param theName
+ * the name of the data object, e.g. "dset".
+ * @param thePath
+ * the group path of the data object, e.g. "/arrays".
+ */
+ public HObject(FileFormat theFile, String theName, String thePath) {
+ this(theFile, theName, thePath, null);
+ }
+
+ /**
+ * Constructs an instance of a data object with specific name and path.
+ *
+ * For example, in H5ScalarDS(h5file, "dset", "/arrays"), "dset" is the name
+ * of the dataset, "/arrays" is the group path of the dataset.
+ *
+ * @param theFile
+ * the file that contains the data object.
+ * @param theName
+ * the name of the data object, e.g. "dset".
+ * @param thePath
+ * the group path of the data object, e.g. "/arrays".
+ * @param oid
+ * the ids of the data object.
+ */
+ @Deprecated
+ public HObject(FileFormat theFile, String theName, String thePath, long[] oid) {
+ this.fileFormat = theFile;
+ this.oid = oid;
+
+ if (fileFormat != null) {
+ this.filename = fileFormat.getFilePath();
+ }
+ else {
+ this.filename = null;
+ }
+
+ // file name is packed in the full path
+ if ((theName == null) && (thePath != null)) {
+ if (thePath.equals(SEPARATOR)) {
+ theName = SEPARATOR;
+ thePath = null;
+ }
+ else {
+ // the path must starts with "/"
+ if (!thePath.startsWith(HObject.SEPARATOR)) {
+ thePath = HObject.SEPARATOR + thePath;
+ }
+
+ // get rid of the last "/"
+ if (thePath.endsWith(HObject.SEPARATOR)) {
+ thePath = thePath.substring(0, thePath.length() - 1);
+ }
+
+ // separate the name and the path
+ theName = thePath.substring(thePath.lastIndexOf(SEPARATOR) + 1);
+ thePath = thePath.substring(0, thePath.lastIndexOf(SEPARATOR));
+ }
+ }
+ else if ((theName != null) && (thePath == null) && (theName.indexOf(SEPARATOR) >= 0)) {
+ if (theName.equals(SEPARATOR)) {
+ theName = SEPARATOR;
+ thePath = null;
+ }
+ else {
+ // the full name must starts with "/"
+ if (!theName.startsWith(SEPARATOR)) {
+ theName = SEPARATOR + theName;
+ }
+
+ // the fullname must not end with "/"
+ int n = theName.length();
+ if (theName.endsWith(SEPARATOR)) {
+ theName = theName.substring(0, n - 1);
+ }
+
+ int idx = theName.lastIndexOf(SEPARATOR);
+ if (idx < 0) {
+ thePath = SEPARATOR;
+ }
+ else {
+ thePath = theName.substring(0, idx);
+ theName = theName.substring(idx + 1);
+ }
+ }
+ }
+
+ // the path must start and end with "/"
+ if (thePath != null) {
+ thePath = thePath.replaceAll("//", "/");
+ if (!thePath.endsWith(SEPARATOR)) {
+ thePath += SEPARATOR;
+ }
+ }
+
+ this.name = theName;
+ this.path = thePath;
+
+ log.trace("name={} path={}", this.name, this.path);
+
+ if (thePath != null) {
+ this.fullName = thePath + theName;
+ }
+ else {
+ if (theName == null) {
+ this.fullName = "/";
+ }
+ else if (theName.startsWith("/")) {
+ this.fullName = theName;
+ }
+ else {
+ if (this instanceof Attribute)
+ this.fullName = theName;
+ else
+ this.fullName = "/" + theName;
+ }
+ }
+
+ log.trace("fullName={}", this.fullName);
+ }
+
+ /**
+ * Print out debug information
+ *
+ *
+ * @param msg
+ * the debug message to print
+ */
+ protected final void debug(Object msg) {
+ System.out.println("*** " + this.getClass().getName() + ": " + msg);
+ }
+
+ /**
+ * Returns the name of the file that contains this data object.
+ *
+ * The file name is necessary because the file of this data object is
+ * uniquely identified when multiple files are opened by an application at
+ * the same time.
+ *
+ * @return The full path (path + name) of the file.
+ */
+ public final String getFile() {
+ return filename;
+ }
+
+ /**
+ * Returns the name of the object. For example, "Raster Image #2".
+ *
+ * @return The name of the object.
+ */
+ public final String getName() {
+ return name;
+ }
+
+ /**
+ * Returns the name of the target object that is linked to.
+ *
+ * @return The name of the object that is linked to.
+ */
+ public final String getLinkTargetObjName() {
+ return linkTargetObjName;
+ }
+
+ /**
+ * Sets the name of the target object that is linked to.
+ *
+ * @param targetObjName
+ * The new name of the object.
+ */
+ public final void setLinkTargetObjName(String targetObjName) {
+ linkTargetObjName = targetObjName;
+ }
+
+ /**
+ * Returns the full name (group path + object name) of the object. For
+ * example, "/Images/Raster Image #2"
+ *
+ * @return The full name (group path + object name) of the object.
+ */
+ public final String getFullName() {
+ return fullName;
+ }
+
+ /**
+ * Returns the group path of the object. For example, "/Images".
+ *
+ * @return The group path of the object.
+ */
+ public final String getPath() {
+ return path;
+ }
+
+ /**
+ * Sets the name of the object.
+ *
+ * setName (String newName) changes the name of the object in the file.
+ *
+ * @param newName
+ * The new name of the object.
+ *
+ * @throws Exception if name is root or contains separator
+ */
+ public void setName(String newName) throws Exception {
+ if (newName != null) {
+ if (newName.equals(HObject.SEPARATOR)) {
+ throw new IllegalArgumentException("The new name cannot be the root");
+ }
+
+ if (newName.startsWith(HObject.SEPARATOR)) {
+ newName = newName.substring(1);
+ }
+
+ if (newName.endsWith(HObject.SEPARATOR)) {
+ newName = newName.substring(0, newName.length() - 2);
+ }
+
+ if (newName.contains(HObject.SEPARATOR)) {
+ throw new IllegalArgumentException("The new name contains the SEPARATOR character: " + HObject.SEPARATOR);
+ }
+ }
+
+ name = newName;
+ }
+
+ /**
+ * Sets the path of the object.
+ *
+ * setPath() is needed to change the path for an object when the name of a
+ * group containing the object is changed by setName(). The path of the
+ * object in memory under this group should be updated to the new path to
+ * the group. Unlike setName(), setPath() does not change anything in file.
+ *
+ * @param newPath
+ * The new path of the object.
+ *
+ * @throws Exception if a failure occurred
+ */
+ public void setPath(String newPath) throws Exception {
+ if (newPath == null) {
+ newPath = "/";
+ }
+
+ path = newPath;
+ }
+
+ /**
+ * Opens an existing object such as a dataset or group for access.
+ *
+ * The return value is an object identifier obtained by implementing classes
+ * such as H5.H5Dopen(). This function is needed to allow other objects to
+ * be able to access the object. For instance, H5File class uses the open()
+ * function to obtain object identifier for copyAttributes(long src_id, long
+ * dst_id) and other purposes. The open() function should be used in pair
+ * with close(long) function.
+ *
+ * @see HObject#close(long)
+ *
+ * @return the object identifier if successful; otherwise returns a negative
+ * value.
+ */
+ public abstract long open();
+
+ /**
+ * Closes access to the object.
+ *
+ * Sub-classes must implement this interface because different data objects
+ * have their own ways of how the data resources are closed.
+ *
+ * For example, H5Group.close() calls the hdf.hdf5lib.H5.H5Gclose()
+ * method and closes the group resource specified by the group id.
+ *
+ * @param id
+ * The object identifier.
+ */
+ public abstract void close(long id);
+
+ /**
+ * Returns the file identifier of of the file containing the object.
+ *
+ * @return the file identifier of of the file containing the object.
+ */
+ public final long getFID() {
+ if (fileFormat != null) {
+ return fileFormat.getFID();
+ }
+ else {
+ return -1;
+ }
+ }
+
+ /**
+ * Returns the file that contains the object.
+ *
+ * @return The file that contains the object.
+ */
+ public final FileFormat getFileFormat() {
+ return fileFormat;
+ }
+
+ /**
+ * Returns a cloned copy of the object identifier.
+ *
+ * The object OID cannot be modified once it is created. getOID() clones the object OID to ensure
+ * the object OID cannot be modified outside of this class.
+ *
+ * @return the cloned copy of the object OID.
+ */
+ public final long[] getOID() {
+ if (oid == null) {
+ return null;
+ }
+
+ return oid.clone();
+ }
+
+ /**
+ * Checks if the OID of the object is the same as the given object identifier within the same file.
+ *
+ * HDF4 and HDF5 data objects are identified by their unique OIDs. A data object in a file may have
+ * multiple logical names , which are represented in a graph structure as separate objects.
+ *
+ * The HObject.equalsOID(long[] theID) can be used to check if two data objects with different names
+ * are pointed to the same object within the same file.
+ *
+ * @param theID
+ * The list object identifiers.
+ *
+ * @return true if the ID of the object equals the given OID; otherwise, returns false.
+ */
+ public final boolean equalsOID(long[] theID) {
+ if ((theID == null) || (oid == null)) {
+ return false;
+ }
+
+ int n1 = theID.length;
+ int n2 = oid.length;
+
+ if (n1 == 0 || n2 == 0) {
+ return false;
+ }
+
+ int n = Math.min(n1, n2);
+ boolean isMatched = (theID[0] == oid[0]);
+
+ for (int i = 1; isMatched && (i < n); i++) {
+ isMatched = (theID[i] == oid[i]);
+ }
+
+ return isMatched;
+ }
+
+ /**
+ * Returns the name of the object.
+ *
+ * This method overwrites the toString() method in the Java Object class
+ * (the root class of all Java objects) so that it returns the name of the
+ * HObject instead of the name of the class.
+ *
+ * For example, toString() returns "Raster Image #2" instead of
+ * "hdf.object.h4.H4SDS".
+ *
+ * @return The name of the object.
+ */
+ @Override
+ public String toString() {
+ if (this instanceof Group) {
+ if (((Group) this).isRoot() && this.getFileFormat() != null) return this.getFileFormat().getName();
+ }
+
+ if (name != null) return name;
+
+ return super.toString();
+ }
+
+ /**
+ * Returns whether this HObject is equal to the specified HObject by comparing their OIDs.
+ *
+ * @param obj
+ * The object
+ *
+ * @return true if the object is equal by OID
+ */
+ public boolean equals(HObject obj) {
+ // Cast down to Object to avoid infinite recursion
+ if (this.equals((Object) obj))
+ return true;
+
+ // comparing the state of OID with
+ // the state of 'this' OID.
+ return this.equalsOID(obj.getOID());
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null)
+ return false;
+
+ // checking if both the object references are
+ // referring to the same object.
+ if (this == obj)
+ return true;
+
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ // We are returning the OID as a hashcode value.
+ return (int) oid[0];
+ }
+}
diff --git a/src/main/java/hdf/object/MetaDataContainer.java b/src/main/java/hdf/object/MetaDataContainer.java
new file mode 100644
index 0000000..e3897cb
--- /dev/null
+++ b/src/main/java/hdf/object/MetaDataContainer.java
@@ -0,0 +1,91 @@
+/*****************************************************************************
+ * Copyright by The HDF Group. *
+ * Copyright by the Board of Trustees of the University of Illinois. *
+ * All rights reserved. *
+ * *
+ * This file is part of the HDF Java Products distribution. *
+ * The full copyright notice, including terms governing use, modification, *
+ * and redistribution, is contained in the files COPYING and Copyright.html. *
+ * COPYING can be found at the root of the source code distribution tree. *
+ * Or, see https://support.hdfgroup.org/products/licenses.html *
+ * If you do not have access to either file, you may request a copy from *
+ * help@hdfgroup.org. *
+ ****************************************************************************/
+
+package hdf.object;
+
+import java.util.List;
+
+/**
+ * An interface that provides general I/O operations for object metadata
+ * attached to an object. For example, reading metadata content from the file
+ * into memory or writing metadata content from memory into the file.
+ *
+ *
+ * @see HObject
+ *
+ * @version 2.0 4/2/2018
+ * @author Peter X. Cao, Jordan T. Henderson
+ */
+@SuppressWarnings("rawtypes")
+public interface MetaDataContainer {
+ /**
+ * Retrieves the object's metadata, such as attributes, from the file.
+ *
+ * Metadata, such as attributes, is stored in a List.
+ *
+ * @return the list of metadata objects.
+ *
+ * @throws Exception
+ * if the metadata can not be retrieved
+ */
+ public abstract List getMetadata() throws Exception;
+
+ /**
+ * Writes a specific piece of metadata (such as an attribute) into the file.
+ *
+ * If an HDF(4&5) attribute exists in the file, this method updates its
+ * value. If the attribute does not exist in the file, it creates the
+ * attribute in the file and attaches it to the object. It will fail to
+ * write a new attribute to the object where an attribute with the same name
+ * already exists. To update the value of an existing attribute in the file,
+ * one needs to get the instance of the attribute by getMetadata(), change
+ * its values, then use writeMetadata() to write the value.
+ *
+ * @param metadata
+ * the metadata to write.
+ *
+ * @throws Exception
+ * if the metadata can not be written
+ */
+ public abstract void writeMetadata(Object metadata) throws Exception;
+
+ /**
+ * Deletes an existing piece of metadata from this object.
+ *
+ * @param metadata
+ * the metadata to delete.
+ *
+ * @throws Exception
+ * if the metadata can not be removed
+ */
+ public abstract void removeMetadata(Object metadata) throws Exception;
+
+ /**
+ * Updates an existing piece of metadata attached to this object.
+ *
+ * @param metadata
+ * the metadata to update.
+ *
+ * @throws Exception
+ * if the metadata can not be updated
+ */
+ public abstract void updateMetadata(Object metadata) throws Exception;
+
+ /**
+ * Check if the object has any attributes attached.
+ *
+ * @return true if it has any attributes, false otherwise.
+ */
+ public abstract boolean hasAttribute();
+}
diff --git a/src/main/java/hdf/object/ScalarDS.java b/src/main/java/hdf/object/ScalarDS.java
new file mode 100644
index 0000000..1af9962
--- /dev/null
+++ b/src/main/java/hdf/object/ScalarDS.java
@@ -0,0 +1,450 @@
+/*****************************************************************************
+ * Copyright by The HDF Group. *
+ * Copyright by the Board of Trustees of the University of Illinois. *
+ * All rights reserved. *
+ * *
+ * This file is part of the HDF Java Products distribution. *
+ * The full copyright notice, including terms governing use, modification, *
+ * and redistribution, is contained in the files COPYING and Copyright.html. *
+ * COPYING can be found at the root of the source code distribution tree. *
+ * Or, see https://support.hdfgroup.org/products/licenses.html *
+ * If you do not have access to either file, you may request a copy from *
+ * help@hdfgroup.org. *
+ ****************************************************************************/
+
+package hdf.object;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Vector;
+
+/**
+ * A scalar dataset is a multiple dimension array of scalar points. The Datatype of a scalar dataset must be an atomic
+ * datatype. Common datatypes of scalar datasets include char, byte, short, int, long, float, double and string.
+ *
+ * A ScalarDS can be an image or spreadsheet data. ScalarDS defines methods to deal with both images and
+ * spreadsheets.
+ *
+ * ScalarDS is an abstract class. Current implementing classes are the H4SDS, H5GRImage and H5ScalarDS.
+ *
+ * @version 1.1 9/4/2007
+ * @author Peter X. Cao
+ */
+public abstract class ScalarDS extends Dataset {
+ private static final long serialVersionUID = 8925371455928203981L;
+
+ private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ScalarDS.class);
+
+ /************************************************************
+ * The following constant strings are copied from *
+ * https://support.hdfgroup.org/HDF5/doc/ADGuide/ImageSpec.html *
+ * to make the definition consistent with the image specs. *
+ ************************************************************/
+
+ /**
+ * Indicates that the pixel RGB values are contiguous.
+ */
+ public static final int INTERLACE_PIXEL = 0;
+
+ /** Indicates that each pixel component of RGB is stored as a scan line. */
+ public static final int INTERLACE_LINE = 1;
+
+ /** Indicates that each pixel component of RGB is stored as a plane. */
+ public static final int INTERLACE_PLANE = 2;
+
+ /**
+ * The interlace mode of the stored raster image data. Valid values are INTERLACE_PIXEL, INTERLACE_LINE and
+ * INTERLACE_PLANE.
+ */
+ protected int interlace;
+
+ /**
+ * The min-max range of image data values. For example, [0, 255] indicates the min is 0, and the max is 255.
+ */
+ protected double[] imageDataRange;
+
+ /**
+ * The indexed RGB color model with 256 colors.
+ *
+ * The palette values are stored in a two-dimensional byte array and arrange by color components of red, green and
+ * blue. palette[][] = byte[3][256], where, palette[0][], palette[1][] and palette[2][] are the red, green and blue
+ * components respectively.
+ */
+ protected byte[][] palette;
+
+ /**
+ * True if this dataset is an image.
+ */
+ protected boolean isImage;
+
+ /**
+ * True if this dataset is a true color image.
+ */
+ protected boolean isTrueColor;
+
+ /**
+ * True if this dataset is ASCII text.
+ */
+ protected boolean isText;
+
+ /**
+ * Flag to indicate is the original unsigned C data is converted.
+ */
+ protected boolean unsignedConverted;
+
+ /** The fill value of the dataset. */
+ protected Object fillValue = null;
+
+ private List
+ * For example, in H5ScalarDS(h5file, "dset", "/arrays/"), "dset" is the name of the dataset, "/arrays" is the group
+ * path of the dataset.
+ *
+ * @param theFile
+ * the file that contains the data object.
+ * @param theName
+ * the name of the data object, e.g. "dset".
+ * @param thePath
+ * the full path of the data object, e.g. "/arrays/".
+ */
+ public ScalarDS(FileFormat theFile, String theName, String thePath) {
+ this(theFile, theName, thePath, null);
+ }
+
+ /**
+ * @deprecated Not for public use in the future.
+ * A Scalar dataset can be displayed as spreadsheet data or an image. When a scalar dataset is displayed as an
+ * image, the palette or color table may be needed to translate a pixel value to color components (for example, red,
+ * green, and blue). Some scalar datasets have no palette and some datasets have one or more than one palettes. If
+ * an associated palette exists but is not loaded, this interface retrieves the palette from the file and returns the
+ * palette. If the palette is loaded, it returns the palette. It returns null if there is no palette associated with
+ * the dataset.
+ *
+ * Current implementation only supports palette model of indexed RGB with 256 colors. Other models such as
+ * YUV", "CMY", "CMYK", "YCbCr", "HSV will be supported in the future.
+ *
+ * The palette values are stored in a two-dimensional byte array and are arranges by color components of red, green and
+ * blue. palette[][] = byte[3][256], where, palette[0][], palette[1][] and palette[2][] are the red, green and blue
+ * components respectively.
+ *
+ * Sub-classes have to implement this interface. HDF4 and HDF5 images use different libraries to retrieve the
+ * associated palette.
+ *
+ * @return the 2D palette byte array.
+ */
+ public abstract byte[][] getPalette();
+
+ /**
+ * Sets the palette for this dataset.
+ *
+ * @param pal
+ * the 2D palette byte array.
+ */
+ public final void setPalette(byte[][] pal) {
+ palette = pal;
+ }
+
+ /**
+ * Reads a specific image palette from file.
+ *
+ * A scalar dataset may have multiple palettes attached to it. readPalette(int idx) returns a specific palette
+ * identified by its index.
+ *
+ * @param idx
+ * the index of the palette to read.
+ *
+ * @return the image palette
+ */
+ public abstract byte[][] readPalette(int idx);
+
+ /**
+ * Get the name of a specific image palette from file.
+ *
+ * A scalar dataset may have multiple palettes attached to it. getPaletteName(int idx) returns the name of a
+ * specific palette identified by its index.
+ *
+ * @param idx
+ * the index of the palette to retrieve the name.
+ *
+ * @return The name of the palette
+ */
+ public String getPaletteName(int idx) {
+ String paletteName = "Default ";
+ if (idx != 0)
+ paletteName = "Default " + idx;
+ return paletteName;
+ }
+
+ /**
+ * Returns the byte array of palette refs.
+ *
+ * A palette reference is an object reference that points to the palette dataset.
+ *
+ * For example, Dataset "Iceberg" has an attribute of object reference "Palette". The arrtibute "Palette" has value
+ * "2538" that is the object reference of the palette data set "Iceberg Palette".
+ *
+ * @return null if there is no palette attribute attached to this dataset.
+ */
+ public abstract byte[] getPaletteRefs();
+
+ /**
+ * Returns true if this dataset is an image.
+ *
+ * For all Images, they must have an attribute called "CLASS". The value of this attribute is "IMAGE". For more
+ * details, read HDF5 Image and Palette Specification
+ *
+ * @return true if the dataset is an image; otherwise, returns false.
+ */
+ public final boolean isImage() {
+ return isImage;
+ }
+
+ /**
+ * Returns true if this dataset is displayed as an image.
+ *
+ * A ScalarDS can be displayed as an image or a spreadsheet in a table.
+ *
+ * @return true if this dataset is displayed as an image; otherwise, returns false.
+ */
+ public final boolean isImageDisplay() {
+
+ return isImageDisplay;
+ }
+
+ /**
+ * Returns true if this dataset is displayed as an image with default image order.
+ *
+ * A ScalarDS can be displayed as an image with different orders of dimensions.
+ *
+ * @return true if this dataset is displayed as an image with default image order; otherwise, returns false.
+ */
+ public final boolean isDefaultImageOrder() {
+ return isDefaultImageOrder;
+ }
+
+ /**
+ * Sets the flag to display the dataset as an image.
+ *
+ * @param b
+ * if b is true, display the dataset as an image
+ */
+ public final void setIsImageDisplay(boolean b) {
+ isImageDisplay = b;
+ }
+
+ /**
+ * Sets the flag to indicate this dataset is an image.
+ *
+ * @param b
+ * if b is true, the dataset is an image.
+ */
+ public final void setIsImage(boolean b) {
+ isImage = b;
+ }
+
+ /**
+ * Sets data range for an image.
+ *
+ * @param min
+ * the data range start.
+ * @param max
+ * the data range end.
+ */
+ public final void setImageDataRange(double min, double max) {
+ if (max <= min)
+ return;
+
+ if (imageDataRange == null)
+ imageDataRange = new double[2];
+
+ imageDataRange[0] = min;
+ imageDataRange[1] = max;
+ }
+
+ /**
+ * Add a value that will be filtered out in an image.
+ *
+ * @param x
+ * value to be filtered
+ */
+ public void addFilteredImageValue(Number x) {
+ Iterator
+ * An HDF5 dataset is an object composed of a collection of data elements, or raw data, and metadata
+ * that stores a description of the data elements, data layout, and all other information necessary
+ * to write, read, and interpret the stored data.
+ *
+ * A HDF5 compound datatype is similar to a struct in C or a common block in Fortran: it is a
+ * collection of one or more atomic types or small arrays of such types. Each member of a compound
+ * type has a name which is unique within that type, and a byte offset that determines the first
+ * byte (smallest byte address) of that member in a compound datum.
+ *
+ * For more information on HDF5 datasets and datatypes, read the HDF5
+ * User's Guide.
+ *
+ * There are two basic types of compound datasets: simple compound data and nested compound data.
+ * Members of a simple compound dataset have atomic datatypes. Members of a nested compound dataset
+ * are compound or array of compound data.
+ *
+ * Since Java does not understand C structures, we cannot directly read/write compound data values
+ * as in the following C example.
+ *
+ *
+ * The nested names are separated by CompoundDS.SEPARATOR. For example, if compound dataset "A" has
+ * the following nested structure,
+ *
+ *
+ * The dataset object represents an existing dataset in the file. For example, new
+ * H5CompoundDS(file, "dset1", "/g0/") constructs a dataset object that corresponds to the
+ * dataset,"dset1", at group "/g0/".
+ *
+ * This object is usually constructed at FileFormat.open(), which loads the file structure and
+ * object information into memory. It is rarely used elsewhere.
+ *
+ * @param theFile
+ * the file that contains the data object.
+ * @param theName
+ * the name of the data object, e.g. "dset".
+ * @param thePath
+ * the full path of the data object, e.g. "/arrays/".
+ */
+ public H5CompoundDS(FileFormat theFile, String theName, String thePath) {
+ this(theFile, theName, thePath, null);
+ }
+
+ /**
+ * @deprecated Not for public use in the future.
+ * The init() is designed to support lazy operation in a dataset object. When a
+ * data object is retrieved from file, the datatype, dataspace and raw data are
+ * not loaded into memory. When it is asked to read the raw data from file,
+ * init() is first called to get the datatype and dataspace information, then
+ * load the raw data from file.
+ *
+ * init() is also used to reset the selection of a dataset (start, stride and
+ * count) to the default, which is the entire dataset for 1D or 2D datasets. In
+ * the following example, init() at step 1) retrieves datatype and dataspace
+ * information from file. getData() at step 3) reads only one data point. init()
+ * at step 4) resets the selection to the whole dataset. getData() at step 4)
+ * reads the values of whole dataset into memory.
+ *
+ *
+ * The data buffer is a vector that contains the data values of compound fields. The data is written
+ * into file field by field.
+ *
+ * @param buf
+ * The vector that contains the data values of compound fields.
+ *
+ * @throws Exception
+ * If there is an error at the HDF5 library level.
+ */
+ @Override
+ public void write(Object buf) throws Exception {
+ log.trace("write(): start");
+
+ if (this.getFileFormat().isReadOnly())
+ throw new Exception("cannot write to compound dataset in file opened as read-only");
+
+ if (!isInited())
+ init();
+
+ try {
+ compoundDatasetCommonIO(IO_TYPE.WRITE, buf);
+ }
+ catch (Exception ex) {
+ log.debug("write(): failed to write compound dataset: ", ex);
+ throw new Exception("failed to write compound dataset: " + ex.getMessage(), ex);
+ }
+
+ log.trace("write(): finish");
+ }
+
+ private Object compoundDatasetCommonIO(IO_TYPE ioType, Object writeBuf) throws Exception {
+ log.trace("compoundDatasetCommonIO(): start");
+
+ H5Datatype dsDatatype = (H5Datatype) getDatatype();
+ Object data = null;
+
+ if (numberOfMembers <= 0) {
+ log.debug("compoundDatasetCommonIO(): Dataset contains no members");
+ log.trace("compoundDatasetCommonIO(): exit");
+ throw new Exception("dataset contains no members");
+ }
+
+ /*
+ * I/O type-specific pre-initialization.
+ */
+ if (ioType == IO_TYPE.WRITE) {
+ if ((writeBuf == null) || !(writeBuf instanceof List)) {
+ log.debug("compoundDatasetCommonIO(): writeBuf is null or invalid");
+ log.trace("compoundDatasetCommonIO(): exit");
+ throw new Exception("write buffer is null or invalid");
+ }
+
+ /*
+ * Check for any unsupported datatypes and fail early before
+ * attempting to write to the dataset.
+ */
+ if (dsDatatype.isArray() && dsDatatype.getDatatypeBase().isCompound()) {
+ log.debug("compoundDatasetCommonIO(): cannot write dataset of type ARRAY of COMPOUND");
+ log.trace("compoundDatasetCommonIO(): finish");
+ throw new HDF5Exception("Unsupported dataset of type ARRAY of COMPOUND");
+ }
+
+ if (dsDatatype.isVLEN() && dsDatatype.getDatatypeBase().isCompound()) {
+ log.debug("compoundDatasetCommonIO(): cannot write dataset of type VLEN of COMPOUND");
+ log.trace("compoundDatasetCommonIO(): finish");
+ throw new HDF5Exception("Unsupported dataset of type VLEN of COMPOUND");
+ }
+ }
+
+ log.trace("compoundDatasetCommonIO(): open dataset");
+
+ long did = open();
+ if (did >= 0) {
+ long[] spaceIDs = { -1, -1 }; // spaceIDs[0]=mspace, spaceIDs[1]=fspace
+
+ try {
+ /*
+ * NOTE: this call sets up a hyperslab selection in the file according to the
+ * current selection in the dataset object.
+ */
+ long totalSelectedSpacePoints = H5Utils.getTotalSelectedSpacePoints(did, dims, startDims,
+ selectedStride, selectedDims, spaceIDs);
+
+ data = compoundTypeIO(ioType, did, spaceIDs, (int) totalSelectedSpacePoints, dsDatatype, writeBuf, new int[]{0});
+ }
+ finally {
+ if (HDF5Constants.H5S_ALL != spaceIDs[0]) {
+ try {
+ H5.H5Sclose(spaceIDs[0]);
+ }
+ catch (Exception ex) {
+ log.debug("compoundDatasetCommonIO(): H5Sclose(spaceIDs[0] {}) failure: ", spaceIDs[0], ex);
+ }
+ }
+
+ if (HDF5Constants.H5S_ALL != spaceIDs[1]) {
+ try {
+ H5.H5Sclose(spaceIDs[1]);
+ }
+ catch (Exception ex) {
+ log.debug("compoundDatasetCommonIO(): H5Sclose(spaceIDs[1] {}) failure: ", spaceIDs[1], ex);
+ }
+ }
+
+ close(did);
+ }
+ }
+ else
+ log.debug("compoundDatasetCommonIO(): failed to open dataset");
+
+ log.trace("compoundDatasetCommonIO(): finish");
+
+ return data;
+ }
+
+ /*
+ * Private recursive routine to read/write an entire compound datatype field by
+ * field. This routine is called recursively for ARRAY of COMPOUND and VLEN of
+ * COMPOUND datatypes.
+ *
+ * NOTE: the globalMemberIndex hack is ugly, but we need to keep track of a
+ * running counter so that we can index properly into the flattened name list
+ * generated from H5Datatype.extractCompoundInfo() at dataset init time.
+ */
+ private Object compoundTypeIO(IO_TYPE ioType, long did, long[] spaceIDs, int nSelPoints, final H5Datatype cmpdType,
+ Object writeBuf, int[] globalMemberIndex) {
+ log.trace("compoundTypeIO(): start");
+
+ Object theData = null;
+
+ if (cmpdType.isArray()) {
+ log.trace("compoundTypeIO(): ARRAY type");
+
+ long[] arrayDims = cmpdType.getArrayDims();
+ int arrSize = nSelPoints;
+ for (int i = 0; i < arrayDims.length; i++) {
+ arrSize *= arrayDims[i];
+ }
+
+ theData = compoundTypeIO(ioType, did, spaceIDs, arrSize, (H5Datatype) cmpdType.getDatatypeBase(), writeBuf, globalMemberIndex);
+ }
+ else if (cmpdType.isVLEN() && !cmpdType.isVarStr()) {
+ /*
+ * TODO: true variable-length support.
+ */
+ String[] errVal = new String[nSelPoints];
+ String errStr = "*UNSUPPORTED*";
+
+ for (int j = 0; j < nSelPoints; j++)
+ errVal[j] = errStr;
+
+ /*
+ * Setup a fake data list.
+ */
+ Datatype baseType = cmpdType.getDatatypeBase();
+ while (baseType != null && !baseType.isCompound()) {
+ baseType = baseType.getDatatypeBase();
+ }
+
+ List
+ * comp --> m01 (int)
+ * comp --> m02 (float)
+ * comp --> nest1 --> m11 (char)
+ * comp --> nest1 --> m12 (String)
+ * comp --> nest1 --> nest2 --> m21 (long)
+ * comp --> nest1 --> nest2 --> m22 (double)
+ *
+ *
+ * The data object is a Java list of six arrays: {int[], float[], char[],
+ * Stirng[], long[] and double[]}.
+ *
+ *
+ * @version 1.1 9/4/2007
+ * @author Peter X. Cao
+ */
+public abstract class CompoundDS extends Dataset implements CompoundDataFormat {
+ private static final long serialVersionUID = -4880399929644095662L;
+
+ private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(CompoundDS.class);
+
+ /**
+ * A single character to separate the names of nested compound fields. An
+ * extended ASCII character, 0x95, is used to avoid common characters in
+ * compound names.
+ */
+ public static final String SEPARATOR = "\u0095";
+
+ /**
+ * The number of members of the compound dataset.
+ */
+ protected int numberOfMembers;
+
+ /**
+ * The names of members of the compound dataset.
+ */
+ protected String[] memberNames;
+
+ /**
+ * Returns array containing the total number of elements of the members of
+ * this compound dataset.
+ *
+ * COMP {
+ * int A;
+ * float B[5];
+ * double C[2][3];
+ * }
+ *
+ *
+ * memberOrders is an integer array of {1, 5, 6} to indicate that member A
+ * has one element, member B has 5 elements, and member C has 6 elements.
+ */
+ protected int[] memberOrders;
+
+ /**
+ * The dimension sizes of each member.
+ *
+ * For example, if a compound dataset has four members
+ * String[] memberNames = {"X", "Y", "Z", "TIME"};
+ * and
+ * boolean[] isMemberSelected = {true, false, false, true};
+ * members "X" and "TIME" are selected for read and write.
+ *
+ */
+ protected boolean[] isMemberSelected;
+
+ /**
+ * Constructs a CompoundDS object with the given file, dataset name and path.
+ *
+ * Using {@link #CompoundDS(FileFormat, String, String)}
+ *
+ * @param theFile
+ * the file that contains the dataset.
+ * @param dsName
+ * the name of the CompoundDS, e.g. "compDS".
+ * @param dsPath
+ * the full path of the CompoundDS, e.g. "/g1".
+ * @param oid
+ * the oid of the CompoundDS.
+ */
+ @Deprecated
+ public CompoundDS(FileFormat theFile, String dsName, String dsPath, long[] oid) {
+ super(theFile, dsName, dsPath, oid);
+
+ numberOfMembers = 0;
+ memberNames = null;
+ isMemberSelected = null;
+ memberTypes = null;
+ }
+
+ /**
+ * Returns the number of members of the compound dataset.
+ *
+ * @return the number of members of the compound dataset.
+ */
+ @Override
+ public final int getMemberCount() {
+ return numberOfMembers;
+ }
+
+ /**
+ * Returns the number of selected members of the compound dataset.
+ *
+ * Selected members are the compound fields which are selected for
+ * read/write.
+ *
+ * COMP {
+ * int A;
+ * float B[5];
+ * double C[2][3];
+ * }
+ *
+ *
+ * getMemberOrders() will return an integer array of {1, 5, 6} to indicate
+ * that member A has one element, member B has 5 elements, and member C has
+ * 6 elements.
+ *
+ * @return the array containing the total number of elements of the members
+ * of compound.
+ */
+ @Override
+ public final int[] getMemberOrders() {
+ return memberOrders;
+ }
+
+ /**
+ * Returns array containing the total number of elements of the selected
+ * members of the compound dataset.
+ *
+ *
+ * COMP {
+ * int A;
+ * float B[5];
+ * double C[2][3];
+ * }
+ *
+ *
+ * If A and B are selected, getSelectedMemberOrders() returns an array of
+ * {1, 5}
+ *
+ * @return array containing the total number of elements of the selected
+ * members of compound.
+ */
+ @Override
+ public final int[] getSelectedMemberOrders() {
+ log.trace("getSelectedMemberOrders(): start");
+
+ if (isMemberSelected == null) {
+ log.debug("getSelectedMemberOrders(): isMemberSelected array is null");
+ log.trace("getSelectedMemberOrders(): finish");
+ return memberOrders;
+ }
+
+ int idx = 0;
+ int[] orders = new int[getSelectedMemberCount()];
+ for (int i = 0; i < isMemberSelected.length; i++) {
+ if (isMemberSelected[i]) {
+ orders[idx++] = memberOrders[i];
+ }
+ }
+
+ log.trace("getSelectedMemberOrders(): finish");
+
+ return orders;
+ }
+
+ /**
+ * Returns the dimension sizes of the i-th member.
+ *
+ * COMP {
+ * int A;
+ * float B[5];
+ * double C[2][3];
+ * }
+ *
+ *
+ * getMemberDims(2) returns an array of {2, 3}, while getMemberDims(1)
+ * returns an array of {5}, and getMemberDims(0) returns null.
+ *
+ * @param i the i-th member
+ *
+ * @return the dimension sizes of the i-th member, null if the compound
+ * member is not an array.
+ */
+ @Override
+ public final int[] getMemberDims(int i) {
+ if (memberDims == null) {
+ return null;
+ }
+ return (int[]) memberDims[i];
+ }
+
+ /**
+ * Returns an array of datatype objects of compound members.
+ *
+ * COMP {
+ * int A;
+ * float B[5];
+ * double C[2][3];
+ * }
+ *
+ *
+ * getMemberOrders() will return an integer array of {1, 5, 6} to indicate that
+ * member A has one element, member B has 5 elements, and member C has 6
+ * elements.
+ *
+ * @return the array containing the total number of elements of the members of
+ * the compound data object.
+ */
+ public abstract int[] getMemberOrders();
+
+ /**
+ * Returns array containing the total number of elements of the selected members
+ * of the compound data object.
+ *
+ *
+ * COMP {
+ * int A;
+ * float B[5];
+ * double C[2][3];
+ * }
+ *
+ *
+ * If A and B are selected, getSelectedMemberOrders() returns an array of {1, 5}
+ *
+ * @return array containing the total number of elements of the selected members
+ * of the compound data object.
+ */
+ public abstract int[] getSelectedMemberOrders();
+
+ /**
+ * Returns the dimension sizes of the i-th member.
+ *
+ * COMP {
+ * int A;
+ * float B[5];
+ * double C[2][3];
+ * }
+ *
+ *
+ * getMemberDims(2) returns an array of {2, 3}, while getMemberDims(1) returns
+ * an array of {5}, and getMemberDims(0) returns null.
+ *
+ * @param i
+ * the i-th member
+ *
+ * @return the dimension sizes of the i-th member, null if the compound member
+ * is not an array.
+ */
+ public abstract int[] getMemberDims(int i);
+
+ /**
+ * Returns an array of datatype objects of the compound members.
+ *
+ * 0, 1, 2, 3, 4
+ * 10, 11, 12, 13, 14
+ * 20, 21, 22, 23, 24
+ * 30, 31, 32, 33, 34
+ * long[] dims = {4, 5};
+ * long[] startDims = {1, 2};
+ * long[] selectedDims = {3, 3};
+ * long[] selectedStride = {1, 1};
+ * then the following subset is selected by the startDims and selectedDims
+ * 12, 13, 14
+ * 22, 23, 24
+ * 32, 33, 34
+ *
+ *
+ * @return the dimension sizes of the selected subset.
+ */
+ public abstract long[] getSelectedDims();
+
+ /**
+ * Returns the starting position of a selected subset.
+ *
+ * 0, 1, 2, 3, 4
+ * 10, 11, 12, 13, 14
+ * 20, 21, 22, 23, 24
+ * 30, 31, 32, 33, 34
+ * long[] dims = {4, 5};
+ * long[] startDims = {1, 2};
+ * long[] selectedDims = {3, 3};
+ * long[] selectedStride = {1, 1};
+ * then the following subset is selected by the startDims and selectedDims
+ * 12, 13, 14
+ * 22, 23, 24
+ * 32, 33, 34
+ *
+ *
+ * @return the starting position of a selected subset.
+ */
+ public abstract long[] getStartDims();
+
+ /**
+ * Returns the selectedStride of the selected dataset.
+ *
+ * 0, 1, 2, 3, 4
+ * 10, 11, 12, 13, 14
+ * 20, 21, 22, 23, 24
+ * 30, 31, 32, 33, 34
+ * long[] dims = {4, 5};
+ * long[] startDims = {0, 0};
+ * long[] selectedDims = {2, 2};
+ * long[] selectedStride = {2, 3};
+ * then the following subset is selected by the startDims and selectedDims
+ * 0, 3
+ * 20, 23
+ *
+ *
+ * @return the selectedStride of the selected dataset.
+ */
+ public abstract long[] getStride();
+
+ /**
+ * Returns the indices of display order.
+ *
+ *
+ *
+ * @return the array of the indices of display order.
+ */
+ public int[] getSelectedIndex();
+
+ /**************************************************************************
+ * * The following two definitions are used primarily for GUI applications. * *
+ **************************************************************************/
+
+ /**
+ * Returns the dimension size of the vertical axis.
+ *
+ *
+ * For example, for a four dimension dataset, if selectedIndex[] = {1, 2, 3},
+ * then dim[1] is selected as row index, dim[2] is selected as column index and
+ * dim[3] is selected as depth index. dim[0] is not selected. Its location is
+ * fixed at 0 by default.
+ *
+ *
+ * Applications can use getSelectedIndex() to access and change the display
+ * order. For example, in a 2D dataset of 200x50 (dim0=200, dim1=50), the
+ * following code will set the height=200 and width=50.
+ *
+ *
+ * int[] selectedIndex = dataset.getSelectedIndex();
+ * selectedIndex[0] = 0;
+ * selectedIndex[1] = 1;
+ *
+ *
+ * @see #getSelectedIndex()
+ * @see #getWidth()
+ *
+ * @return the size of dimension of the vertical axis.
+ */
+ public long getHeight();
+
+ /**
+ * Returns the dimension size of the horizontal axis.
+ *
+ *
+ *
+ * Applications can use getSelectedIndex() to access and change the display
+ * order. For example, in a 2D dataset of 200x50 (dim0=200, dim1=50), the
+ * following code will set the height=200 and width=100.
+ *
+ *
+ * int[] selectedIndex = dataset.getSelectedIndex();
+ * selectedIndex[0] = 0;
+ * selectedIndex[1] = 1;
+ *
+ *
+ * @see #getSelectedIndex()
+ * @see #getHeight()
+ *
+ * @return the size of dimension of the horizontal axis.
+ */
+ public long getWidth();
+
+ /**
+ * Returns the string representation of compression information.
+ *
+ * 0, 1, 2, 3, 4
+ * 10, 11, 12, 13, 14
+ * 20, 21, 22, 23, 24
+ * 30, 31, 32, 33, 34
+ * long[] dims = {4, 5};
+ * long[] startDims = {1, 2};
+ * long[] selectedDims = {3, 3};
+ * then the following subset is selected by the startDims and selectedDims above:
+ * 12, 13, 14
+ * 22, 23, 24
+ * 32, 33, 34
+ *
+ */
+ protected long[] selectedDims;
+
+ /**
+ * The starting position of each dimension of a selected subset. With both
+ * the starting position and selected sizes, the subset of a rectangle
+ * selection is fully defined.
+ */
+ protected long[] startDims;
+
+ /**
+ * Array that contains the indices of the dimensions selected for display.
+ *
+ *
+ */
+ protected final int[] selectedIndex;
+
+ /**
+ * The number of elements to move from the start location in each dimension.
+ * For example, if selectedStride[0] = 2, every other data point is selected
+ * along dim[0].
+ */
+ protected long[] selectedStride;
+
+ /**
+ * The array of dimension sizes for a chunk.
+ */
+ protected long[] chunkSize;
+
+ /** The compression information. */
+ protected StringBuilder compression;
+ public static final String COMPRESSION_GZIP_TXT = "GZIP: level = ";
+
+ /** The filters information. */
+ protected StringBuilder filters;
+
+ /** The storage layout information. */
+ protected StringBuilder storageLayout;
+
+ /** The storage information. */
+ protected StringBuilder storage;
+
+ /** The datatype object of the dataset. */
+ protected Datatype datatype;
+
+ /**
+ * Array of strings that represent the dimension names. It is null if dimension names do not exist.
+ */
+ protected String[] dimNames;
+
+ /** Flag to indicate if the byte[] array is converted to strings */
+ protected boolean convertByteToString = true;
+
+ /** Flag to indicate if data values are loaded into memory. */
+ protected boolean isDataLoaded = false;
+
+ /** Flag to indicate if this dataset has been initialized */
+ protected boolean inited = false;
+
+ /** The number of data points in the memory buffer. */
+ protected long nPoints = 1;
+
+ /**
+ * The data buffer that contains the raw data directly reading from file
+ * (before any data conversion).
+ */
+ protected transient Object originalBuf = null;
+
+ /**
+ * The array that holds the converted data of unsigned C-type integers.
+ *
+ * Using {@link #Dataset(FileFormat, String, String)}
+ *
+ * @param theFile
+ * the file that contains the dataset.
+ * @param dsName
+ * the name of the Dataset, e.g. "dset1".
+ * @param dsPath
+ * the full group path of this Dataset, e.g. "/arrays/".
+ * @param oid
+ * the oid of this Dataset.
+ */
+ @Deprecated
+ public Dataset(FileFormat theFile, String dsName, String dsPath, long[] oid) {
+ super(theFile, dsName, dsPath, oid);
+
+ datatype = null;
+ rank = -1;
+ data = null;
+ dims = null;
+ maxDims = null;
+ selectedDims = null;
+ startDims = null;
+ selectedStride = null;
+ chunkSize = null;
+ compression = new StringBuilder("NONE");
+ filters = new StringBuilder("NONE");
+ storageLayout = new StringBuilder("NONE");
+ storage = new StringBuilder("NONE");
+ dimNames = null;
+
+ selectedIndex = new int[3];
+ selectedIndex[0] = 0;
+ selectedIndex[1] = 1;
+ selectedIndex[2] = 2;
+ }
+
+ /**
+ * Clears memory held by the dataset, such as the data buffer.
+ */
+ @SuppressWarnings("rawtypes")
+ public void clear() {
+ if (data != null) {
+ if (data instanceof List) {
+ ((List) data).clear();
+ }
+ data = null;
+ originalBuf = null;
+ convertedBuf = null;
+ }
+ isDataLoaded = false;
+ }
+
+ /**
+ * Returns the rank (number of dimensions) of the dataset.
+ *
+ * @return the number of dimensions of the dataset.
+ */
+ @Override
+ public final int getRank() {
+ if (!inited)
+ init();
+
+ return rank;
+ }
+
+ /**
+ * Returns the array that contains the dimension sizes of the dataset.
+ *
+ * @return the dimension sizes of the dataset.
+ */
+ @Override
+ public final long[] getDims() {
+ if (!inited)
+ init();
+
+ return dims;
+ }
+
+ /**
+ * Returns the array that contains the max dimension sizes of the dataset.
+ *
+ * @return the max dimension sizes of the dataset.
+ */
+ public final long[] getMaxDims() {
+ if (!inited) init();
+
+ if (maxDims == null) return dims;
+
+ return maxDims;
+ }
+
+ /**
+ * Returns the dimension sizes of the selected subset.
+ *
+ * 0, 1, 2, 3, 4
+ * 10, 11, 12, 13, 14
+ * 20, 21, 22, 23, 24
+ * 30, 31, 32, 33, 34
+ * long[] dims = {4, 5};
+ * long[] startDims = {1, 2};
+ * long[] selectedDims = {3, 3};
+ * long[] selectedStride = {1, 1};
+ * then the following subset is selected by the startDims and selectedDims
+ * 12, 13, 14
+ * 22, 23, 24
+ * 32, 33, 34
+ *
+ *
+ * @return the dimension sizes of the selected subset.
+ */
+ @Override
+ public final long[] getSelectedDims() {
+ if (!inited) init();
+
+ return selectedDims;
+ }
+
+ /**
+ * Returns the starting position of a selected subset.
+ *
+ * 0, 1, 2, 3, 4
+ * 10, 11, 12, 13, 14
+ * 20, 21, 22, 23, 24
+ * 30, 31, 32, 33, 34
+ * long[] dims = {4, 5};
+ * long[] startDims = {1, 2};
+ * long[] selectedDims = {3, 3};
+ * long[] selectedStride = {1, 1};
+ * then the following subset is selected by the startDims and selectedDims
+ * 12, 13, 14
+ * 22, 23, 24
+ * 32, 33, 34
+ *
+ *
+ * @return the starting position of a selected subset.
+ */
+ @Override
+ public final long[] getStartDims() {
+ if (!inited) init();
+
+ return startDims;
+ }
+
+ /**
+ * Returns the selectedStride of the selected dataset.
+ *
+ * 0, 1, 2, 3, 4
+ * 10, 11, 12, 13, 14
+ * 20, 21, 22, 23, 24
+ * 30, 31, 32, 33, 34
+ * long[] dims = {4, 5};
+ * long[] startDims = {0, 0};
+ * long[] selectedDims = {2, 2};
+ * long[] selectedStride = {2, 3};
+ * then the following subset is selected by the startDims and selectedDims
+ * 0, 3
+ * 20, 23
+ *
+ *
+ * @return the selectedStride of the selected dataset.
+ */
+ @Override
+ public final long[] getStride() {
+ if (!inited) init();
+
+ if (rank <= 0) {
+ return null;
+ }
+
+ if (selectedStride == null) {
+ selectedStride = new long[rank];
+ for (int i = 0; i < rank; i++) {
+ selectedStride[i] = 1;
+ }
+ }
+
+ return selectedStride;
+ }
+
+ /**
+ * Sets the flag that indicates if a byte array is converted to a string
+ * array.
+ *
+ *
+ * getStartDims(), getStride() and getSelectedDims() returns the start,
+ * stride and count arrays respectively. Applications can make a selection
+ * by changing the values of the arrays.
+ *
+ * We want to select every other data point in dims[1] and dims[2]
+ *
+ *
+ * int rank = dataset.getRank(); // number of dimensions of the dataset
+ * long[] dims = dataset.getDims(); // the dimension sizes of the dataset
+ * long[] selected = dataset.getSelectedDims(); // the selected size of the dataet
+ * long[] start = dataset.getStartDims(); // the offset of the selection
+ * long[] stride = dataset.getStride(); // the stride of the dataset
+ * int[] selectedIndex = dataset.getSelectedIndex(); // the selected dimensions for display
+ *
+ * // select dim1 and dim2 as 2D data for display,and slice through dim0
+ * selectedIndex[0] = 1;
+ * selectedIndex[1] = 2;
+ * selectedIndex[1] = 0;
+ *
+ * // reset the selection arrays
+ * for (int i = 0; i < rank; i++) {
+ * start[i] = 0;
+ * selected[i] = 1;
+ * stride[i] = 1;
+ * }
+ *
+ * // set stride to 2 on dim1 and dim2 so that every other data point is
+ * // selected.
+ * stride[1] = 2;
+ * stride[2] = 2;
+ *
+ * // set the selection size of dim1 and dim2
+ * selected[1] = dims[1] / stride[1];
+ * selected[2] = dims[1] / stride[2];
+ *
+ * // when dataset.getData() is called, the selection above will be used since
+ * // the dimension arrays are passed by reference. Changes of these arrays
+ * // outside the dataset object directly change the values of these array
+ * // in the dataset object.
+ *
+ *
+ * comp --> m01 (int)
+ * comp --> m02 (float)
+ * comp --> nest1 --> m11 (char)
+ * comp --> nest1 --> m12 (String)
+ * comp --> nest1 --> nest2 --> m21 (long)
+ * comp --> nest1 --> nest2 --> m22 (double)
+ *
+ *
+ * getData() returns a list of six arrays: {int[], float[], char[],
+ * String[], long[] and double[]}.
+ *
+ * @return the memory buffer of the dataset.
+ *
+ * @throws Exception if object can not be read
+ * @throws OutOfMemoryError if memory is exhausted
+ */
+ @Override
+ public final Object getData() throws Exception, OutOfMemoryError {
+ log.trace("getData: start");
+ if (!isDataLoaded) {
+ log.trace("getData: read");
+ data = read(); // load the data
+ originalBuf = data;
+ isDataLoaded = true;
+ nPoints = 1;
+ log.trace("getData: selectedDims length={}",selectedDims.length);
+ for (int j = 0; j < selectedDims.length; j++) {
+ nPoints *= selectedDims[j];
+ }
+ log.trace("getData: read {}", nPoints);
+ }
+
+ log.trace("getData: finish");
+ return data;
+ }
+
+ /**
+ * Not for public use in the future.
+ *
+ *
+ * Applications can use getSelectedIndex() to access and change the display
+ * order. For example, in a 2D dataset of 200x50 (dim0=200, dim1=50), the
+ * following code will set the height=200 and width=50.
+ *
+ *
+ * int[] selectedIndex = dataset.getSelectedIndex();
+ * selectedIndex[0] = 0;
+ * selectedIndex[1] = 1;
+ *
+ *
+ * @see #getSelectedIndex()
+ * @see #getWidth()
+ *
+ * @return the size of dimension of the vertical axis.
+ */
+ @Override
+ public final long getHeight() {
+ if (!inited) init();
+
+ if ((selectedDims == null) || (selectedIndex == null)) {
+ return 0;
+ }
+
+ return selectedDims[selectedIndex[0]];
+ }
+
+ /**
+ * Returns the dimension size of the horizontal axis.
+ *
+ *
+ *
+ * Applications can use getSelectedIndex() to access and change the display
+ * order. For example, in a 2D dataset of 200x50 (dim0=200, dim1=50), the
+ * following code will set the height=200 and width=100.
+ *
+ *
+ * int[] selectedIndex = dataset.getSelectedIndex();
+ * selectedIndex[0] = 0;
+ * selectedIndex[1] = 1;
+ *
+ *
+ * @see #getSelectedIndex()
+ * @see #getHeight()
+ *
+ * @return the size of dimension of the horizontal axis.
+ */
+ @Override
+ public final long getWidth() {
+ if (!inited) init();
+
+ if ((selectedDims == null) || (selectedIndex == null)) {
+ return 0;
+ }
+
+ if ((selectedDims.length < 2) || (selectedIndex.length < 2)) {
+ return 1;
+ }
+
+ return selectedDims[selectedIndex[1]];
+ }
+
+ /**
+ * Returns the indices of display order.
+ *
+ *
+ *
+ * @return the array of the indices of display order.
+ */
+ @Override
+ public final int[] getSelectedIndex() {
+ if (!inited) init();
+
+ return selectedIndex;
+ }
+
+ /**
+ * Returns the string representation of compression information.
+ *
+ * For example, for a four dimension dataset, if selectedIndex[] = {1, 2, 3},
+ * then dim[1] is selected as row index, dim[2] is selected as column index
+ * and dim[3] is selected as depth index. dim[0] is not selected. Its
+ * location is fixed at 0 by default.
+ *
+ * Using {@link #convertFromUnsignedC(Object, Object)}
+ *
+ * @param dataIN the object data
+ *
+ * @return the converted object
+ */
+ @Deprecated
+ public static Object convertFromUnsignedC(Object dataIN) {
+ return Dataset.convertFromUnsignedC(dataIN, null);
+ }
+
+ /**
+ * Converts one-dimension array of unsigned C-type integers to a new array
+ * of appropriate Java integer in memory.
+ *
+ *
+ * NOTE: this conversion cannot deal with unsigned 64-bit integers.
+ * Therefore, the values of unsigned 64-bit datasets may be wrong in Java
+ * applications.
+ *
+ *
+ * Unsigned C Integer
+ * JAVA Integer
+ *
+ *
+ * unsigned byte
+ * signed short
+ *
+ *
+ * unsigned short
+ * signed int
+ *
+ *
+ * unsigned int
+ * signed long
+ *
+ *
+ * unsigned long
+ * signed long
+ *
+ * Using {@link #convertToUnsignedC(Object, Object)}
+ *
+ * @param dataIN
+ * the input 1D array of the unsigned C-type integers.
+ *
+ * @return the upgraded 1D array of Java integers.
+ */
+ @Deprecated
+ public static Object convertToUnsignedC(Object dataIN) {
+ return Dataset.convertToUnsignedC(dataIN, null);
+ }
+
+ /**
+ * Converts the array of converted unsigned integers back to unsigned C-type
+ * integer data in memory.
+ *
+ * Using {@link #Datatype(FileFormat, String, String)}
+ *
+ * @param theFile
+ * the HDF file.
+ * @param typeName
+ * the name of the datatype, e.g "12-bit Integer".
+ * @param typePath
+ * the full group path of the datatype, e.g. "/datatypes/".
+ * @param oid
+ * the oidof the datatype.
+ */
+ @Deprecated
+ public Datatype(FileFormat theFile, String typeName, String typePath, long[] oid) {
+ super(theFile, typeName, typePath, oid);
+ }
+
+ /**
+ * Constructs a Datatype with specified class, size, byte order and sign.
+ *
+ *
+ *
+ * @param tclass
+ * the class of the datatype, e.g. CLASS_INTEGER, CLASS_FLOAT and etc.
+ * @param tsize
+ * the size of the datatype in bytes, e.g. for a 32-bit integer, the size is 4.
+ * Valid values are NATIVE or a positive value.
+ * @param torder
+ * the byte order of the datatype. Valid values are ORDER_LE, ORDER_BE, ORDER_VAX,
+ * ORDER_NONE and NATIVE.
+ * @param tsign
+ * the sign of the datatype. Valid values are SIGN_NONE, SIGN_2 and NATIVE.
+ *
+ * @throws Exception
+ * if there is an error
+ */
+ public Datatype(int tclass, int tsize, int torder, int tsign) throws Exception {
+ this(tclass, tsize, torder, tsign, null);
+ }
+
+ /**
+ * Constructs a Datatype with specified class, size, byte order and sign.
+ *
+ * Datatype type = new Dataype(Datatype.CLASS_INTEGER, Datatype.NATIVE, Datatype.NATIVE, Datatype.SIGN_NONE);
+ *
+ * Datatype type = new Dataype(Datatype.CLASS_INTEGER, 2, Datatype.ORDER_BE, Datatype.NATIVE);
+ *
+ * Datatype type = new Dataype(Datatype.CLASS_FLOAT, Datatype.NATIVE, Datatype.NATIVE, Datatype.NATIVE);
+ *
+ * Datatype type = new Dataype(Datatype.CLASS_FLOAT, 8, Datatype.NATIVE, Datatype.NATIVE);
+ *
+ *
+ *
+ * @param tclass
+ * the class of the datatype, e.g. CLASS_INTEGER, CLASS_FLOAT and
+ * etc.
+ * @param tsize
+ * the size of the datatype in bytes, e.g. for a 32-bit integer,
+ * the size is 4.
+ * Valid values are NATIVE or a positive value.
+ * @param torder
+ * the byte order of the datatype. Valid values are ORDER_LE,
+ * ORDER_BE, ORDER_VAX, ORDER_NONE and NATIVE.
+ * @param tsign
+ * the sign of the datatype. Valid values are SIGN_NONE, SIGN_2 and NATIVE.
+ * @param tbase
+ * the base datatype of the new datatype
+ *
+* @throws Exception
+ * if there is an error
+ */
+ public Datatype(int tclass, int tsize, int torder, int tsign, Datatype tbase) throws Exception {
+ this(tclass, tsize, torder, tsign, tbase, null);
+ }
+
+ /**
+ * Constructs a Datatype with specified class, size, byte order and sign.
+ *
+ * Datatype type = new Dataype(Datatype.CLASS_INTEGER, Datatype.NATIVE, Datatype.NATIVE, Datatype.SIGN_NONE);
+ *
+ * Datatype type = new Dataype(Datatype.CLASS_INTEGER, 2, Datatype.ORDER_BE, Datatype.NATIVE);
+ *
+ * Datatype type = new Dataype(Datatype.CLASS_FLOAT, Datatype.NATIVE, Datatype.NATIVE, Datatype.NATIVE);
+ *
+ * Datatype type = new Dataype(Datatype.CLASS_FLOAT, 8, Datatype.NATIVE, Datatype.NATIVE);
+ *
+ *
+ *
+ * @param tclass
+ * the class of the datatype, e.g. CLASS_INTEGER, CLASS_FLOAT and etc.
+ * @param tsize
+ * the size of the datatype in bytes, e.g. for a 32-bit integer, the size is 4.
+ * Valid values are NATIVE or a positive value.
+ * @param torder
+ * the byte order of the datatype. Valid values are ORDER_LE, ORDER_BE, ORDER_VAX,
+ * ORDER_NONE and NATIVE.
+ * @param tsign
+ * the sign of the datatype. Valid values are SIGN_NONE, SIGN_2 and NATIVE.
+ * @param tbase
+ * the base datatype of the new datatype
+ * @param pbase
+ * the parent datatype of the new datatype
+ *
+* @throws Exception
+ * if there is an error
+ */
+ public Datatype(int tclass, int tsize, int torder, int tsign, Datatype tbase, Datatype pbase) throws Exception {
+ if ((tsize == 0) || (tsize < 0 && tsize != NATIVE))
+ throw new Exception("invalid datatype size - " + tsize);
+ if ((torder != ORDER_LE) && (torder != ORDER_BE) && (torder != ORDER_VAX)
+ && (torder != ORDER_NONE) && (torder != NATIVE))
+ throw new Exception("invalid datatype order - " + torder);
+ if ((tsign != SIGN_NONE) && (tsign != SIGN_2) && (tsign != NATIVE))
+ throw new Exception("invalid datatype sign - " + tsign);
+
+ datatypeClass = tclass;
+ datatypeSize = tsize;
+ datatypeOrder = torder;
+ datatypeSign = tsign;
+ enumMembers = null;
+ baseType = tbase;
+ arrayDims = null;
+ isVariableStr = (datatypeClass == Datatype.CLASS_STRING) && (tsize < 0);
+ isVLEN = (datatypeClass == Datatype.CLASS_VLEN) || isVariableStr;
+
+ compoundMemberNames = new ArrayList<>();
+ compoundMemberTypes = new ArrayList<>();
+ compoundMemberOffsets = new ArrayList<>();
+
+ log.trace("datatypeClass={} datatypeSize={} datatypeOrder={} datatypeSign={} baseType={}",
+ datatypeClass, datatypeSize, datatypeOrder, datatypeSign, baseType);
+ }
+
+ /**
+ * Constructs a Datatype with a given native datatype identifier.
+ *
+ * Datatype type = new Dataype(Datatype.CLASS_INTEGER, Datatype.NATIVE, Datatype.NATIVE, Datatype.SIGN_NONE);
+ *
+ * Datatype type = new Dataype(Datatype.CLASS_INTEGER, 2, Datatype.ORDER_BE, Datatype.NATIVE);
+ *
+ * Datatype type = new Dataype(Datatype.CLASS_FLOAT, Datatype.NATIVE, Datatype.NATIVE, Datatype.NATIVE);
+ *
+ * Datatype type = new Dataype(Datatype.CLASS_FLOAT, 8, Datatype.NATIVE, Datatype.NATIVE);
+ *
+ * long tid = H5.H5Tcopy(HDF5Constants.H5T_NATIVE_UNINT32);
+ * Datatype dtype = new Datatype(tid);
+ *
+ *
+ * will construct a datatype equivalent to new Datatype(CLASS_INTEGER, 4, NATIVE, SIGN_NONE);
+ *
+ * @see #fromNative(long tid)
+ * @param tid
+ * the native datatype identifier.
+ *
+* @throws Exception
+ * if there is an error
+ */
+ public Datatype(long tid) throws Exception {
+ this(tid, null);
+ }
+
+ /**
+ * Constructs a Datatype with a given native datatype identifier.
+ *
+ * long tid = H5.H5Tcopy(HDF5Constants.H5T_NATIVE_UNINT32);
+ * Datatype dtype = new Datatype(tid);
+ *
+ *
+ * will construct a datatype equivalent to new Datatype(CLASS_INTEGER, 4, NATIVE, SIGN_NONE);
+ *
+ * @see #fromNative(long tid)
+ * @param tid
+ * the native datatype identifier.
+ * @param pbase
+ * the parent datatype of the new datatype
+ *
+* @throws Exception
+ * if there is an error
+ */
+ public Datatype(long tid, Datatype pbase) throws Exception {
+ this(CLASS_NO_CLASS, NATIVE, NATIVE, NATIVE, null, pbase);
+ }
+
+ /**
+ * Opens access to this named datatype. Sub-classes must replace this default implementation. For
+ * example, in H5Datatype, open() function H5.H5Topen(loc_id, name) to get the datatype identifier.
+ *
+ * @return the datatype identifier if successful; otherwise returns negative value.
+ */
+ @Override
+ public long open() {
+ return -1;
+ }
+
+ /**
+ * Closes a datatype identifier.
+ *
+ *
+ *
+ * @return the class of the datatype.
+ */
+ public int getDatatypeClass() {
+ return datatypeClass;
+ }
+
+ /**
+ * Returns the size of the datatype in bytes. For example, for a 32-bit
+ * integer, the size is 4 (bytes).
+ *
+ * @return the size of the datatype.
+ */
+ public long getDatatypeSize() {
+ return datatypeSize;
+ }
+
+ /**
+ * Returns the byte order of the datatype. Valid values are
+ *
+ *
+ *
+ * @return the byte order of the datatype.
+ */
+ public int getDatatypeOrder() {
+ return datatypeOrder;
+ }
+
+ /**
+ * Returns the sign (SIGN_NONE, SIGN_2) of an integer datatype.
+ *
+ * @return the sign of the datatype.
+ */
+ public int getDatatypeSign() {
+ return datatypeSign;
+ }
+
+ /**
+ * Returns the base datatype for this datatype.
+ *
+ *
+ *
+ * @param enumStr
+ * the (key, value) pairs of enum members
+ */
+ public final void setEnumMembers(String enumStr) {
+ log.trace("setEnumMembers: is_enum enum_members={}", enumStr);
+ enumMembers = new HashMap<>();
+ String[] entries = enumStr.split(",");
+ for (String entry : entries) {
+ String[] keyValue = entry.split("=");
+ enumMembers.put(keyValue[0].trim(), keyValue[1].trim());
+ if (log.isTraceEnabled())
+ log.trace("setEnumMembers: is_enum value={} name={}", keyValue[0].trim(), keyValue[1].trim());
+ }
+ }
+
+ /**
+ * Returns the Map<String,String> pairs of enum members for enum datatype.
+ *
+ * @return enumStr Map<String,String%gt; pairs of enum members
+ */
+ public final Map
+ *
+ *
+ * @return enumStr the (key, value) pairs of enum members
+ */
+ @SuppressWarnings("rawtypes")
+ public final String getEnumMembersAsString() {
+ if (enumMembers == null) {
+ enumMembers = new HashMap<>();
+ enumMembers.put("1", "0");
+ enumMembers.put("2", "1");
+ }
+
+ StringBuilder enumStr = new StringBuilder();
+ Iterator
+ *
+ *
+ * H5Dataype dtype = new H5Datatype(CLASS_INTEGER, 4, NATIVE, SIGN_NONE);
+ * int tid = dtype.createNative();
+ *
+ *
+ * The "tid" will be the HDF5 datatype id of a 64-bit unsigned integer, which is equivalent to
+ *
+ *
+ * int tid = H5.H5Tcopy(HDF5Constants.H5T_NATIVE_UNINT32);
+ *
+ *
+ * @return the identifier of the native datatype.
+ */
+ public abstract long createNative();
+
+ /**
+ * Set datatype characteristics (class, size, byte order and sign) from a given datatype identifier.
+ *
+ * H5Datatype dtype = new H5Datatype();
+ * dtype.fromNative(HDF5Constants.H5T_NATIVE_UNINT32);
+ *
+ *
+ * Where dtype is equivalent to
+ * new H5Datatype(CLASS_INTEGER, 4, NATIVE, SIGN_NONE);
+ *
+ * @param nativeID
+ * the datatype identifier.
+ */
+ public abstract void fromNative(long nativeID);
+
+ /**
+ * Returns a short text description of this datatype.
+ *
+ * @return a short text description of this datatype
+ */
+ public String getDescription() {
+ log.trace("getDescription(): start");
+
+ if (datatypeDescription != null) {
+ log.trace("getDescription(): finish");
+ return datatypeDescription;
+ }
+
+ StringBuilder description = new StringBuilder();
+
+ switch (datatypeClass) {
+ case CLASS_CHAR:
+ description.append("8-bit ").append((isUnsigned() ? "unsigned " : "")).append("integer");
+ break;
+ case CLASS_INTEGER:
+ if (datatypeSize == NATIVE)
+ description.append("native ").append((isUnsigned() ? "unsigned " : "")).append("integer");
+ else
+ description.append(String.valueOf(datatypeSize * 8)).append("-bit ")
+ .append((isUnsigned() ? "unsigned " : "")).append("integer");
+ break;
+ case CLASS_FLOAT:
+ if (datatypeSize == NATIVE)
+ description.append("native floating-point");
+ else
+ description.append(String.valueOf(datatypeSize * 8)).append("-bit floating-point");
+ break;
+ case CLASS_STRING:
+ description.append("String");
+ break;
+ case CLASS_REFERENCE:
+ description.append("Object reference");
+ break;
+ case CLASS_OPAQUE:
+ if (datatypeSize == NATIVE)
+ description.append("native opaque");
+ else
+ description.append(String.valueOf(datatypeSize * 8)).append("-bit opaque");
+ break;
+ case CLASS_BITFIELD:
+ if (datatypeSize == NATIVE)
+ description.append("native bitfield");
+ else
+ description.append(String.valueOf(datatypeSize * 8)).append("-bit bitfield");
+ break;
+ case CLASS_ENUM:
+ if (datatypeSize == NATIVE)
+ description.append("native enum");
+ else
+ description.append(String.valueOf(datatypeSize * 8)).append("-bit enum");
+ break;
+ case CLASS_ARRAY:
+ description.append("Array");
+
+ if (arrayDims != null) {
+ description.append(" [");
+ for (int i = 0; i < arrayDims.length; i++) {
+ description.append(arrayDims[i]);
+ if (i < arrayDims.length - 1)
+ description.append(" x ");
+ }
+ description.append("]");
+ }
+
+ break;
+ case CLASS_COMPOUND:
+ description.append("Compound");
+ break;
+ case CLASS_VLEN:
+ description.append("Variable-length");
+ break;
+ default:
+ description.append("Unknown");
+ break;
+ }
+
+ if (baseType != null) {
+ description.append(" of " + baseType.getDescription());
+ }
+
+ log.trace("getDescription(): finish");
+ return description.toString();
+ }
+
+ /**
+ * Checks if this datatype is unsigned.
+ *
+ * @return true if the datatype is unsigned;
+ * otherwise, returns false.
+ */
+ public boolean isUnsigned() {
+ if (baseType != null)
+ return baseType.isUnsigned();
+ else {
+ if (isCompound()) {
+ if ((compoundMemberTypes != null) && !compoundMemberTypes.isEmpty()) {
+ boolean allMembersUnsigned = true;
+
+ Iterator
+ * FileFormat
+ * _________________|_________________
+ * | | |
+ * H5File H4File Other...
+ *
+ * filename
argument is null
.
+ * @see File#File(String)
+ * @see #createFile(String, int)
+ * @see #createInstance(String, int)
+ * @see #getInstance(String)
+ */
+ public FileFormat(String filename) {
+ super(filename);
+
+ fullFileName = filename;
+
+ if ((filename != null) && (filename.length() > 0)) {
+ try {
+ fullFileName = this.getAbsolutePath();
+ }
+ catch (Exception ex) {
+ log.debug("File {} getAbsolutePath failure: ", filename, ex);
+ }
+ }
+ isReadOnly = false;
+ log.trace("fullFileName={} isReadOnly={}", fullFileName, isReadOnly);
+ }
+
+ /***************************************************************************
+ * Class methods
+ **************************************************************************/
+
+ /**
+ * Adds a FileFormat with specified key to the list of supported formats.
+ *
+ * Class fileClass = Class.forName( "companyC.files.xyzFile" );
+ * FileFormat ff = (FileFormat) fileClass.newInstance();
+ * if ( ff != null ) {
+ * ff.addFileFormat ("xyz", ff )
+ * }
+ *
+ * key
or fileformat
are
+ * null
, or if key
is already in use, the method
+ * returns without updating the list of supported File Formats.
+ *
+ * @param key
+ * A string that identifies the FileFormat.
+ * @param fileformat
+ * An instance of the FileFormat to be added.
+ * @see #getFileFormat(String)
+ * @see #getFileFormatKeys()
+ * @see #getFileFormats()
+ * @see #removeFileFormat(String)
+ */
+ public static final void addFileFormat(String key, FileFormat fileformat) {
+ if ((fileformat == null) || (key == null)) {
+ return;
+ }
+
+ key = key.trim();
+
+ if (!FileList.containsKey(key)) {
+ FileList.put(key, fileformat);
+ }
+ }
+
+ /**
+ * Returns the FileFormat with specified key from the list of supported
+ * formats.
+ * null
is returned.
+ *
+ * @param key
+ * A string that identifies the FileFormat.
+ * @return The FileFormat that matches the given key, or null
+ * if the key is not found in the list of supported File Formats.
+ * @see #addFileFormat(String,FileFormat)
+ * @see #getFileFormatKeys()
+ * @see #getFileFormats()
+ * @see #removeFileFormat(String)
+ */
+ public static final FileFormat getFileFormat(String key) {
+ return FileList.get(key);
+ }
+
+ /**
+ * Returns an Enumeration of keys for all supported formats.
+ * null
is returned.
+ *
+ * @return An array of all FileFormat instances in the list of supported
+ * File Formats, or null
if the list is empty.
+ * @see #addFileFormat(String,FileFormat)
+ * @see #getFileFormat(String)
+ * @see #getFileFormatKeys()
+ * @see #removeFileFormat(String)
+ */
+ @SuppressWarnings("rawtypes")
+ public static final FileFormat[] getFileFormats() {
+ int n = FileList.size();
+ if (n <= 0) {
+ return null;
+ }
+
+ int i = 0;
+ FileFormat[] fileformats = new FileFormat[n];
+ Enumeration> local_enum = ((Hashtable) FileList).elements();
+ while (local_enum.hasMoreElements()) {
+ fileformats[i++] = (FileFormat) local_enum.nextElement();
+ }
+
+ return fileformats;
+ }
+
+ /**
+ * Removes a FileFormat from the list of supported formats.
+ * null
is
+ * returned.
+ *
+ * @param key
+ * A string that identifies the FileFormat to be removed.
+ * @return The FileFormat that is removed, or null
if the key
+ * is not found in the list of supported File Formats.
+ * @see #addFileFormat(String,FileFormat)
+ * @see #getFileFormat(String)
+ * @see #getFileFormatKeys()
+ * @see #getFileFormats()
+ */
+ public static final FileFormat removeFileFormat(String key) {
+ return FileList.remove(key);
+ }
+
+ /**
+ * Adds file extension(s) to the list of file extensions for supported file
+ * formats.
+ * null
is returned.
+ * null
if no
+ * match.
+ * @throws IllegalArgumentException
+ * If the filename
argument is null
or
+ * does not specify an existing file.
+ * @throws Exception
+ * If there are problems creating the new instance.
+ * @see #createFile(String, int)
+ * @see #createInstance(String, int)
+ * @see #getFileFormats()
+ */
+ @SuppressWarnings("rawtypes")
+ public static final FileFormat getInstance(String filename) throws Exception {
+ if ((filename == null) || (filename.length() <= 0)) {
+ throw new IllegalArgumentException("Invalid file name: " + filename);
+ }
+
+ if (!(new File(filename)).exists()) {
+ throw new IllegalArgumentException("File " + filename + " does not exist.");
+ }
+
+ FileFormat fileFormat = null;
+ FileFormat knownFormat = null;
+ Enumeration> elms = ((Hashtable) FileList).elements();
+
+ while (elms.hasMoreElements()) {
+ knownFormat = (FileFormat) elms.nextElement();
+ if (knownFormat.isThisType(filename)) {
+ try {
+ fileFormat = knownFormat.createInstance(filename, WRITE);
+ }
+ catch (Exception ex) {
+ log.debug("File {} createInstance failure: ", filename, ex);
+ }
+ break;
+ }
+ }
+
+ return fileFormat;
+ }
+
+ /***************************************************************************
+ * Implementation Class methods. These methods are related to the
+ * implementing FileFormat class, but not to a particular instance of that
+ * class. Since we can't override class methods (they can only be shadowed
+ * in Java), these are instance methods.
+ *
+ * The non-abstract methods just throw an exception indicating that the
+ * implementing class doesn't support the functionality.
+ **************************************************************************/
+
+ /**
+ * Returns the version of the library for the implementing FileFormat class.
+ *
+ * FileFormat h5F = FileFormat.getFileFormat(FileFormat.FILE_TYPE_HDF5);
+ * HObject hObject = viewer.getTreeView().getCurrentObject();
+ * FileFormat thisF = hObject.getFileFormat();
+ * boolean isH5 = h5F.isThisType(thisF);
+ *
+ *
+ * @param fileFormat
+ * The FileFormat to be checked.
+ * @return True if this instance implements the specified FileFormat;
+ * otherwise returns false.
+ * @see #isThisType(String)
+ */
+ public abstract boolean isThisType(FileFormat fileFormat);
+
+ /**
+ * Checks if the implementing FileFormat class matches the format of the
+ * specified file.
+ * false
, and the second
+ * call will return true
.
+ *
+ *
+ * FileFormat ncF = FileFormat.getFileFormat(FileFormat.FILE_TYPE_NC3);
+ * FileFormat h4F = FileFormat.getFileFormat(FileFormat.FILE_TYPE_HDF4);
+ * FileFormat h5F = FileFormat.getFileFormat(FileFormat.FILE_TYPE_HDF5);
+ * boolean isH4 = h4F.isThisType("test.h5"); // false
+ * boolean isH5 = h5F.isThisType("test.h5"); // true
+ *
+ *
+ * @param filename
+ * The name of the file to be checked.
+ * @return True if the format of the file matches the format of this
+ * instance; otherwise returns false.
+ * @see #isThisType(FileFormat)
+ */
+ public abstract boolean isThisType(String filename);
+
+ /**
+ * Creates a file with the specified name and returns a new FileFormat
+ * implementation instance associated with the file.
+ *
+ *
+ * FILE_CREATE_DELETE
and
+ * FILE_CREATE_OPEN
.
+ * @throws NullPointerException
+ * If the filename
argument is null
.
+ * @throws UnsupportedOperationException
+ * If the implementing class does not support the file creation
+ * operation.
+ * @throws Exception
+ * If the file cannot be created or if the creation flag has an
+ * unexpected value. The exceptions thrown vary depending on the
+ * implementing class.
+ * @see #createInstance(String, int)
+ * @see #getInstance(String)
+ * @see #open()
+ *
+ * @return the FileFormat instance.
+ */
+ public FileFormat createFile(String filename, int createFlag) throws Exception {
+ // If the implementing subclass doesn't have this method then that
+ // format doesn't support File Creation and we throw an exception.
+ throw new UnsupportedOperationException("FileFormat FileFormat.createFile(...) is not implemented.");
+ }
+
+ /**
+ * Creates a FileFormat implementation instance with specified filename and
+ * access.
+ *
+ *
+ *
+ * // Request the implementing class of FileFormat: H5File
+ * FileFormat h5file = FileFormat.getFileFormat(FileFormat.FILE_TYPE_HDF5);
+ *
+ * // Create an instance of H5File object with read/write access
+ * H5File test1 = (H5File) h5file.createInstance("test_hdf5.h5",
+ * FileFormat.WRITE);
+ *
+ * // Open the file and load the file structure; file id is returned.
+ * int fid = test1.open();
+ *
+ *
+ * @param filename
+ * The filename; a pathname string.
+ * @param access
+ * The file access flag, which determines behavior when file is
+ * opened. Acceptable values are READ, WRITE,
and
+ * CREATE
.
+ * @throws NullPointerException
+ * If the filename
argument is null
.
+ * @throws Exception
+ * If the instance cannot be created or if the access flag has
+ * an unexpected value. The exceptions thrown vary depending on
+ * the implementing class.
+ * @see #createFile(String, int)
+ * @see #getInstance(String)
+ * @see #open()
+ *
+ * @return the FileFormat instance.
+ */
+ public abstract FileFormat createInstance(String filename, int access) throws Exception;
+
+ // REVIEW DOCS for createInstance()
+ // What if READ ONLY in implementation? What if file already open?
+ // Can we doc exceptions better or in implementation methods?
+
+ /***************************************************************************
+ * Final instance methods
+ *
+ * Related to a given instance of the class, but at the FileFormat level,
+ * not at the implementing class level.
+ **************************************************************************/
+
+ /**
+ * Returns the absolute path for the file.
+ * null
is returned.
+ *
+ * @return The full path (file path + file name) of the associated file, or
+ * null
if there is no associated file.
+ */
+ public final String getFilePath() {
+ return fullFileName;
+ }
+
+ /**
+ * Returns file identifier of open file associated with this instance.
+ *
+ * @return The file identifer, or -1 if there is no file open.
+ */
+ public final long getFID() {
+ return fid;
+ }
+
+ /**
+ * Returns true if the file access is read-only.
+ * filename
and access
+ * parameters specified in the createFile(), createInstance(),
+ * or getInstance() call to open the file. It returns the file
+ * identifier if successful, or a negative value in case of failure.
+ * null
will be returned.
+ * null
if there is no
+ * associated file or if the associated file has not yet been
+ * opened.
+ * @see #open()
+ */
+ public abstract HObject getRootObject();
+
+ /**
+ * Gets the HObject with the specified path from the file.
+ *
+ *
+ *
+ * /g0 Group
+ * /g0/dataset_comp Dataset {50, 10}
+ * /g0/dataset_int Dataset {50, 10}
+ * /g0/g00 Group
+ * /g0/g00/dataset_float Dataset {50, 10}
+ * /g0/g01 Group
+ * /g0/g01/dataset_string Dataset {50, 10}
+ *
+ *
+ *
+ *
+ *
+ * @param path
+ * Full path of the data object to be returned.
+ * @return The object if it exists in the file; otherwise get("/g0")
returns the
+ * instance for /g0 with the information necessary to access
+ * /g0/dataset_comp, /g0/dataset_int, /g0/g00, /g0/g00/dataset_float,
+ * /g0/g01, and /g0/g01/dataset_string.
+ * get("/go")
+ * returns the instance for /g0 with the information necessary to access
+ * /g0/dataset_comp, /g0/dataset_int, /g0/g00, and /g0/g01.
+ * null
.
+ * @throws Exception
+ * If there are unexpected problems in trying to retrieve the
+ * object. The exceptions thrown vary depending on the
+ * implementing class.
+ */
+ public abstract HObject get(String path) throws Exception;
+
+ // REVIEW DOCS for get(); What if no file associated w/ instance?
+ // Look at exceptions. Confirm example. Make sure perf tradeoffs
+ // documented properly.
+
+ /**
+ * Creates a named datatype in a file.
+ *
+ * H5File file = (H5File) h5file.createInstance("test_hdf5.h5", FileFormat.WRITE);
+ * H5Datatype dtype = file.createDatatype(
+ * Datatype.CLASS_INTEGER,
+ * 4,
+ * Datatype.NATIVE,
+ * Datatype.NATIVE,
+ * "Native Integer");
+ *
+ *
+ * @param tclass
+ * class of datatype, e.g. Datatype.CLASS_INTEGER
+ * @param tsize
+ * size of the datatype in bytes, e.g. 4 for 32-bit integer.
+ * @param torder
+ * order of the byte endianing, e.g. Datatype.ORDER_LE.
+ * @param tsign
+ * signed or unsigned of an integer, e.g. Datatype.SIGN_NONE.
+ * @param name
+ * name of the datatype to create, e.g. "Native Integer".
+ * @return The new datatype if successful; otherwise returns null.
+ * @throws Exception
+ * The exceptions thrown vary depending on the implementing
+ * class.
+ */
+ public abstract Datatype createDatatype(int tclass, int tsize, int torder, int tsign, String name) throws Exception;
+
+ /**
+ * Creates a named datatype in a file.
+ *
+ * H5File file = (H5File) h5file.createInstance("test_hdf5.h5", FileFormat.WRITE);
+ * H5Datatype dtype = file.createDatatype(
+ * Datatype.CLASS_INTEGER,
+ * 4,
+ * Datatype.NATIVE,
+ * Datatype.NATIVE,
+ * basetype,
+ * "Native Integer");
+ *
+ *
+ * @param tclass
+ * class of datatype, e.g. Datatype.CLASS_INTEGER
+ * @param tsize
+ * size of the datatype in bytes, e.g. 4 for 32-bit integer.
+ * @param torder
+ * order of the byte endianing, e.g. Datatype.ORDER_LE.
+ * @param tsign
+ * signed or unsigned of an integer, e.g. Datatype.SIGN_NONE.
+ * @param tbase
+ * the base datatype of the new datatype
+ * @param name
+ * name of the datatype to create, e.g. "Native Integer".
+ * @return The new datatype if successful; otherwise returns null.
+ * @throws Exception
+ * The exceptions thrown vary depending on the implementing
+ * class.
+ */
+ public Datatype createDatatype(int tclass, int tsize, int torder, int tsign, Datatype tbase, String name) throws Exception
+ {
+ // Derived classes must override this function to use base type option
+ return createDatatype(tclass, tsize, torder, tsign, name);
+ }
+
+ // REVIEW DOCS for createDatatype(). Check and document exceptions.
+
+ /***************************************************************************
+ * Methods related to Datatypes and HObjects in the implementing FileFormat.
+ *
+ * Strictly speaking, these methods aren't related to FileFormat and the
+ * actions could be carried out through the HObject and Datatype classes.
+ * But, in some cases they allow a null input and expect the generated
+ * object to be of a type that has particular FileFormat. Therefore, we put
+ * them in the implementing FileFormat class so that we create the proper
+ * type of HObject... H5Group or H4Group for example.
+ *
+ * Here again, if there could be Implementation Class methods we'd use
+ * those. But, since we can't override class methods (they can only be
+ * shadowed in Java), these are instance methods.
+ *
+ * The non-abstract methods just throw an exception indicating that the
+ * implementing class doesn't support the functionality.
+ **************************************************************************/
+
+ /**
+ * Creates a new datatype in memory.
+ *
+ * H5File file = (H5File) h5file.createInstance("test_hdf5.h5", FileFormat.WRITE);
+ * H5Datatype dtype = file.createDatatype(
+ * Datatype.CLASS_INTEGER,
+ * 4,
+ * Datatype.NATIVE,
+ * Datatype.NATIVE);
+ *
+ *
+ * @param tclass
+ * class of datatype, e.g. Datatype.CLASS_INTEGER
+ * @param tsize
+ * size of the datatype in bytes, e.g. 4 for 32-bit integer.
+ * @param torder
+ * order of the byte endian, e.g. Datatype.ORDER_LE.
+ * @param tsign
+ * signed or unsigned of an integer, e.g. Datatype.SIGN_NONE.
+ * @return The new datatype object if successful; otherwise returns null.
+ * @throws Exception
+ * The exceptions thrown vary depending on the implementing
+ * class.
+ */
+ public abstract Datatype createDatatype(int tclass, int tsize, int torder, int tsign) throws Exception;
+
+ /**
+ * Creates a new datatype in memory.
+ *
+ * H5File file = (H5File) h5file.createInstance("test_hdf5.h5", FileFormat.WRITE);
+ * H5Datatype dtype = file.createDatatype(
+ * Datatype.CLASS_INTEGER,
+ * 4,
+ * Datatype.NATIVE,
+ * Datatype.NATIVE,
+ * basetype);
+ *
+ *
+ * @param tclass
+ * class of datatype, e.g. Datatype.CLASS_INTEGER
+ * @param tsize
+ * size of the datatype in bytes, e.g. 4 for 32-bit integer.
+ * @param torder
+ * order of the byte endian, e.g. Datatype.ORDER_LE.
+ * @param tsign
+ * signed or unsigned of an integer, e.g. Datatype.SIGN_NONE.
+ * @param tbase
+ * the base datatype of the new datatype
+ * @return The new datatype object if successful; otherwise returns null.
+ * @throws Exception
+ * The exceptions thrown vary depending on the implementing
+ * class.
+ */
+ public Datatype createDatatype(int tclass, int tsize, int torder, int tsign, Datatype tbase) throws Exception
+ {
+ // Derived classes must override this function to use base type option
+ return createDatatype(tclass, tsize, torder, tsign);
+ }
+
+ // REVIEW DOCS for createDatatype(). Check and document exceptions.
+
+ /**
+ * Creates a new dataset in a file with/without chunking/compression.
+ *
+ * String name = "2D integer";
+ * Group pgroup = (Group) getRootObject();
+ * Datatype dtype = new H5Datatype(Datatype.CLASS_INTEGER, // class
+ * 4, // size in bytes
+ * Datatype.ORDER_LE, // byte order
+ * Datatype.SIGN_NONE); // unsigned
+ * long[] dims = { 100, 50 };
+ * long[] maxdims = dims;
+ * long[] chunks = null; // no
+ * // chunking
+ * int gzip = 0; // no compression
+ * Object data = null; // no initial data values
+ * Dataset d = (H5File) file.createScalarDS(name, pgroup, dtype, dims, maxdims, chunks, gzip, data);
+ *
+ *
+ * @param name
+ * name of the new dataset, e.g. "2D integer"
+ * @param pgroup
+ * parent group where the new dataset is created.
+ * @param type
+ * datatype of the new dataset.
+ * @param dims
+ * dimension sizes of the new dataset, e.g. long[] dims = {100, 50}.
+ * @param maxdims
+ * maximum dimension sizes of the new dataset, null if maxdims is the same as dims.
+ * @param chunks
+ * chunk sizes of the new dataset, null if no chunking.
+ * @param gzip
+ * GZIP compression level (1 to 9), 0 or negative values if no compression.
+ * @param fillValue
+ * default value.
+ * @param data
+ * data written to the new dataset, null if no data is written to the new dataset.
+ *
+ * @return The new dataset if successful; otherwise returns null
+ * @throws Exception
+ * The exceptions thrown vary depending on the implementing class.
+ */
+ public abstract Dataset createScalarDS(String name, Group pgroup, Datatype type, long[] dims, long[] maxdims,
+ long[] chunks, int gzip, Object fillValue, Object data) throws Exception;
+
+ public Dataset createScalarDS(String name, Group pgroup, Datatype type, long[] dims, long[] maxdims, long[] chunks,
+ int gzip, Object data) throws Exception {
+ return createScalarDS(name, pgroup, type, dims, maxdims, chunks, gzip, null, data);
+ }
+
+ // REVIEW DOCS for createScalarDS(). Check and document exceptions.
+
+ /**
+ * Creates a new compound dataset in a file with/without chunking and
+ * compression.
+ *
+ * String name = "2D compound";
+ * Group pgroup = (Group) getRootObject();
+ * long[] dims = {100, 50};
+ * long[] chunks = {1, 50};
+ * int gzip = 9;
+ * String[] memberNames = {"x", "y"};
+ *
+ * Datatype[] memberDatatypes = {
+ * new H5Datatype(Datatype.CLASS_INTEGER, Datatype.NATIVE,
+ * Datatype.NATIVE, Datatype.NATIVE)
+ * new H5Datatype(Datatype.CLASS_FLOAT, Datatype.NATIVE,
+ * Datatype.NATIVE, Datatype.NATIVE));
+ *
+ * int[] memberSizes = {1, 10};
+ * Object data = null; // no initial data values
+ * Dataset d = (H5File)file.createCompoundDS(name, pgroup, dims, null,
+ * chunks, gzip, memberNames, memberDatatypes, memberSizes, null);
+ *
+ *
+ * @param name
+ * name of the new dataset
+ * @param pgroup
+ * parent group where the new dataset is created.
+ * @param dims
+ * dimension sizes of the new dataset.
+ * @param maxdims
+ * maximum dimension sizes of the new dataset, null if maxdims is
+ * the same as dims.
+ * @param chunks
+ * chunk sizes of the new dataset, null if no chunking.
+ * @param gzip
+ * GZIP compression level (1 to 9), 0 or negative values if no
+ * compression.
+ * @param memberNames
+ * names of the members.
+ * @param memberDatatypes
+ * datatypes of the members.
+ * @param memberSizes
+ * array sizes of the members.
+ * @param data
+ * data written to the new dataset, null if no data is written to
+ * the new dataset.
+ *
+ * @return new dataset object if successful; otherwise returns null
+ * @throws UnsupportedOperationException
+ * If the implementing class does not support compound datasets.
+ * @throws Exception
+ * The exceptions thrown vary depending on the implementing
+ * class.
+ */
+ public Dataset createCompoundDS(String name, Group pgroup, long[] dims, long[] maxdims, long[] chunks, int gzip,
+ String[] memberNames, Datatype[] memberDatatypes, int[] memberSizes, Object data) throws Exception
+ // REVIEW DOCS for createCompoundDS(). Check and document exceptions.
+ {
+ // If the implementing subclass doesn't have this method then that
+ // format doesn't support Compound DataSets and we throw an
+ // exception.
+ throw new UnsupportedOperationException("Dataset FileFormat.createCompoundDS(...) is not implemented.");
+ }
+
+ /**
+ * Creates a new image in a file.
+ *
+ * String name = "2D image";
+ * Group pgroup = (Group) getRootObject();
+ * Datatype dtype = new H5Datatype(Datatype.CLASS_INTEGER, 1, Datatype.NATIVE, Datatype.SIGN_NONE);
+ * long[] dims = {100, 50};
+ * long[] maxdims = dims;
+ * long[] chunks = null; // no chunking
+ * int gzip = 0; // no compression
+ * int ncomp = 3; // RGB true color image
+ * int interlace = ScalarDS.INTERLACE_PIXEL;
+ * Object data = null; // no initial data values
+ * Dataset d = (H5File) file.createScalarDS(name, pgroup, dtype, dims, maxdims, chunks, gzip, ncomp, interlace, data);
+ *
+ *
+ * @param name
+ * name of the new image, "2D image".
+ * @param pgroup
+ * parent group where the new image is created.
+ * @param type
+ * datatype of the new image.
+ * @param dims
+ * dimension sizes of the new dataset, e.g. long[] dims = {100,
+ * 50}.
+ * @param maxdims
+ * maximum dimension sizes of the new dataset, null if maxdims is
+ * the same as dims.
+ * @param chunks
+ * chunk sizes of the new dataset, null if no chunking.
+ * @param gzip
+ * GZIP compression level (1 to 9), 0 or negative values if no
+ * compression.
+ * @param ncomp
+ * number of components of the new image, e.g. int ncomp = 3; //
+ * RGB true color image.
+ * @param interlace
+ * interlace mode of the image. Valid values are
+ * ScalarDS.INTERLACE_PIXEL, ScalarDS.INTERLACE_PLANEL and
+ * ScalarDS.INTERLACE_LINE.
+ * @param data
+ * data value of the image, null if no data.
+ *
+ * @return The new image object if successful; otherwise returns null
+ *
+ * @throws Exception
+ * The exceptions thrown vary depending on the implementing
+ * class.
+ */
+ public abstract Dataset createImage(
+ String name, Group pgroup, Datatype type, long[] dims, long[] maxdims, long[] chunks, int gzip, int ncomp,
+ int interlace, Object data) throws Exception;
+
+ // REVIEW DOCS for createImage(). Check and document exceptions.
+
+ /**
+ * Creates a new group with specified name in existing group.
+ *
+ * // Open the existing file with the source object.
+ * H5File existingFile = new H5File("existingFile.h5", FileFormat.READ);
+ * existingFile.open();
+ * // Our source object will be the root group.
+ * HObject srcObj = existingFile.get("/");
+ * // Create a new file.
+ * H5File newFile = new H5File("newFile.h5", FileFormat.CREATE);
+ * newFile.open();
+ * // Both copies in the new file will have the root group as their
+ * // destination group.
+ * Group dstGroup = (Group) newFile.get("/");
+ * // First copy goes to "/copy1" and second goes to "/copy2".
+ * // Notice that we can use either H5File instance to perform the copy.
+ * HObject copy1 = existingFile.copy(srcObj, dstGroup, "copy1");
+ * HObject copy2 = newFile.copy(srcObj, dstGroup, "copy2");
+ * // Close both the files.
+ * file.close();
+ * newFile.close();
+ *
+ *
+ * @param srcObj
+ * The object to copy.
+ * @param dstGroup
+ * The destination group for the new object.
+ * @param dstName
+ * The name of the new object. If dstName is null, the name of
+ * srcObj will be used.
+ *
+ * @return The new object, or null if the copy fails.
+ *
+ * @throws Exception
+ * are specific to the implementing class.
+ */
+ public abstract HObject copy(HObject srcObj, Group dstGroup, String dstName) throws Exception;
+
+ /**
+ * Deletes an object from a file.
+ *
+ * @param obj
+ * The object to delete.
+ * @throws Exception
+ * The exceptions thrown vary depending on the implementing
+ * class.
+ */
+ public abstract void delete(HObject obj) throws Exception;
+
+ // REVIEW DOCS for delete(). Check and document exceptions.
+
+ /**
+ * Attaches a given attribute to an object.
+ * FileFormat.FILE_CREATE_DELETE
as the second
+ * argument in the replacement method to mimic the behavior
+ * originally provided by this method.
+ *
+ * @param fileName
+ * The filename; a pathname string.
+ *
+ * @return the created file object
+ *
+ * @throws Exception if file cannot be created
+ */
+ @Deprecated
+ public final FileFormat create(String fileName) throws Exception {
+ return createFile(fileName, FileFormat.FILE_CREATE_DELETE);
+ }
+
+ /**
+ * @deprecated As of 2.4, replaced by {@link #createInstance(String, int)}
+ *
+ * The replacement method has identical functionality and a more
+ * descriptive name. Since open is used elsewhere to
+ * perform a different function this method has been deprecated.
+ *
+ * @param pathname
+ * The pathname string.
+ * @param access
+ * The file access properties
+ *
+ * @return the opened file object
+ *
+ * @throws Exception if the file cannot be opened
+ */
+ @Deprecated
+ public final FileFormat open(String pathname, int access) throws Exception {
+ return createInstance(pathname, access);
+ }
+
+ /**
+ * @deprecated As of 2.4, replaced by
+ * {@link #createCompoundDS(String, Group, long[], long[], long[], int, String[], Datatype[], int[], Object)}
+ * maxdims, chunks,
and gzip
. To mimic
+ * the behavior originally provided by this method, call the
+ * replacement method with the following parameter list:
+ * ( name, pgroup, dims, null, null, -1,
+ * memberNames, memberDatatypes, memberSizes, data );
+ *
+ * @param name
+ * The dataset name.
+ * @param pgroup
+ * The dataset parent.
+ * @param dims
+ * The dataset dimensions.
+ * @param memberNames
+ * The dataset compound member names.
+ * @param memberDatatypes
+ * The dataset compound member datatypes.
+ * @param memberSizes
+ * The dataset compound member sizes.
+ * @param data
+ * The dataset data.
+ *
+ * @return
+ * The dataset created.
+ *
+ * @throws Exception if the dataset cannot be created
+ */
+ @Deprecated
+ public final Dataset createCompoundDS(String name, Group pgroup, long[] dims, String[] memberNames,
+ Datatype[] memberDatatypes, int[] memberSizes, Object data) throws Exception {
+ return createCompoundDS(name, pgroup, dims, null, null, -1, memberNames, memberDatatypes, memberSizes, data);
+ }
+
+ /**
+ * @deprecated As of 2.4, replaced by {@link #copy(HObject, Group, String)}
+ * null
as the 3rd
+ * parameter.
+ *
+ * @param srcObj
+ * The object to be copied
+ * @param dstGroup
+ * The group to contain the copied object
+ *
+ * @return the copied object
+ *
+ * @throws Exception if object can not be copied
+ */
+ @Deprecated
+ public final HObject copy(HObject srcObj, Group dstGroup) throws Exception {
+ return copy(srcObj, dstGroup, null);
+ }
+
+ /**
+ * @deprecated As of 2.4, replaced by {@link #get(String)}
+ *
+ *
+ *
+ * @param fullPath
+ * The file path string.
+ *
+ * @return the object that has the given full path
+ *
+ * @throws Exception if the object can not be found
+ */
+ @Deprecated
+ public static final HObject getHObject(String fullPath) throws Exception {
+ if ((fullPath == null) || (fullPath.length() <= 0)) {
+ return null;
+ }
+
+ String filename = null, path = null;
+ int idx = fullPath.indexOf(FILE_OBJ_SEP);
+
+ if (idx > 0) {
+ filename = fullPath.substring(0, idx);
+ path = fullPath.substring(idx + FILE_OBJ_SEP.length());
+ if ((path == null) || (path.length() == 0)) {
+ path = "/";
+ }
+ }
+ else {
+ filename = fullPath;
+ path = "/";
+ }
+
+ return FileFormat.getHObject(filename, path);
+ };
+
+ /**
+ * @deprecated As of 2.4, replaced by {@link #get(String)}
+ *
+ *
+ *
+ * @param filename
+ * The filename string.
+ * @param path
+ * The path of the file
+ *
+ * @return the object that has the given filename and path returns null
+ *
+ * @throws Exception if the object can not be found
+ */
+ @Deprecated
+ public static final HObject getHObject(String filename, String path) throws Exception {
+ if ((filename == null) || (filename.length() <= 0)) {
+ throw new IllegalArgumentException("Invalid file name. " + filename);
+ }
+
+ if (!(new File(filename)).exists()) {
+ throw new IllegalArgumentException("File does not exists");
+ }
+
+ HObject obj = null;
+ FileFormat file = FileFormat.getInstance(filename);
+
+ if (file != null) {
+ obj = file.get(path);
+ if (obj == null) {
+ file.close();
+ }
+ }
+
+ return obj;
+ }
+
+ /**
+ * Finds an object by its object ID
+ *
+ * @param file
+ * the file containing the object
+ * @param oid
+ * the oid to search for
+ *
+ * @return the object that has the given OID; otherwise returns null
+ */
+ public static final HObject findObject(FileFormat file, long[] oid) {
+ log.trace("findObject(): start");
+
+ if ((file == null) || (oid == null)) {
+ log.debug("findObject(): file is null or oid is null");
+ log.trace("findObject(): finish");
+ return null;
+ }
+
+ HObject theObj = null;
+
+ HObject theRoot = file.getRootObject();
+ if (theRoot == null) {
+ log.debug("findObject(): rootObject is null");
+ log.trace("findObject(): finish");
+ return null;
+ }
+
+ Iterator
+ * Dataset dset = H5File.getObject("hdf5_test.h5", "/images/iceburg");
+ * ...
+ * // close the file through dset
+ * dset.getFileFormat().close();
+ *
+ *
+ *
+ *
+ *
+ * @return The new group if successful; otherwise returns null.
+ *
+ * @throws Exception
+ * The exceptions thrown vary depending on the implementing class.
+ */
+ public Group createGroup(String name, Group pgroup, long... gplist) throws Exception {
+ throw new UnsupportedOperationException("Unsupported operation. Subclasses must implement FileFormat:createGroup.");
+ }
+
+ /***
+ * Creates the group creation property list identifier, gcpl. This
+ * identifier is used when creating Groups.
+ *
+ * @param creationorder
+ * The order in which the objects in a group should be created.
+ * It can be Tracked or Indexed.
+ * @param maxcompact
+ * The maximum number of links to store in the group in a compact
+ * format.
+ * @param mindense
+ * The minimum number of links to store in the indexed
+ * format.Groups which are in indexed format and in which the
+ * number of links falls below this threshold are automatically
+ * converted to compact format.
+ *
+ * @return The gcpl identifier.
+ *
+ * @throws Exception
+ * The exceptions thrown vary depending on the implementing class.
+ */
+ public long createGcpl(int creationorder, int maxcompact, int mindense) throws Exception {
+ throw new UnsupportedOperationException("Unsupported operation. Subclasses must implement FileFormat:createGcpl.");
+ }
+
+ /**
+ * Creates a link to an existing object in the open file.
+ *
+ * Using {@link #Group(FileFormat, String, String, Group)}
+ *
+ * @param theFile
+ * the file containing the group.
+ * @param grpName
+ * the name of this group, e.g. "grp01".
+ * @param grpPath
+ * the full path of this group, e.g. "/groups/".
+ * @param grpParent
+ * the parent of this group.
+ * @param oid
+ * the oid of this group.
+ */
+ @Deprecated
+ public Group(FileFormat theFile, String grpName, String grpPath, Group grpParent, long[] oid) {
+ super(theFile, grpName, grpPath, oid);
+
+ this.parent = grpParent;
+ }
+
+ /**
+ * Clears up member list and other resources in memory for the group. Since
+ * the destructor will clear memory space, the function is usually not
+ * needed.
+ */
+ public void clear() {
+ if (memberList != null) {
+ ((Vector
+ * HObject
+ * __________________________|________________________________
+ * | | |
+ * Group Dataset Datatype
+ * | _________|___________ |
+ * | | | |
+ * | ScalarDS CompoundDS |
+ * | | | |
+ * ---------------------Implementing classes such as-------------------------
+ * ____|____ _____|______ _____|_____ _____|_____
+ * | | | | | | | |
+ * H5Group H4Group H5ScalarDS H4ScalarDS H5CompDS H4CompDS H5Datatype H4Datatype
+ *
+ *
+ *
+ * All HDF4 and HDF5 data objects are inherited from HObject. At the top level
+ * of the hierarchy, both HDF4 and HDF5 have the same super-classes, such as
+ * Group and Dataset. At the bottom level of the hierarchy, HDF4 and HDF5
+ * objects have their own implementation, such as H5Group, H5ScalarDS,
+ * H5CompoundDS, and H5Datatype.
+ *
+ * // retrieve the object ID
+ * try {
+ * byte[] ref_buf = H5.H5Rcreate(h5file.getFID(), this.getFullName(), HDF5Constants.H5R_OBJECT, -1);
+ * long[] oid = new long[1];
+ * oid[0] = HDFNativeData.byteToLong(ref_buf, 0);
+ * } catch (Exception ex) {
+ * }
+ *
+ *
+ * @version 2.0 4/2/2018
+ * @author Peter X. Cao, Jordan T. Henderson
+ * @see hdf.object.DataFormat
+ */
+public abstract class HObject implements Serializable {
+
+ /**
+ * The serialVersionUID is a universal version identifier for a Serializable
+ * class. Deserialization uses this number to ensure that a loaded class
+ * corresponds exactly to a serialized object. For details, see
+ * http://java.sun.com/j2se/1.5.0/docs/api/java/io/Serializable.html
+ */
+ private static final long serialVersionUID = -1723666708199882519L;
+
+ private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(HObject.class);
+
+ /**
+ * The separator of object path, i.e. "/".
+ */
+ public static final String SEPARATOR = "/";
+
+ /**
+ * The full path of the file that contains the object.
+ */
+ private String filename;
+
+ /**
+ * The file which contains the object
+ */
+ protected final FileFormat fileFormat;
+
+ /**
+ * The name of the data object. The root group has its default name, a
+ * slash. The name can be changed except the root group.
+ */
+ private String name;
+
+ /**
+ * The full path of the data object. The full path always starts with the
+ * root, a slash. The path cannot be changed. Also, a path must be ended with a
+ * slash. For example, /arrays/ints/
+ */
+ private String path;
+
+ /** The full name of the data object, i.e. "path + name" */
+ private String fullName;
+
+ /**
+ * Array of long integer storing unique identifier for the object.
+ *
+ * HDF5 objects are uniquely identified by an object reference. i.e.
+ * oid[0] = obj_id.
+ */
+ protected long[] oid;
+
+ /**
+ * The name of the Target Object that is being linked to.
+ */
+ protected String linkTargetObjName;
+
+ /**
+ * Number of attributes attached to the object.
+ */
+ // protected int nAttributes = -1;
+
+ /**
+ * Constructs an instance of a data object without name and path.
+ */
+ public HObject() {
+ this(null, null, null, null);
+ }
+
+ /**
+ * Constructs an instance of a data object with specific name and path.
+ *
+ * Using {@link #ScalarDS(FileFormat, String, String)}
+ *
+ * @param theFile
+ * the file that contains the data object.
+ * @param theName
+ * the name of the data object, e.g. "dset".
+ * @param thePath
+ * the full path of the data object, e.g. "/arrays/".
+ * @param oid
+ * the v of the data object.
+ */
+ @Deprecated
+ public ScalarDS(FileFormat theFile, String theName, String thePath, long[] oid) {
+ super(theFile, theName, thePath, oid);
+
+ palette = null;
+ isImage = false;
+ isTrueColor = false;
+ isText = false;
+ interlace = -1;
+ imageDataRange = null;
+ isImageDisplay = false;
+ isDefaultImageOrder = true;
+ isFillValueConverted = false;
+ filteredImageValues = new Vector<>();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see hdf.object.Dataset#clearData()
+ */
+ @Override
+ public void clearData() {
+ super.clearData();
+ unsignedConverted = false;
+ }
+
+ /**
+ * Converts the data values of this dataset to appropriate Java integer if they are unsigned integers.
+ *
+ * @see Dataset#convertToUnsignedC(Object)
+ * @see Dataset#convertFromUnsignedC(Object, Object)
+ *
+ * @return the converted data buffer.
+ */
+ @Override
+ public Object convertFromUnsignedC() {
+ log.trace("convertFromUnsignedC(): start");
+ // keep a copy of original buffer and the converted buffer
+ // so that they can be reused later to save memory
+ log.trace("convertFromUnsignedC(): unsigned={}", getDatatype().isUnsigned());
+ if ((data != null) && getDatatype().isUnsigned() && !unsignedConverted) {
+ log.trace("convertFromUnsignedC(): convert");
+ originalBuf = data;
+ convertedBuf = convertFromUnsignedC(originalBuf, convertedBuf);
+ data = convertedBuf;
+ unsignedConverted = true;
+
+ if (fillValue != null) {
+ if (!isFillValueConverted) {
+ fillValue = convertFromUnsignedC(fillValue, null);
+ isFillValueConverted = true;
+ }
+ }
+ }
+
+ log.trace("convertFromUnsignedC(): finish");
+ return data;
+ }
+
+ /**
+ * Converts Java integer data of this dataset back to unsigned C-type integer data if they are unsigned integers.
+ *
+ * @see Dataset#convertToUnsignedC(Object)
+ * @see Dataset#convertToUnsignedC(Object, Object)
+ * @see #convertFromUnsignedC(Object data_in)
+ *
+ * @return the converted data buffer.
+ */
+ @Override
+ public Object convertToUnsignedC() {
+ log.trace("convertToUnsignedC(): start");
+ // keep a copy of original buffer and the converted buffer
+ // so that they can be reused later to save memory
+ log.trace("convertToUnsignedC(): unsigned={}", getDatatype().isUnsigned());
+ if ((data != null) && getDatatype().isUnsigned()) {
+ log.trace("convertToUnsignedC(): convert");
+ convertedBuf = data;
+ originalBuf = convertToUnsignedC(convertedBuf, originalBuf);
+ data = originalBuf;
+ }
+
+ log.trace("convertToUnsignedC(): finish");
+ return data;
+ }
+
+ /**
+ * Returns the palette of this scalar dataset or null if palette does not exist.
+ *
+ * INTERLACE_PIXEL -- RGB components are contiguous, i.e. rgb, rgb, rgb, ...
+ * INTERLACE_LINE -- each RGB component is stored as a scan line
+ * INTERLACE_PLANE -- each RGB component is stored as a plane
+ *
+ *
+ * @return the interlace mode of a true color image (RGB).
+ */
+ public final int getInterlace() {
+ return interlace;
+ }
+
+ /**
+ * Returns the (min, max) pair of image data range.
+ *
+ * @return the (min, max) pair of image data range.
+ */
+ public double[] getImageDataRange() {
+ return imageDataRange;
+ }
+
+ /**
+ * Returns the fill values for the dataset.
+ *
+ * @return the fill values for the dataset.
+ */
+ @Override
+ public final Object getFillValue() {
+ return fillValue;
+ }
+}
diff --git a/src/main/java/hdf/object/Utils.java b/src/main/java/hdf/object/Utils.java
new file mode 100644
index 0000000..36de85e
--- /dev/null
+++ b/src/main/java/hdf/object/Utils.java
@@ -0,0 +1,48 @@
+/*****************************************************************************
+ * Copyright by The HDF Group. *
+ * Copyright by the Board of Trustees of the University of Illinois. *
+ * All rights reserved. *
+ * *
+ * This file is part of the HDF Java Products distribution. *
+ * The full copyright notice, including terms governing use, modification, *
+ * and redistribution, is contained in the files COPYING and Copyright.html. *
+ * COPYING can be found at the root of the source code distribution tree. *
+ * Or, see https://support.hdfgroup.org/products/licenses.html *
+ * If you do not have access to either file, you may request a copy from *
+ * help@hdfgroup.org. *
+ ****************************************************************************/
+
+package hdf.object;
+
+public final class Utils {
+ private Utils() {
+ throw new IllegalStateException("Utility class");
+ }
+
+ /**
+ * Retrieves the Java Runtime Class of the given Object. B = byte array, S = short array, I = int
+ * array, J = long array, F = float array, D = double array, L = class or interface
+ *
+ * @param o
+ * the Object to determine the Runtime Class of
+ * @return the Java Runtime Class of the given Object.
+ */
+ public static char getJavaObjectRuntimeClass(Object o) {
+ if (o == null)
+ return ' ';
+
+ String cName = o.getClass().getName();
+
+ if (cName.equals("java.lang.String") || cName.equals("java.util.Vector")
+ || cName.equals("java.util.Arrays$ArrayList") || cName.equals("java.util.ArrayList"))
+ return 'L';
+
+ int cIndex = cName.lastIndexOf('[');
+ if (cIndex >= 0) {
+ return cName.charAt(cIndex + 1);
+ }
+
+ return ' ';
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/hdf/object/h5/H5CompoundDS.java b/src/main/java/hdf/object/h5/H5CompoundDS.java
new file mode 100644
index 0000000..69844d3
--- /dev/null
+++ b/src/main/java/hdf/object/h5/H5CompoundDS.java
@@ -0,0 +1,2255 @@
+/*****************************************************************************
+ * Copyright by The HDF Group. *
+ * Copyright by the Board of Trustees of the University of Illinois. *
+ * All rights reserved. *
+ * *
+ * This file is part of the HDF Java Products distribution. *
+ * The full copyright notice, including terms governing use, modification, *
+ * and redistribution, is contained in the files COPYING and Copyright.html. *
+ * COPYING can be found at the root of the source code distribution tree. *
+ * Or, see https://support.hdfgroup.org/products/licenses.html *
+ * If you do not have access to either file, you may request a copy from *
+ * help@hdfgroup.org. *
+ ****************************************************************************/
+
+package hdf.object.h5;
+
+import java.lang.reflect.Array;
+import java.text.DecimalFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Vector;
+
+import hdf.hdf5lib.H5;
+import hdf.hdf5lib.HDF5Constants;
+import hdf.hdf5lib.HDFNativeData;
+import hdf.hdf5lib.exceptions.HDF5DataFiltersException;
+import hdf.hdf5lib.exceptions.HDF5Exception;
+import hdf.hdf5lib.structs.H5O_info_t;
+import hdf.object.Attribute;
+import hdf.object.CompoundDS;
+import hdf.object.Dataset;
+import hdf.object.Datatype;
+import hdf.object.FileFormat;
+import hdf.object.Group;
+import hdf.object.HObject;
+import hdf.object.Utils;
+
+/**
+ * The H5CompoundDS class defines an HDF5 dataset of compound datatypes.
+ *
+ * typedef struct s1_t {
+ * int a;
+ * float b;
+ * double c;
+ * } s1_t;
+ * s1_t s1[LENGTH];
+ * ...
+ * H5Dwrite(..., s1);
+ * H5Dread(..., s1);
+ *
+ *
+ * Values of compound data fields are stored in java.util.Vector object. We read and write compound
+ * data by fields instead of compound structure. As for the example above, the java.util.Vector
+ * object has three elements: int[LENGTH], float[LENGTH] and double[LENGTH]. Since Java understands
+ * the primitive datatypes of int, float and double, we will be able to read/write the compound data
+ * by field.
+ *
+ * @version 1.1 9/4/2007
+ * @author Peter X. Cao
+ */
+public class H5CompoundDS extends CompoundDS {
+ private static final long serialVersionUID = -5968625125574032736L;
+
+ private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(H5CompoundDS.class);
+
+ /**
+ * The list of attributes of this data object. Members of the list are instance of Attribute.
+ */
+ private List
+ * A --> m01
+ * A --> m02
+ * A --> nest1 --> m11
+ * A --> nest1 --> m12
+ * A --> nest1 --> nest2 --> m21
+ * A --> nest1 --> nest2 --> m22
+ * i.e.
+ * A = { m01, m02, nest1{m11, m12, nest2{ m21, m22}}}
+ *
+ *
+ * The flatNameList of compound dataset "A" will be {m01, m02, nest1[m11, nest1[m12,
+ * nest1[nest2[m21, nest1[nest2[m22}
+ *
+ */
+ private List
+ * Using {@link #H5CompoundDS(FileFormat, String, String)}
+ *
+ * @param theFile
+ * the file that contains the data object.
+ * @param theName
+ * the name of the data object, e.g. "dset".
+ * @param thePath
+ * the full path of the data object, e.g. "/arrays/".
+ * @param oid
+ * the oid of the data object.
+ */
+ @Deprecated
+ public H5CompoundDS(FileFormat theFile, String theName, String thePath, long[] oid) {
+ super(theFile, theName, thePath, oid);
+ objInfo = new H5O_info_t(-1L, -1L, 0, 0, -1L, 0L, 0L, 0L, 0L, null, null, null);
+
+ if ((oid == null) && (theFile != null)) {
+ // retrieve the object ID
+ try {
+ byte[] refBuf = H5.H5Rcreate(theFile.getFID(), this.getFullName(), HDF5Constants.H5R_OBJECT, -1);
+ this.oid = new long[1];
+ this.oid[0] = HDFNativeData.byteToLong(refBuf, 0);
+ }
+ catch (Exception ex) {
+ log.debug("constructor ID {} for {} failed H5Rcreate", theFile.getFID(), this.getFullName());
+ }
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see hdf.object.HObject#open()
+ */
+ @Override
+ public long open() {
+ log.trace("open(): start");
+
+ long did = -1;
+
+ try {
+ did = H5.H5Dopen(getFID(), getPath() + getName(), HDF5Constants.H5P_DEFAULT);
+ log.trace("open(): did={}", did);
+ }
+ catch (HDF5Exception ex) {
+ log.debug("open(): Failed to open dataset {}: ", getPath() + getName(), ex);
+ did = -1;
+ }
+
+ log.trace("open(): finish");
+ return did;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see hdf.object.HObject#close(int)
+ */
+ @Override
+ public void close(long did) {
+ log.trace("close(): start");
+
+ if (did >= 0) {
+ try {
+ H5.H5Fflush(did, HDF5Constants.H5F_SCOPE_LOCAL);
+ }
+ catch (Exception ex) {
+ log.debug("close(): H5Fflush(did {}) failure: ", did, ex);
+ }
+ try {
+ H5.H5Dclose(did);
+ }
+ catch (HDF5Exception ex) {
+ log.debug("close(): H5Dclose(did {}) failure: ", did, ex);
+ }
+ }
+
+ log.trace("close(): finish");
+ }
+
+ /**
+ * Retrieves datatype and dataspace information from file and sets the dataset
+ * in memory.
+ *
+ * dset = (Dataset) file.get(NAME_DATASET);
+ *
+ * // 1) get datatype and dataspace information from file
+ * dset.init();
+ * rank = dset.getRank(); // rank = 2, a 2D dataset
+ * count = dset.getSelectedDims();
+ * start = dset.getStartDims();
+ * dims = dset.getDims();
+ *
+ * // 2) select only one data point
+ * for (int i = 0; i < rank; i++) {
+ * start[0] = 0;
+ * count[i] = 1;
+ * }
+ *
+ * // 3) read one data point
+ * data = dset.getData();
+ *
+ * // 4) reset selection to the whole dataset
+ * dset.init();
+ *
+ * // 5) clean the memory data buffer
+ * dset.clearData();
+ *
+ * // 6) Read the whole dataset
+ * data = dset.getData();
+ *
+ */
+ @Override
+ public void init() {
+ log.trace("init(): start");
+
+ if (inited) {
+ resetSelection();
+ log.trace("init(): Dataset already initialized");
+ log.trace("init(): finish");
+ return; // already called. Initialize only once
+ }
+
+ long did = -1;
+ long tid = -1;
+ long sid = -1;
+ flatNameList = new Vector<>();
+ flatTypeList = new Vector<>();
+
+ did = open();
+ if (did >= 0) {
+ // check if it is an external or virtual dataset
+ long pid = -1;
+ try {
+ pid = H5.H5Dget_create_plist(did);
+ try {
+ int nfiles = H5.H5Pget_external_count(pid);
+ isExternal = (nfiles > 0);
+ int layoutType = H5.H5Pget_layout(pid);
+ if (isVirtual = (layoutType == HDF5Constants.H5D_VIRTUAL)) {
+ try {
+ long vmaps = H5.H5Pget_virtual_count(pid);
+ if (vmaps > 0) {
+ virtualNameList = new Vector<>();
+ for (long next = 0; next < vmaps; next++) {
+ try {
+ String fname = H5.H5Pget_virtual_filename(pid, next);
+ virtualNameList.add(fname);
+ log.trace("init(): virtualNameList[{}]={}", next, fname);
+ }
+ catch (Exception err) {
+ log.trace("init(): vds[{}] continue", next);
+ }
+ }
+ }
+ }
+ catch (Exception err) {
+ log.debug("init(): vds count error: ", err);
+ }
+ }
+ log.trace("init(): pid={} nfiles={} isExternal={} isVirtual={}", pid, nfiles, isExternal, isVirtual);
+ }
+ catch (Exception ex) {
+ log.debug("init(): check if it is an external or virtual dataset:", ex);
+ }
+ }
+ catch (Exception ex) {
+ log.debug("init(): H5Dget_create_plist() failure: ", ex);
+ }
+ finally {
+ try {
+ H5.H5Pclose(pid);
+ }
+ catch (Exception ex) {
+ log.debug("init(): H5Pclose(pid {}) failure: ", pid, ex);
+ }
+ }
+
+ try {
+ sid = H5.H5Dget_space(did);
+ rank = H5.H5Sget_simple_extent_ndims(sid);
+ tid = H5.H5Dget_type(did);
+ log.trace("init(): tid={} sid={} rank={}", tid, sid, rank);
+
+ if (rank == 0) {
+ // a scalar data point
+ rank = 1;
+ dims = new long[1];
+ dims[0] = 1;
+ log.trace("init(): rank is a scalar data point");
+ }
+ else {
+ dims = new long[rank];
+ maxDims = new long[rank];
+ H5.H5Sget_simple_extent_dims(sid, dims, maxDims);
+ log.trace("init(): rank={}, dims={}, maxDims={}", rank, dims, maxDims);
+ }
+
+ startDims = new long[rank];
+ selectedDims = new long[rank];
+
+ try {
+ datatype = new H5Datatype(tid);
+
+ log.trace("init(): tid={} has isText={} : isVLEN={} : isEnum={} : isUnsigned={} : isRegRef={}", tid,
+ datatype.isText(), datatype.isVLEN(), ((H5Datatype) datatype).isEnum(), datatype.isUnsigned(), ((H5Datatype) datatype).isRegRef());
+
+ H5Datatype.extractCompoundInfo((H5Datatype) datatype, "", flatNameList, flatTypeList);
+ }
+ catch (Exception ex) {
+ log.debug("init(): failed to create datatype for dataset: ", ex);
+ datatype = null;
+ }
+
+ // initialize member information
+ numberOfMembers = flatNameList.size();
+ log.trace("init(): numberOfMembers={}", numberOfMembers);
+
+ memberNames = new String[numberOfMembers];
+ memberTypes = new Datatype[numberOfMembers];
+ memberOrders = new int[numberOfMembers];
+ isMemberSelected = new boolean[numberOfMembers];
+ memberDims = new Object[numberOfMembers];
+
+ for (int i = 0; i < numberOfMembers; i++) {
+ isMemberSelected[i] = true;
+ memberOrders[i] = 1;
+ memberDims[i] = null;
+
+ try {
+ memberTypes[i] = flatTypeList.get(i);
+ log.trace("init()[{}]: memberTypes[{}]={}", i, i, memberTypes[i].getDescription());
+
+ if (memberTypes[i].isArray()) {
+ long mdim[] = memberTypes[i].getArrayDims();
+ int idim[] = new int[mdim.length];
+ int arrayNpoints = 1;
+
+ for (int j = 0; j < idim.length; j++) {
+ idim[j] = (int) mdim[j];
+ arrayNpoints *= idim[j];
+ }
+
+ memberDims[i] = idim;
+ memberOrders[i] = arrayNpoints;
+ }
+ }
+ catch (Exception ex) {
+ log.debug("init()[{}]: memberTypes[{}] get failure: ", i, i, ex);
+ memberTypes[i] = null;
+ }
+
+ try {
+ memberNames[i] = flatNameList.get(i);
+ log.trace("init()[{}]: memberNames[{}]={}", i, i, memberNames[i]);
+ }
+ catch (Exception ex) {
+ log.debug("init()[{}]: memberNames[{}] get failure: ", i, i, ex);
+ memberNames[i] = "null";
+ }
+ } // (int i=0; i