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 COPYING file, which can be found  *
009 * at the root of the source code distribution tree,                         *
010 * or in https://www.hdfgroup.org/licenses.                                  *
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.nc2;
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 hdf.object.Attribute;
026import hdf.object.DataFormat;
027import hdf.object.Dataset;
028import hdf.object.Datatype;
029import hdf.object.FileFormat;
030import hdf.object.Group;
031import hdf.object.HObject;
032import hdf.object.ScalarDS;
033
034import org.slf4j.Logger;
035import org.slf4j.LoggerFactory;
036
037/**
038 * An attribute is a (name, value) pair of metadata attached to a primary data object such as a dataset, group
039 * or named datatype.
040 *
041 * Like a dataset, an attribute has a name, datatype and dataspace.
042 *
043 * For more details on attributes, <a
044 * href="https://support.hdfgroup.org/releases/hdf5/v1_14/v1_14_5/documentation/doxygen/_h5_a__u_g.html#sec_attribute">HDF5
045 * Attributes in HDF5 User Guide</a>
046 *
047 * The following code is an example of an attribute with 1D integer array of two elements.
048 *
049 * <pre>
050 * // Example of creating a new attribute
051 * // The name of the new attribute
052 * String name = "Data range";
053 * // Creating an unsigned 1-byte integer datatype
054 * Datatype type = new Datatype(Datatype.CLASS_INTEGER, // class
055 *                              1,                      // size in bytes
056 *                              Datatype.ORDER_LE,      // byte order
057 *                              Datatype.SIGN_NONE);    // unsigned
058 * // 1-D array of size two
059 * long[] dims = {2};
060 * // The value of the attribute
061 * int[] value = {0, 255};
062 * // Create a new attribute
063 * Attribute dataRange = new Attribute(name, type, dims);
064 * // Set the attribute value
065 * dataRange.setValue(value);
066 * // See FileFormat.writeAttribute() for how to attach an attribute to an object,
067 * &#64;see hdf.object.FileFormat#writeAttribute(HObject, Attribute, boolean)
068 * </pre>
069 *
070 *
071 * For an atomic datatype, the value of an Attribute will be a 1D array of integers, floats and strings. For a
072 * compound datatype, it will be a 1D array of strings with field members separated by a comma. For example,
073 * "{0, 10.5}, {255, 20.0}, {512, 30.0}" is a compound attribute of {int, float} of three data points.
074 *
075 * @see hdf.object.Datatype
076 *
077 * @version 2.0 4/2/2018
078 * @author Peter X. Cao, Jordan T. Henderson
079 */
080public class NC2Attribute extends ScalarDS implements Attribute {
081    private static final long serialVersionUID = 2072473407027648309L;
082
083    private static final Logger log = LoggerFactory.getLogger(NC2Attribute.class);
084
085    /** The HObject to which this NC2Attribute is attached, Attribute interface */
086    protected HObject parentObject;
087
088    /** additional information and properties for the attribute, Attribute interface */
089    private transient Map<String, Object> properties;
090
091    /**
092     * Create an attribute with specified name, data type and dimension sizes.
093     *
094     * For scalar attribute, the dimension size can be either an array of size one
095     * or null, and the rank can be either 1 or zero. Attribute is a general class
096     * and is independent of file format, e.g., the implementation of attribute
097     * applies to both HDF4 and HDF5.
098     *
099     * The following example creates a string attribute with the name "CLASS" and
100     * value "IMAGE".
101     *
102     * <pre>
103     * long[] attrDims = { 1 };
104     * String attrName = &quot;CLASS&quot;;
105     * String[] classValue = { &quot;IMAGE&quot; };
106     * Datatype attrType = null;
107     * try {
108     *     attrType = new NC2Datatype(Datatype.CLASS_STRING, classValue[0].length() + 1, Datatype.NATIVE,
109     * Datatype.NATIVE);
110     * }
111     * catch (Exception ex) {}
112     * Attribute attr = new Attribute(attrName, attrType, attrDims);
113     * attr.setValue(classValue);
114     * </pre>
115     *
116     * @param parentObj
117     *            the HObject to which this Attribute is attached.
118     * @param attrName
119     *            the name of the attribute.
120     * @param attrType
121     *            the datatype of the attribute.
122     * @param attrDims
123     *            the dimension sizes of the attribute, null for scalar attribute
124     *
125     * @see hdf.object.Datatype
126     */
127    public NC2Attribute(HObject parentObj, String attrName, Datatype attrType, long[] attrDims)
128    {
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 NC2Datatype(Datatype.CLASS_STRING, classValue[0].length() + 1, Datatype.NATIVE,
150     * Datatype.NATIVE);
151     * }
152     * catch (Exception ex) {}
153     * NC2Attribute attr = new NC2Attribute(attrName, attrType, attrDims, classValue);
154     * </pre>
155     *
156     * @param parentObj
157     *            the HObject to which this Attribute is attached.
158     * @param attrName
159     *            the name of the attribute.
160     * @param attrType
161     *            the datatype of the attribute.
162     * @param attrDims
163     *            the dimension sizes of the attribute, null for scalar attribute
164     * @param attrValue
165     *            the value of the attribute, null if no value
166     *
167     * @see hdf.object.Datatype
168     */
169    @SuppressWarnings({"rawtypes", "unchecked", "deprecation"})
170    public NC2Attribute(HObject parentObj, String attrName, Datatype attrType, long[] attrDims,
171                        Object attrValue)
172    {
173        super((parentObj == null) ? null : parentObj.getFileFormat(), attrName,
174              (parentObj == null) ? null : parentObj.getFullName(), null);
175
176        log.trace("NC2Attribute: start {}", parentObj);
177        this.parentObject = parentObj;
178
179        unsignedConverted = false;
180
181        datatype = attrType;
182
183        if (attrValue != null) {
184            data         = attrValue;
185            originalBuf  = attrValue;
186            isDataLoaded = true;
187        }
188        properties = new HashMap();
189
190        if (attrDims == null) {
191            rank = 1;
192            dims = new long[] {1};
193        }
194        else {
195            dims = attrDims;
196            rank = dims.length;
197        }
198
199        selectedDims   = new long[rank];
200        startDims      = new long[rank];
201        selectedStride = new long[rank];
202
203        log.trace("attrName={}, attrType={}, attrValue={}, rank={}, isUnsigned={}", attrName,
204                  getDatatype().getDescription(), data, rank, getDatatype().isUnsigned());
205
206        resetSelection();
207    }
208
209    /*
210     * (non-Javadoc)
211     *
212     * @see hdf.object.HObject#open()
213     */
214    @Override
215    public long open()
216    {
217        long aid    = -1;
218        long pObjID = -1;
219
220        if (parentObject == null) {
221            log.debug("open(): attribute's parent object is null");
222            return -1;
223        }
224
225        try {
226            pObjID = parentObject.open();
227            if (pObjID >= 0) {
228                if (this.getFileFormat().isThisType(FileFormat.getFileFormat(FileFormat.FILE_TYPE_NC3))) {
229                    log.trace("open(): FILE_TYPE_NC3");
230                    /*
231                     * TODO: Get type of netcdf3 object this is attached to and retrieve attribute info.
232                     */
233                }
234            }
235
236            log.trace("open(): aid={}", aid);
237        }
238        catch (Exception ex) {
239            log.debug("open(): Failed to open attribute {}: ", getName(), ex);
240            aid = -1;
241        }
242        finally {
243            parentObject.close(pObjID);
244        }
245
246        return aid;
247    }
248
249    /*
250     * (non-Javadoc)
251     *
252     * @see hdf.object.HObject#close(int)
253     */
254    @Override
255    public void close(long aid)
256    {
257        if (aid >= 0) {
258            if (this.getFileFormat().isThisType(FileFormat.getFileFormat(FileFormat.FILE_TYPE_NC3))) {
259                log.trace("close(): FILE_TYPE_NC3");
260                /*
261                 * TODO: Get type of netcdf3 object this is attached to and close attribute.
262                 */
263            }
264        }
265    }
266
267    @Override
268    public void init()
269    {
270        if (inited) {
271            resetSelection();
272            log.trace("init(): NC2Attribute already inited");
273            return;
274        }
275
276        if (this.getFileFormat().isThisType(FileFormat.getFileFormat(FileFormat.FILE_TYPE_NC3))) {
277            log.trace("init(): FILE_TYPE_NC3");
278            /*
279             * TODO: If netcdf3 attribute object needs to init dependent objects.
280             */
281            inited = true;
282        }
283
284        resetSelection();
285    }
286
287    /**
288     * Reads the data from file.
289     *
290     * read() reads the data from file to a memory buffer and returns the memory
291     * buffer. The dataset object does not hold the memory buffer. To store the
292     * memory buffer in the dataset object, one must call getData().
293     *
294     * By default, the whole dataset is read into memory. Users can also select
295     * a subset to read. Subsetting is done in an implicit way.
296     *
297     * @return the data read from file.
298     *
299     * @see #getData()
300     *
301     * @throws Exception
302     *             if object can not be read
303     * @throws OutOfMemoryError
304     *             if memory is exhausted
305     */
306    @Override
307    public Object read() throws Exception, OutOfMemoryError
308    {
309        if (!inited)
310            init();
311
312        return data;
313    }
314
315    /* Implement abstract Dataset */
316
317    /**
318     * Writes a memory buffer to the object in the file.
319     *
320     * @param buf
321     *            the data to write
322     *
323     * @throws Exception
324     *             if data can not be written
325     */
326    @Override
327    public void write(Object buf) throws Exception
328    {
329        log.trace("function of dataset: write(Object) start");
330        if (!buf.equals(data))
331            setData(buf);
332
333        init();
334
335        if (parentObject == null) {
336            log.debug("write(Object): parent object is null; nowhere to write attribute to");
337            return;
338        }
339    }
340
341    /*
342     * (non-Javadoc)
343     * @see hdf.object.Dataset#copy(hdf.object.Group, java.lang.String, long[], java.lang.Object)
344     */
345    @Override
346    public Dataset copy(Group pgroup, String dstName, long[] dims, Object buff) throws Exception
347    {
348        // not supported
349        throw new UnsupportedOperationException("copy operation unsupported for NC2.");
350    }
351
352    /*
353     * (non-Javadoc)
354     * @see hdf.object.Dataset#readBytes()
355     */
356    @Override
357    public byte[] readBytes() throws Exception
358    {
359        // not supported
360        throw new UnsupportedOperationException("readBytes operation unsupported for NC2.");
361    }
362
363    /* Implement interface Attribute */
364
365    /**
366     * Returns the HObject to which this Attribute is currently "attached".
367     *
368     * @return the HObject to which this Attribute is currently "attached".
369     */
370    public HObject getParentObject() { return parentObject; }
371
372    /**
373     * Sets the HObject to which this Attribute is "attached".
374     *
375     * @param pObj
376     *            the new HObject to which this Attribute is "attached".
377     */
378    public void setParentObject(HObject pObj) { parentObject = pObj; }
379
380    /**
381     * set a property for the attribute.
382     *
383     * @param key the attribute Map key
384     * @param value the attribute Map value
385     */
386    public void setProperty(String key, Object value) { properties.put(key, value); }
387
388    /**
389     * get a property for a given key.
390     *
391     * @param key the attribute Map key
392     *
393     * @return the property
394     */
395    public Object getProperty(String key) { return properties.get(key); }
396
397    /**
398     * get all property keys.
399     *
400     * @return the Collection of property keys
401     */
402    public Collection<String> getPropertyKeys() { return properties.keySet(); }
403
404    /**
405     * Returns the name of the object. For example, "Raster Image #2".
406     *
407     * @return The name of the object.
408     */
409    public final String getAttributeName() { return getName(); }
410
411    /**
412     * Retrieves the attribute data from the file.
413     *
414     * @return the attribute data.
415     *
416     * @throws Exception
417     *             if the data can not be retrieved
418     */
419    public final Object getAttributeData() throws Exception, OutOfMemoryError { return getData(); }
420
421    /**
422     * Returns the datatype of the attribute.
423     *
424     * @return the datatype of the attribute.
425     */
426    public final Datatype getAttributeDatatype() { return getDatatype(); }
427
428    /**
429     * Returns the space type for the attribute. It returns a
430     * negative number if it failed to retrieve the type information from
431     * the file.
432     *
433     * @return the space type for the attribute.
434     */
435    public final int getAttributeSpaceType() { return getSpaceType(); }
436
437    /**
438     * Returns the rank (number of dimensions) of the attribute. It returns a
439     * negative number if it failed to retrieve the dimension information from
440     * the file.
441     *
442     * @return the number of dimensions of the attribute.
443     */
444    public final int getAttributeRank() { return getRank(); }
445
446    /**
447     * Returns the selected size of the rows and columns of the attribute. It returns a
448     * negative number if it failed to retrieve the size information from
449     * the file.
450     *
451     * @return the selected size of the rows and colums of the attribute.
452     */
453    public final int getAttributePlane() { return (int)getWidth() * (int)getHeight(); }
454
455    /**
456     * Returns the array that contains the dimension sizes of the data value of
457     * the attribute. It returns null if it failed to retrieve the dimension
458     * information from the file.
459     *
460     * @return the dimension sizes of the attribute.
461     */
462    public final long[] getAttributeDims() { return getDims(); }
463
464    /**
465     * @return true if the dataspace is a NULL; otherwise, returns false.
466     */
467    public boolean isAttributeNULL() { return isNULL(); }
468
469    /**
470     * @return true if the data is a single scalar point; otherwise, returns false.
471     */
472    public boolean isAttributeScalar() { return isScalar(); }
473
474    /**
475     * Not for public use in the future.
476     *
477     * setData() is not safe to use because it changes memory buffer
478     * of the dataset object. Dataset operations such as write/read
479     * will fail if the buffer type or size is changed.
480     *
481     * @param d  the object data -must be an array of Objects
482     */
483    public void setAttributeData(Object d) { setData(d); }
484
485    /**
486     * Writes the memory buffer of this dataset to file.
487     *
488     * @throws Exception if buffer can not be written
489     */
490    public void writeAttribute() throws Exception { write(); }
491
492    /**
493     * Writes the given data buffer into this attribute in a file.
494     *
495     * The data buffer is a vector that contains the data values of compound fields. The data is written
496     * into file as one data blob.
497     *
498     * @param buf
499     *            The vector that contains the data values of compound fields.
500     *
501     * @throws Exception
502     *             If there is an error at the library level.
503     */
504    public void writeAttribute(Object buf) throws Exception { write(buf); }
505
506    /**
507     * Returns a string representation of the data value. For
508     * example, "0, 255".
509     *
510     * For a compound datatype, it will be a 1D array of strings with field
511     * members separated by the delimiter. For example,
512     * "{0, 10.5}, {255, 20.0}, {512, 30.0}" is a compound attribute of {int,
513     * float} of three data points.
514     *
515     * @param delimiter
516     *            The delimiter used to separate individual data points. It
517     *            can be a comma, semicolon, tab or space. For example,
518     *            toString(",") will separate data by commas.
519     *
520     * @return the string representation of the data values.
521     */
522    public String toAttributeString(String delimiter) { return toString(delimiter, -1); }
523
524    /**
525     * Returns a string representation of the data value. For
526     * example, "0, 255".
527     *
528     * For a compound datatype, it will be a 1D array of strings with field
529     * members separated by the delimiter. For example,
530     * "{0, 10.5}, {255, 20.0}, {512, 30.0}" is a compound attribute of {int,
531     * float} of three data points.
532     *
533     * @param delimiter
534     *            The delimiter used to separate individual data points. It
535     *            can be a comma, semicolon, tab or space. For example,
536     *            toString(",") will separate data by commas.
537     * @param maxItems
538     *            The maximum number of Array values to return
539     *
540     * @return the string representation of the data values.
541     */
542    public String toAttributeString(String delimiter, int maxItems) { return toString(delimiter, maxItems); }
543}