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