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 file COPYING.                     *
009 * COPYING can be found at the root of the source code distribution tree.    *
010 * If you do not have access to this file, you may request a copy from       *
011 * help@hdfgroup.org.                                                        *
012 ****************************************************************************/
013
014package hdf.object;
015
016import java.lang.reflect.Array;
017import java.math.BigInteger;
018import java.util.Collection;
019import java.util.HashMap;
020import java.util.Map;
021
022/**
023 * An attribute is a (name, value) pair of metadata attached to a primary data
024 * object such as a dataset, group or named datatype.
025 * <p>
026 * Like a dataset, an attribute has a name, datatype and dataspace.
027 *
028 * <p>
029 * For more details on attributes,
030 * <a href="https://www.hdfgroup.org/HDF5/doc/UG/HDF5_Users_Guide-Responsive%20HTML5/index.html">HDF5 User's Guide</a>
031 * <p>
032 *
033 * The following code is an example of an attribute with 1D integer array of two
034 * elements.
035 *
036 * <pre>
037 * // Example of creating a new attribute
038 * // The name of the new attribute
039 * String name = "Data range";
040 * // Creating an unsigned 1-byte integer datatype
041 * Datatype type = new Datatype(Datatype.CLASS_INTEGER, // class
042 *                              1,                      // size in bytes
043 *                              Datatype.ORDER_LE,      // byte order
044 *                              Datatype.SIGN_NONE);    // signed or unsigned
045 * // 1-D array of size two
046 * long[] dims = {2};
047 * // The value of the attribute
048 * int[] value = {0, 255};
049 * // Create a new attribute
050 * Attribute dataRange = new Attribute(name, type, dims);
051 * // Set the attribute value
052 * dataRange.setValue(value);
053 * // See FileFormat.writeAttribute() for how to attach an attribute to an object,
054 * @see hdf.object.FileFormat#writeAttribute(HObject, Attribute, boolean)
055 * </pre>
056 *
057 * @see hdf.object.Datatype
058 *
059 * @version 1.1 9/4/2007
060 * @author Peter X. Cao
061 */
062public class Attribute implements Metadata {
063    private static final long serialVersionUID = 2072473407027648309L;
064
065    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(Attribute.class);
066
067    /** The name of the attribute. */
068    private final String      name;
069
070    /** The datatype of the attribute. */
071    private final Datatype    type;
072
073    /** The rank of the data value of the attribute. */
074    private int               rank;
075
076    /** The dimension sizes of the attribute. */
077    private long[]            dims;
078
079    /** The value of the attribute. */
080    private Object            value;
081
082    /** additional information and properties for the attribute */
083    private Map<String, Object>  properties;
084
085    /** Flag to indicate if the datatype is an unsigned integer. */
086    private boolean           isUnsigned;
087
088    /** flag to indicate if the dataset is a single scalar point */
089    protected boolean         isScalar         = false;
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
095     * one or null, and the rank can be either 1 or zero. Attribute is a general
096     * class and is independent of file format, e.g., the implementation of
097     * attribute applies to both HDF4 and HDF5.
098     * <p>
099     * The following example creates a string attribute with the name "CLASS"
100     * and value "IMAGE".
101     *
102     * <pre>
103     * long[] attrDims = { 1 };
104     * String attrName = &quot;CLASS&quot;;
105     * String[] classValue = { &quot;IMAGE&quot; };
106     * Datatype attrType = new H5Datatype(Datatype.CLASS_STRING, classValue[0].length() + 1, -1, -1);
107     * Attribute attr = new Attribute(attrName, attrType, attrDims);
108     * attr.setValue(classValue);
109     * </pre>
110     *
111     * @param attrName
112     *            the name of the attribute.
113     * @param attrType
114     *            the datatype of the attribute.
115     * @param attrDims
116     *            the dimension sizes of the attribute, null for scalar
117     *            attribute
118     *
119     * @see hdf.object.Datatype
120     */
121    public Attribute(String attrName, Datatype attrType, long[] attrDims) {
122        this(attrName, attrType, attrDims, null);
123    }
124
125    /**
126     * Create an attribute with specific name and value.
127     *
128     * For scalar attribute, the dimension size can be either an array of size
129     * one or null, and the rank can be either 1 or zero. Attribute is a general
130     * class and is independent of file format, e.g., the implementation of
131     * attribute applies to both HDF4 and HDF5.
132     * <p>
133     * The following example creates a string attribute with the name "CLASS"
134     * and value "IMAGE".
135     *
136     * <pre>
137     * long[] attrDims = { 1 };
138     * String attrName = &quot;CLASS&quot;;
139     * String[] classValue = { &quot;IMAGE&quot; };
140     * Datatype attrType = new H5Datatype(Datatype.CLASS_STRING, classValue[0].length() + 1, -1, -1);
141     * Attribute attr = new Attribute(attrName, attrType, attrDims, classValue);
142     * </pre>
143     *
144     * @param attrName
145     *            the name of the attribute.
146     * @param attrType
147     *            the datatype of the attribute.
148     * @param attrDims
149     *            the dimension sizes of the attribute, null for scalar
150     *            attribute
151     * @param attrValue
152     *            the value of the attribute, null if no value
153     *
154     * @see hdf.object.Datatype
155     */
156    @SuppressWarnings({"rawtypes","unchecked"})
157    public Attribute(String attrName, Datatype attrType, long[] attrDims, Object attrValue) {
158        name = attrName;
159        type = attrType;
160        dims = attrDims;
161        value = null;
162        properties = new HashMap();
163        rank = 0;
164        log.trace("Attribute: {}, attrValue={}", attrName, attrValue);
165
166        if (dims != null) {
167            rank = dims.length;
168        }
169        else {
170            isScalar = true;
171            rank = 1;
172            dims = new long[] { 1 };
173        }
174        if (attrValue != null) {
175            value = attrValue;
176        }
177
178        isUnsigned = (type.getDatatypeSign() == Datatype.SIGN_NONE);
179        log.trace("Attribute: finish");
180    }
181
182    /**
183     * Returns the value of the attribute. For an atomic datatype, this will be
184     * a 1D array of integers, floats and strings. For a compound datatype, it
185     * will be a 1D array of strings with field members separated by a comma. For
186     * example, "{0, 10.5}, {255, 20.0}, {512, 30.0}" is a compound attribute of
187     * {int, float} of three data points.
188     *
189     * @return the value of the attribute, or null if failed to retrieve data
190     *         from file.
191     */
192    public Object getValue() {
193        return value;
194    }
195
196    /**
197     * set a property for the attribute.
198     *
199     * @param key the attribute Map key
200     * @param value the attribute Map value
201     */
202    public void setProperty(String key, Object value)
203    {
204        properties.put(key, value);
205    }
206
207    /**
208     * get a property for a given key.
209     *
210     * @param key the attribute Map key
211     *
212     * @return the property
213     */
214    public Object getProperty(String key)
215    {
216        return properties.get(key);
217    }
218
219    /**
220     * get all property keys.
221     *
222     * @return the Collection of property keys
223     */
224    public Collection<String> getPropertyKeys()
225    {
226        return properties.keySet();
227    }
228
229
230    /**
231     * Sets the value of the attribute. It returns null if it failed to
232     * retrieve the name from file.
233     *
234     * @param theValue
235     *            The value of the attribute to set
236     */
237    public void setValue(Object theValue) {
238        value = theValue;
239    }
240
241    /**
242     * Returns the name of the attribute.
243     *
244     * @return the name of the attribute.
245     */
246    public String getName() {
247        return name;
248    }
249
250    /**
251     * Returns the rank (number of dimensions) of the attribute. It returns a
252     * negative number if it failed to retrieve the dimension information from
253     * file.
254     *
255     * @return the number of dimensions of the attribute.
256     */
257    public int getRank() {
258        return rank;
259    }
260
261    /**
262     * Returns the dimension sizes of the data value of the attribute. It
263     * returns null if it failed to retrieve the dimension information from file.
264     *
265     * @return the dimension sizes of the attribute.
266     */
267    public long[] getDataDims() {
268        return dims;
269    }
270
271    /**
272     * Returns the datatype of the attribute. It returns null if it failed to
273     * retrieve the datatype information from file.
274     *
275     * @return the datatype of the attribute.
276     */
277    public Datatype getType() {
278        return type;
279    }
280
281    /**
282     * @return true if the data is a single scalar point; otherwise, returns
283     *         false.
284     */
285    public boolean isScalar() {
286        return isScalar;
287    }
288
289    /**
290     * Checks if the data type of this attribute is an unsigned integer.
291     *
292     * @return true if the data type of the attribute is an unsigned integer;
293     *         otherwise returns false.
294     */
295    public boolean isUnsigned() {
296        return isUnsigned;
297    }
298
299    /**
300     * Return the name of the attribute.
301     *
302     * @see #toString(String delimiter)
303     */
304    @Override
305    public String toString() {
306        return name;
307    }
308
309    /**
310     * Returns a string representation of the data value of the attribute. For
311     * example, "0, 255".
312     * <p>
313     * For a compound datatype, it will be a 1D array of strings with field
314     * members separated by the delimiter. For example,
315     * "{0, 10.5}, {255, 20.0}, {512, 30.0}" is a compound attribute of {int,
316     * float} of three data points.
317     * <p>
318     *
319     * @param delimiter
320     *            The delimiter used to separate individual data points. It
321     *            can be a comma, semicolon, tab or space. For example,
322     *            toString(",") will separate data by commas.
323     *
324     * @return the string representation of the data values.
325     */
326    public String toString(String delimiter) {
327        log.trace("toString(): start");
328
329        if (value == null) {
330            log.debug("toString(): value is null");
331            log.trace("toString(): finish");
332            return null;
333        }
334
335        Class<? extends Object> valClass = value.getClass();
336
337        if (!valClass.isArray()) {
338            log.trace("toString(): finish");
339            return value.toString();
340        }
341
342        // attribute value is an array
343        StringBuffer sb = new StringBuffer();
344        int n = Array.getLength(value);
345
346        boolean is_unsigned = (this.getType().getDatatypeSign() == Datatype.SIGN_NONE);
347        boolean is_enum = (this.getType().getDatatypeClass() == Datatype.CLASS_ENUM);
348        log.trace("toString: is_enum={} is_unsigned={} Array.getLength={}", is_enum, is_unsigned, n);
349        if(is_enum) {
350            String cname = valClass.getName();
351            char dname = cname.charAt(cname.lastIndexOf("[") + 1);
352            log.trace("toString: is_enum with cname={} dname={}", cname, dname);
353
354            String enum_members = this.getType().getEnumMembers();
355            log.trace("toString: is_enum enum_members={}", enum_members);
356            Map<String,String> map = new HashMap<String,String>();
357            String[] entries = enum_members.split(",");
358            for (String entry : entries) {
359                String[] keyValue = entry.split("=");
360                map.put(keyValue[1],keyValue[0]);
361                log.trace("toString: is_enum value={} name={}", keyValue[1],keyValue[0]);
362            }
363            String theValue = null;
364            switch (dname) {
365                case 'B':
366                    byte[] barray = (byte[]) value;
367                    short sValue = barray[0];
368                    theValue = String.valueOf(sValue);
369                    if (map.containsKey(theValue)) {
370                        sb.append(map.get(theValue));
371                    }
372                    else
373                        sb.append(sValue);
374                    for (int i = 1; i < n; i++) {
375                        sb.append(delimiter);
376                        sValue = barray[i];
377                        theValue = String.valueOf(sValue);
378                        if (map.containsKey(theValue)) {
379                            sb.append(map.get(theValue));
380                        }
381                        else
382                            sb.append(sValue);
383                    }
384                    break;
385                case 'S':
386                    short[] sarray = (short[]) value;
387                    int iValue = sarray[0];
388                    theValue = String.valueOf(iValue);
389                    if (map.containsKey(theValue)) {
390                        sb.append(map.get(theValue));
391                    }
392                    else
393                        sb.append(iValue);
394                    for (int i = 1; i < n; i++) {
395                        sb.append(delimiter);
396                        iValue = sarray[i];
397                        theValue = String.valueOf(iValue);
398                        if (map.containsKey(theValue)) {
399                            sb.append(map.get(theValue));
400                        }
401                        else
402                            sb.append(iValue);
403                    }
404                    break;
405                case 'I':
406                    int[] iarray = (int[]) value;
407                    long lValue = iarray[0];
408                    theValue = String.valueOf(lValue);
409                    if (map.containsKey(theValue)) {
410                        sb.append(map.get(theValue));
411                    }
412                    else
413                        sb.append(lValue);
414                    for (int i = 1; i < n; i++) {
415                        sb.append(delimiter);
416                        lValue = iarray[i];
417                        theValue = String.valueOf(lValue);
418                        if (map.containsKey(theValue)) {
419                            sb.append(map.get(theValue));
420                        }
421                        else
422                            sb.append(lValue);
423                    }
424                    break;
425                case 'J':
426                    long[] larray = (long[]) value;
427                    Long l = (Long) larray[0];
428                    theValue = Long.toString(l);
429                    if (map.containsKey(theValue)) {
430                        sb.append(map.get(theValue));
431                    }
432                    else
433                        sb.append(theValue);
434                    for (int i = 1; i < n; i++) {
435                        sb.append(delimiter);
436                        l = (Long) larray[i];
437                        theValue = Long.toString(l);
438                        if (map.containsKey(theValue)) {
439                            sb.append(map.get(theValue));
440                        }
441                        else
442                            sb.append(theValue);
443                    }
444                    break;
445                default:
446                    sb.append(Array.get(value, 0));
447                    for (int i = 1; i < n; i++) {
448                        sb.append(delimiter);
449                        sb.append(Array.get(value, i));
450                    }
451                    break;
452            }
453        }
454        else if (is_unsigned) {
455            String cname = valClass.getName();
456            char dname = cname.charAt(cname.lastIndexOf("[") + 1);
457            log.trace("toString: is_unsigned with cname={} dname={}", cname, dname);
458
459            switch (dname) {
460                case 'B':
461                    byte[] barray = (byte[]) value;
462                    short sValue = barray[0];
463                    if (sValue < 0) {
464                        sValue += 256;
465                    }
466                    sb.append(sValue);
467                    for (int i = 1; i < n; i++) {
468                        sb.append(delimiter);
469                        sValue = barray[i];
470                        if (sValue < 0) {
471                            sValue += 256;
472                        }
473                        sb.append(sValue);
474                    }
475                    break;
476                case 'S':
477                    short[] sarray = (short[]) value;
478                    int iValue = sarray[0];
479                    if (iValue < 0) {
480                        iValue += 65536;
481                    }
482                    sb.append(iValue);
483                    for (int i = 1; i < n; i++) {
484                        sb.append(delimiter);
485                        iValue = sarray[i];
486                        if (iValue < 0) {
487                            iValue += 65536;
488                        }
489                        sb.append(iValue);
490                    }
491                    break;
492                case 'I':
493                    int[] iarray = (int[]) value;
494                    long lValue = iarray[0];
495                    if (lValue < 0) {
496                        lValue += 4294967296L;
497                    }
498                    sb.append(lValue);
499                    for (int i = 1; i < n; i++) {
500                        sb.append(delimiter);
501                        lValue = iarray[i];
502                        if (lValue < 0) {
503                            lValue += 4294967296L;
504                        }
505                        sb.append(lValue);
506                    }
507                    break;
508                case 'J':
509                    long[] larray = (long[]) value;
510                    Long l = (Long) larray[0];
511                    String theValue = Long.toString(l);
512                    if (l < 0) {
513                        l = (l << 1) >>> 1;
514                        BigInteger big1 = new BigInteger("9223372036854775808"); // 2^65
515                        BigInteger big2 = new BigInteger(l.toString());
516                        BigInteger big = big1.add(big2);
517                        theValue = big.toString();
518                    }
519                    sb.append(theValue);
520                    for (int i = 1; i < n; i++) {
521                        sb.append(delimiter);
522                        l = (Long) larray[i];
523                        theValue = Long.toString(l);
524                        if (l < 0) {
525                            l = (l << 1) >>> 1;
526                            BigInteger big1 = new BigInteger("9223372036854775808"); // 2^65
527                            BigInteger big2 = new BigInteger(l.toString());
528                            BigInteger big = big1.add(big2);
529                            theValue = big.toString();
530                        }
531                        sb.append(theValue);
532                    }
533                    break;
534                default:
535                    sb.append(Array.get(value, 0));
536                    for (int i = 1; i < n; i++) {
537                        sb.append(delimiter);
538                        sb.append(Array.get(value, i));
539                    }
540                    break;
541            }
542        }
543        else {
544            sb.append(Array.get(value, 0));
545            for (int i = 1; i < n; i++) {
546                sb.append(delimiter);
547                sb.append(Array.get(value, i));
548            }
549        }
550
551        log.trace("toString: finish");
552        return sb.toString();
553    }
554}