001/*****************************************************************************
002 * Copyright by The HDF Group.                                               *
003 * Copyright by the Board of Trustees of the University of Illinois.         *
004 * All rights reserved.                                                      *
005 *                                                                           *
006 * This file is part of the HDF Java Products distribution.                  *
007 * The full copyright notice, including terms governing use, modification,   *
008 * and redistribution, is contained in the files COPYING and Copyright.html. *
009 * COPYING can be found at the root of the source code distribution tree.    *
010 * Or, see https://support.hdfgroup.org/products/licenses.html               *
011 * If you do not have access to either file, you may request a copy from     *
012 * help@hdfgroup.org.                                                        *
013 ****************************************************************************/
014
015package hdf.object.h4;
016
017import java.lang.reflect.Array;
018import java.math.BigInteger;
019import java.util.Arrays;
020import java.util.Collection;
021import java.util.HashMap;
022import java.util.List;
023import java.util.Map;
024
025import org.slf4j.Logger;
026import org.slf4j.LoggerFactory;
027
028import hdf.object.Attribute;
029import hdf.object.CompoundDataFormat;
030import hdf.object.CompoundDS;
031import hdf.object.DataFormat;
032import hdf.object.Dataset;
033import hdf.object.Datatype;
034import hdf.object.FileFormat;
035import hdf.object.Group;
036import hdf.object.HObject;
037import hdf.object.MetaDataContainer;
038
039/**
040 * An attribute is a (name, value) pair of metadata attached to a primary data object such as a
041 * dataset, group or named datatype.
042 *
043 * Like a dataset, an attribute has a name, datatype and dataspace.
044 *
045 * For more details on attributes, <a href=
046 * "https://support.hdfgroup.org/HDF5/doc/UG/HDF5_Users_Guide-Responsive%20HTML5/index.html">HDF5
047 * User's Guide</a>
048 *
049 * The following code is an example of an attribute with 1D integer array of two elements.
050 *
051 * <pre>
052 * // Example of creating a new attribute
053 * // The name of the new attribute
054 * String name = "Data range";
055 * // Creating an unsigned 1-byte integer datatype
056 * Datatype type = new Datatype(Datatype.CLASS_INTEGER, // class
057 *                              1,                      // size in bytes
058 *                              Datatype.ORDER_LE,      // byte order
059 *                              Datatype.SIGN_NONE);    // unsigned
060 * // 1-D array of size two
061 * long[] dims = {2};
062 * // The value of the attribute
063 * int[] value = {0, 255};
064 * // Create a new attribute
065 * Attribute dataRange = new Attribute(name, type, dims);
066 * // Set the attribute value
067 * dataRange.setValue(value);
068 * // See FileFormat.writeAttribute() for how to attach an attribute to an object,
069 * &#64;see hdf.object.FileFormat#writeAttribute(HObject, Attribute, boolean)
070 * </pre>
071 *
072 * For a compound datatype, the value of an H4CompoundAttribute will be a 1D array of strings with field members separated
073 * by a comma. For example, "{0, 10.5}, {255, 20.0}, {512, 30.0}" is a compound attribute of {int,
074 * float} of three data points.
075 *
076 * @see hdf.object.Datatype
077 *
078 * @version 2.0 4/2/2018
079 * @author Peter X. Cao, Jordan T. Henderson
080 */
081public class H4CompoundAttribute extends CompoundDS implements Attribute {
082
083    private static final long serialVersionUID = 2072473407027648309L;
084
085    private static final Logger log = LoggerFactory.getLogger(H4CompoundAttribute.class);
086
087    /** The HObject to which this NC2Attribute is attached, Attribute interface */
088    protected HObject         parentObject;
089
090    /** additional information and properties for the attribute, Attribute interface */
091    private transient Map<String, Object> properties;
092
093    /**
094     * Create an attribute with specified name, data type and dimension sizes.
095     *
096     * For scalar attribute, the dimension size can be either an array of size one
097     * or null, and the rank can be either 1 or zero. Attribute is a general class
098     * and is independent of file format, e.g., the implementation of attribute
099     * applies to both HDF4 and HDF5.
100     *
101     * The following example creates a string attribute with the name "CLASS" and
102     * value "IMAGE".
103     *
104     * <pre>
105     * long[] attrDims = { 1 };
106     * String attrName = &quot;CLASS&quot;;
107     * String[] classValue = { &quot;IMAGE&quot; };
108     * Datatype attrType = null;
109     * try {
110     *     attrType = new H4Datatype(Datatype.CLASS_STRING, classValue[0].length() + 1, Datatype.NATIVE, Datatype.NATIVE);
111     * }
112     * catch (Exception ex) {}
113     * Attribute attr = new Attribute(attrName, attrType, attrDims);
114     * attr.setValue(classValue);
115     * </pre>
116     *
117     * @param parentObj
118     *            the HObject to which this Attribute is attached.
119     * @param attrName
120     *            the name of the attribute.
121     * @param attrType
122     *            the datatype of the attribute.
123     * @param attrDims
124     *            the dimension sizes of the attribute, null for scalar attribute
125     *
126     * @see hdf.object.Datatype
127     */
128    public H4CompoundAttribute(HObject parentObj, String attrName, Datatype attrType, long[] attrDims) {
129        this(parentObj, attrName, attrType, attrDims, null);
130    }
131
132    /**
133     * Create an attribute with specific name and value.
134     *
135     * For scalar attribute, the dimension size can be either an array of size one
136     * or null, and the rank can be either 1 or zero. Attribute is a general class
137     * and is independent of file format, e.g., the implementation of attribute
138     * applies to both HDF4 and HDF5.
139     *
140     * The following example creates a string attribute with the name "CLASS" and
141     * value "IMAGE".
142     *
143     * <pre>
144     * long[] attrDims = { 1 };
145     * String attrName = &quot;CLASS&quot;;
146     * String[] classValue = { &quot;IMAGE&quot; };
147     * Datatype attrType = null;
148     * try {
149     *     attrType = new H4Datatype(Datatype.CLASS_STRING, classValue[0].length() + 1, Datatype.NATIVE, Datatype.NATIVE);
150     * }
151     * catch (Exception ex) {}
152     * Attribute attr = new Attribute(attrName, attrType, attrDims, classValue);
153     * </pre>
154     *
155     * @param parentObj
156     *            the HObject to which this Attribute is attached.
157     * @param attrName
158     *            the name of the attribute.
159     * @param attrType
160     *            the datatype of the attribute.
161     * @param attrDims
162     *            the dimension sizes of the attribute, null for scalar attribute
163     * @param attrValue
164     *            the value of the attribute, null if no value
165     *
166     * @see hdf.object.Datatype
167     */
168    @SuppressWarnings({ "rawtypes", "unchecked", "deprecation" })
169    public H4CompoundAttribute(HObject parentObj, String attrName, Datatype attrType, long[] attrDims, Object attrValue) {
170        super((parentObj == null) ? null : parentObj.getFileFormat(), attrName,
171                (parentObj == null) ? null : parentObj.getFullName(), null);
172
173        log.trace("H4CompoundAttribute: start {}", parentObj);
174
175        this.parentObject = parentObj;
176
177        datatype = attrType;
178
179        if (attrValue != null) {
180            data = attrValue;
181            originalBuf = attrValue;
182            isDataLoaded = true;
183        }
184        properties = new HashMap();
185
186        if (attrDims == null) {
187            rank = 1;
188            dims = new long[] { 1 };
189        }
190        else {
191            dims = attrDims;
192            rank = dims.length;
193        }
194
195        selectedDims = new long[rank];
196        startDims = new long[rank];
197        selectedStride = new long[rank];
198
199        log.trace("attrName={}, attrType={}, attrValue={}, rank={}, isUnsigned={}",
200                attrName, getDatatype().getDescription(), data, rank, getDatatype().isUnsigned());
201
202        resetSelection();
203    }
204
205    /*
206     * (non-Javadoc)
207     *
208     * @see hdf.object.HObject#open()
209     */
210    @Override
211    public long open() {
212        if (parentObject == null) {
213            log.debug("open(): attribute's parent object is null");
214            return -1;
215        }
216
217        long aid = -1;
218        long pObjID = -1;
219
220        try {
221            pObjID = parentObject.open();
222            if (pObjID >= 0) {
223                if (this.getFileFormat().isThisType(FileFormat.getFileFormat(FileFormat.FILE_TYPE_HDF4))) {
224                    log.trace("open(): FILE_TYPE_HDF4");
225                    /*
226                     * TODO: Get type of HDF4 object this is attached to and retrieve attribute info.
227                     */
228                }
229            }
230
231            log.trace("open(): aid={}", aid);
232        }
233        catch (Exception ex) {
234            log.debug("open(): Failed to open attribute {}: ", getName(), ex);
235            aid = -1;
236        }
237        finally {
238            parentObject.close(pObjID);
239        }
240
241        return aid;
242    }
243
244    /*
245     * (non-Javadoc)
246     *
247     * @see hdf.object.HObject#close(int)
248     */
249    @Override
250    public void close(long aid) {
251        if (aid >= 0) {
252            if (this.getFileFormat().isThisType(FileFormat.getFileFormat(FileFormat.FILE_TYPE_HDF4))) {
253                log.trace("close(): FILE_TYPE_HDF4");
254                /*
255                 * TODO: Get type of HDF4 object this is attached to and close attribute.
256                 */
257            }
258        }
259    }
260
261    @Override
262    public void init() {
263        if (inited) {
264            resetSelection();
265            log.trace("init(): Attribute already inited");
266            return;
267        }
268
269        if (this.getFileFormat().isThisType(FileFormat.getFileFormat(FileFormat.FILE_TYPE_HDF4))) {
270            log.trace("init(): FILE_TYPE_HDF4");
271            /*
272             * TODO: If HDF4 attribute object needs to init dependent objects.
273             */
274            inited = true;
275        }
276
277        resetSelection();
278    }
279
280    /**
281     * Reads the data from file.
282     *
283     * read() reads the data from file to a memory buffer and returns the memory
284     * buffer. The dataset object does not hold the memory buffer. To store the
285     * memory buffer in the dataset object, one must call getData().
286     *
287     * By default, the whole dataset is read into memory. Users can also select
288     * a subset to read. Subsetting is done in an implicit way.
289     *
290     * @return the data read from file.
291     *
292     * @see #getData()
293     *
294     * @throws Exception
295     *             if object can not be read
296     * @throws OutOfMemoryError
297     *             if memory is exhausted
298     */
299    @Override
300    public Object read() throws Exception, OutOfMemoryError {
301        if (!inited)
302            init();
303
304        /*
305         * TODO: For now, convert a compound Attribute's data (String[]) into a List for
306         * convenient processing
307         */
308        if (getDatatype().isCompound() && !(data instanceof List)) {
309            List<String> valueList = Arrays.asList((String[]) data);
310
311            data = valueList;
312        }
313
314        return data;
315    }
316
317    /* Implement abstract Dataset */
318
319    /**
320     * Writes a memory buffer to the object in the file.
321     *
322     * @param buf
323     *            The buffer that contains the data values.
324     *
325     * @throws Exception
326     *             if data can not be written
327     */
328    @Override
329    public void write(Object buf) throws Exception {
330        log.trace("function of dataset: write(Object) start");
331        if (!buf.equals(data))
332            setData(buf);
333
334        init();
335
336        if (parentObject == null) {
337            log.debug("write(Object): parent object is null; nowhere to write attribute to");
338            return;
339        }
340
341        ((MetaDataContainer) getParentObject()).writeMetadata(this);
342    }
343
344    /*
345     * (non-Javadoc)
346     * @see hdf.object.Dataset#copy(hdf.object.Group, java.lang.String, long[], java.lang.Object)
347     */
348    @Override
349    public Dataset copy(Group pgroup, String dstName, long[] dims, Object buff) throws Exception {
350        // not supported
351        throw new UnsupportedOperationException("copy operation unsupported for H4.");
352    }
353
354    /*
355     * (non-Javadoc)
356     * @see hdf.object.Dataset#readBytes()
357     */
358    @Override
359    public byte[] readBytes() throws Exception {
360        // not supported
361        throw new UnsupportedOperationException("readBytes operation unsupported for H4.");
362    }
363
364    /**
365     * Given an array of bytes representing a compound Datatype and a start index
366     * and length, converts len number of bytes into the correct Object type and
367     * returns it.
368     *
369     * @param data
370     *            The byte array representing the data of the compound Datatype
371     * @param data_type
372     *            The type of data to convert the bytes to
373     * @param start
374     *            The start index of the bytes to get
375     * @param len
376     *            The number of bytes to convert
377     * @return The converted type of the bytes
378     */
379    protected Object convertCompoundByteMember(byte[] data, long data_type, long start, long len) {
380        return null;
381    }
382
383    /**
384     * Converts the data values of this data object to appropriate Java integers if
385     * they are unsigned integers.
386     *
387     * @see hdf.object.Dataset#convertToUnsignedC(Object)
388     * @see hdf.object.Dataset#convertFromUnsignedC(Object, Object)
389     *
390     * @return the converted data buffer.
391     */
392    @Override
393    public Object convertFromUnsignedC() {
394        throw new UnsupportedOperationException("H5CompoundDS:convertFromUnsignedC Unsupported operation.");
395    }
396
397    /**
398     * Converts Java integer data values of this data object back to unsigned C-type
399     * integer data if they are unsigned integers.
400     *
401     * @see hdf.object.Dataset#convertToUnsignedC(Object)
402     * @see hdf.object.Dataset#convertToUnsignedC(Object, Object)
403     *
404     * @return the converted data buffer.
405     */
406    @Override
407    public Object convertToUnsignedC() {
408        throw new UnsupportedOperationException("H5CompoundDS:convertToUnsignedC Unsupported operation.");
409    }
410
411    /* Implement interface Attribute */
412
413    /**
414     * Returns the HObject to which this Attribute is currently "attached".
415     *
416     * @return the HObject to which this Attribute is currently "attached".
417     */
418    public HObject getParentObject() {
419        return parentObject;
420    }
421
422    /**
423     * Sets the HObject to which this Attribute is "attached".
424     *
425     * @param pObj
426     *            the new HObject to which this Attribute is "attached".
427     */
428    public void setParentObject(HObject pObj) {
429        parentObject = pObj;
430    }
431
432    /**
433     * set a property for the attribute.
434     *
435     * @param key the attribute Map key
436     * @param value the attribute Map value
437     */
438    public void setProperty(String key, Object value) {
439        properties.put(key, value);
440    }
441
442    /**
443     * get a property for a given key.
444     *
445     * @param key the attribute Map key
446     *
447     * @return the property
448     */
449    public Object getProperty(String key) {
450        return properties.get(key);
451    }
452
453    /**
454     * get all property keys.
455     *
456     * @return the Collection of property keys
457     */
458    public Collection<String> getPropertyKeys() {
459        return properties.keySet();
460    }
461
462    /**
463     * Returns the name of the object. For example, "Raster Image #2".
464     *
465     * @return The name of the object.
466     */
467    public final String getAttributeName() {
468        return getName();
469    }
470
471    /**
472     * Retrieves the attribute data from the file.
473     *
474     * @return the attribute data.
475     *
476     * @throws Exception
477     *             if the data can not be retrieved
478     */
479    public final Object getAttributeData() throws Exception, OutOfMemoryError {
480        return getData();
481    }
482
483    /**
484     * Returns the datatype of the attribute.
485     *
486     * @return the datatype of the attribute.
487     */
488    public final Datatype getAttributeDatatype() {
489        return getDatatype();
490    }
491
492    /**
493     * Returns the space type for the attribute. It returns a
494     * negative number if it failed to retrieve the type information from
495     * the file.
496     *
497     * @return the space type for the attribute.
498     */
499    public final int getAttributeSpaceType() {
500        return getSpaceType();
501    }
502
503    /**
504     * Returns the rank (number of dimensions) of the attribute. It returns a
505     * negative number if it failed to retrieve the dimension information from
506     * the file.
507     *
508     * @return the number of dimensions of the attribute.
509     */
510    public final int getAttributeRank() {
511        return getRank();
512    }
513
514    /**
515     * Returns the selected size of the rows and columns of the attribute. It returns a
516     * negative number if it failed to retrieve the size information from
517     * the file.
518     *
519     * @return the selected size of the rows and colums of the attribute.
520     */
521    public final int getAttributePlane() {
522        return (int)getWidth() * (int)getHeight();
523    }
524
525    /**
526     * Returns the array that contains the dimension sizes of the data value of
527     * the attribute. It returns null if it failed to retrieve the dimension
528     * information from the file.
529     *
530     * @return the dimension sizes of the attribute.
531     */
532    public final long[] getAttributeDims() {
533        return getDims();
534    }
535
536    /**
537     * @return true if the data is a single scalar point; otherwise, returns
538     *         false.
539     */
540    public boolean isAttributeScalar() {
541        return isScalar();
542    }
543
544    /**
545     * Not for public use in the future.
546     *
547     * setData() is not safe to use because it changes memory buffer
548     * of the dataset object. Dataset operations such as write/read
549     * will fail if the buffer type or size is changed.
550     *
551     * @param d  the object data -must be an array of Objects
552     */
553    public void setAttributeData(Object d) {
554        setData(d);
555    }
556
557    /**
558     * Writes the memory buffer of this dataset to file.
559     *
560     * @throws Exception if buffer can not be written
561     */
562    public void writeAttribute() throws Exception {
563        write();
564    }
565
566    /**
567     * Writes the given data buffer into this attribute in a file.
568     *
569     * The data buffer is a vector that contains the data values of compound fields. The data is written
570     * into file as one data blob.
571     *
572     * @param buf
573     *            The vector that contains the data values of compound fields.
574     *
575     * @throws Exception
576     *             If there is an error at the library level.
577     */
578    public void writeAttribute(Object buf) throws Exception {
579        write(buf);
580    }
581
582    /**
583     * Returns a string representation of the data value. For
584     * example, "0, 255".
585     *
586     * For a compound datatype, it will be a 1D array of strings with field
587     * members separated by the delimiter. For example,
588     * "{0, 10.5}, {255, 20.0}, {512, 30.0}" is a compound attribute of {int,
589     * float} of three data points.
590     *
591     * @param delimiter
592     *            The delimiter used to separate individual data points. It
593     *            can be a comma, semicolon, tab or space. For example,
594     *            toString(",") will separate data by commas.
595     *
596     * @return the string representation of the data values.
597     */
598    public String toAttributeString(String delimiter) {
599        return toString(delimiter, -1);
600    }
601
602    /**
603     * Returns a string representation of the data value. For
604     * example, "0, 255".
605     *
606     * For a compound datatype, it will be a 1D array of strings with field
607     * members separated by the delimiter. For example,
608     * "{0, 10.5}, {255, 20.0}, {512, 30.0}" is a compound attribute of {int,
609     * float} of three data points.
610     *
611     * @param delimiter
612     *            The delimiter used to separate individual data points. It
613     *            can be a comma, semicolon, tab or space. For example,
614     *            toString(",") will separate data by commas.
615     * @param maxItems
616     *            The maximum number of Array values to return
617     *
618     * @return the string representation of the data values.
619     */
620    public String toAttributeString(String delimiter, int maxItems) {
621        return toString(delimiter, maxItems);
622    }
623}