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 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 attibutes, see {@link <a
031 * 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    public void setProperty(String key, Object value) 
208    {
209        properties.put(key, value);
210    }
211    
212    /**
213     * get a property for a given key. 
214     */
215    public Object getProperty(String key) 
216    {
217        return properties.get(key);
218    }
219
220    /**
221     * get all property keys. 
222     */
223    public Collection<String> getPropertyKeys() 
224    {
225        return properties.keySet();
226    }
227
228    
229    /**
230     * Sets the value of the attribute. It returns null if failed to retrieve
231     * the name from file.
232     * 
233     * @param theValue
234     *            The value of the attribute to set
235     */
236    public void setValue(Object theValue) {
237        value = theValue;
238    }
239
240    /**
241     * Returns the name of the attribute.
242     * 
243     * @return the name of the attribute.
244     */
245    public String getName() {
246        return name;
247    }
248
249    /**
250     * Returns the rank (number of dimensions) of the attribute. It returns a
251     * negative number if failed to retrieve the dimension information from
252     * file.
253     * 
254     * @return the number of dimensions of the attribute.
255     */
256    public int getRank() {
257        return rank;
258    }
259
260    /**
261     * Returns the dimension sizes of the data value of the attribute. It
262     * returns null if failed to retrieve the dimension information from file.
263     * 
264     * @return the dimension sizes of the attribute.
265     */
266    public long[] getDataDims() {
267        return dims;
268    }
269
270    /**
271     * Returns the datatype of the attribute. It returns null if failed to
272     * retrieve the datatype information from file.
273     * 
274     * @return the datatype of the attribute.
275     */
276    public Datatype getType() {
277        return type;
278    }
279
280    /**
281     * @return true if the data is a single scalar point; otherwise, returns
282     *         false.
283     */
284    public boolean isScalar() {
285        return isScalar;
286    }
287
288    /**
289     * Checks if the data type of this attribute is an unsigned integer.
290     * 
291     * @return true if the data type of the attribute is an unsigned integer;
292     *         otherwise returns false.
293     */
294    public boolean isUnsigned() {
295        return isUnsigned;
296    }
297
298    /**
299     * Return the name of the attribute.
300     * 
301     * @see #toString(String delimiter)
302     */
303    @Override
304    public String toString() {
305        return name;
306    }
307
308    /**
309     * Returns a string representation of the data value of the attribute. For
310     * example, "0, 255".
311     * <p>
312     * For compound datatype, it will be an 1D array of strings with field
313     * members separated by comma. For example,
314     * "{0, 10.5}, {255, 20.0}, {512, 30.0}" is a compound attribute of {int,
315     * float} of three data points.
316     * <p>
317     * 
318     * @param delimiter
319     *            The delimiter to separate individual data point. It can be
320     *            comma, semicolon, tab or space. For example, to String(",")
321     *            will separate data by comma.
322     * 
323     * @return the string representation of the data values.
324     */
325    public String toString(String delimiter) {
326        if (value == null) {
327            return null;
328        }
329        log.trace("toString: start");
330
331        Class<? extends Object> valClass = value.getClass();
332
333        if (!valClass.isArray()) {
334            return value.toString();
335        }
336
337        // attribute value is an array
338        StringBuffer sb = new StringBuffer();
339        int n = Array.getLength(value);
340
341        boolean is_unsigned = (this.getType().getDatatypeSign() == Datatype.SIGN_NONE);
342        boolean is_enum = (this.getType().getDatatypeClass() == Datatype.CLASS_ENUM);
343        log.trace("toString: is_enum={} is_unsigned={} Array.getLength={}", is_enum, is_unsigned, n);
344        if (is_unsigned) {
345            String cname = valClass.getName();
346            char dname = cname.charAt(cname.lastIndexOf("[") + 1);
347            log.trace("toString: is_unsigned with cname={} dname={}", cname, dname);
348
349            switch (dname) {
350                case 'B':
351                    byte[] barray = (byte[]) value;
352                    short sValue = barray[0];
353                    if (sValue < 0) {
354                        sValue += 256;
355                    }
356                    sb.append(sValue);
357                    for (int i = 1; i < n; i++) {
358                        sb.append(delimiter);
359                        sValue = barray[i];
360                        if (sValue < 0) {
361                            sValue += 256;
362                        }
363                        sb.append(sValue);
364                    }
365                    break;
366                case 'S':
367                    short[] sarray = (short[]) value;
368                    int iValue = sarray[0];
369                    if (iValue < 0) {
370                        iValue += 65536;
371                    }
372                    sb.append(iValue);
373                    for (int i = 1; i < n; i++) {
374                        sb.append(delimiter);
375                        iValue = sarray[i];
376                        if (iValue < 0) {
377                            iValue += 65536;
378                        }
379                        sb.append(iValue);
380                    }
381                    break;
382                case 'I':
383                    int[] iarray = (int[]) value;
384                    long lValue = iarray[0];
385                    if (lValue < 0) {
386                        lValue += 4294967296L;
387                    }
388                    sb.append(lValue);
389                    for (int i = 1; i < n; i++) {
390                        sb.append(delimiter);
391                        lValue = iarray[i];
392                        if (lValue < 0) {
393                            lValue += 4294967296L;
394                        }
395                        sb.append(lValue);
396                    }
397                    break;
398                case 'J':
399                    long[] larray = (long[]) value;
400                    Long l = (Long) larray[0];
401                    String theValue = Long.toString(l);
402                    if (l < 0) {
403                        l = (l << 1) >>> 1;
404                        BigInteger big1 = new BigInteger("9223372036854775808"); // 2^65
405                        BigInteger big2 = new BigInteger(l.toString());
406                        BigInteger big = big1.add(big2);
407                        theValue = big.toString();
408                    }
409                    sb.append(theValue);
410                    for (int i = 1; i < n; i++) {
411                        sb.append(delimiter);
412                        l = (Long) larray[i];
413                        theValue = Long.toString(l);
414                        if (l < 0) {
415                            l = (l << 1) >>> 1;
416                            BigInteger big1 = new BigInteger("9223372036854775808"); // 2^65
417                            BigInteger big2 = new BigInteger(l.toString());
418                            BigInteger big = big1.add(big2);
419                            theValue = big.toString();
420                        }
421                        sb.append(theValue);
422                    }
423                    break;
424                default:
425                    sb.append(Array.get(value, 0));
426                    for (int i = 1; i < n; i++) {
427                        sb.append(delimiter);
428                        sb.append(Array.get(value, i));
429                    }
430                    break;
431            }
432        }
433        else if(is_enum) {
434            String cname = valClass.getName();
435            char dname = cname.charAt(cname.lastIndexOf("[") + 1);
436            log.trace("toString: is_enum with cname={} dname={}", cname, dname);
437
438            String enum_members = this.getType().getEnumMembers();
439            log.trace("toString: is_enum enum_members={}", enum_members);
440            Map<String,String> map = new HashMap<String,String>();
441            String[] entries = enum_members.split(",");
442            for (String entry : entries) {
443                String[] keyValue = entry.split("=");
444                map.put(keyValue[1],keyValue[0]);
445                log.trace("toString: is_enum value={} name={}", keyValue[1],keyValue[0]);
446            }
447            String theValue = null;
448            switch (dname) {
449                case 'B':
450                    byte[] barray = (byte[]) value;
451                    short sValue = barray[0];
452                    theValue = String.valueOf(sValue);
453                    if (map.containsKey(theValue)) {
454                        sb.append(map.get(theValue));
455                    }
456                    else
457                        sb.append(sValue);
458                    for (int i = 1; i < n; i++) {
459                        sb.append(delimiter);
460                        sValue = barray[i];
461                        theValue = String.valueOf(sValue);
462                        if (map.containsKey(theValue)) {
463                            sb.append(map.get(theValue));
464                        }
465                        else
466                            sb.append(sValue);
467                    }
468                    break;
469                case 'S':
470                    short[] sarray = (short[]) value;
471                    int iValue = sarray[0];
472                    theValue = String.valueOf(iValue);
473                    if (map.containsKey(theValue)) {
474                        sb.append(map.get(theValue));
475                    }
476                    else
477                        sb.append(iValue);
478                    for (int i = 1; i < n; i++) {
479                        sb.append(delimiter);
480                        iValue = sarray[i];
481                        theValue = String.valueOf(iValue);
482                        if (map.containsKey(theValue)) {
483                            sb.append(map.get(theValue));
484                        }
485                        else
486                            sb.append(iValue);
487                    }
488                    break;
489                case 'I':
490                    int[] iarray = (int[]) value;
491                    long lValue = iarray[0];
492                    theValue = String.valueOf(lValue);
493                    if (map.containsKey(theValue)) {
494                        sb.append(map.get(theValue));
495                    }
496                    else
497                        sb.append(lValue);
498                    for (int i = 1; i < n; i++) {
499                        sb.append(delimiter);
500                        lValue = iarray[i];
501                        theValue = String.valueOf(lValue);
502                        if (map.containsKey(theValue)) {
503                            sb.append(map.get(theValue));
504                        }
505                        else
506                            sb.append(lValue);
507                    }
508                    break;
509                case 'J':
510                    long[] larray = (long[]) value;
511                    Long l = (Long) larray[0];
512                    theValue = Long.toString(l);
513                    if (map.containsKey(theValue)) {
514                        sb.append(map.get(theValue));
515                    }
516                    else
517                        sb.append(theValue);
518                    for (int i = 1; i < n; i++) {
519                        sb.append(delimiter);
520                        l = (Long) larray[i];
521                        theValue = Long.toString(l);
522                        if (map.containsKey(theValue)) {
523                            sb.append(map.get(theValue));
524                        }
525                        else
526                            sb.append(theValue);
527                    }
528                    break;
529                default:
530                    sb.append(Array.get(value, 0));
531                    for (int i = 1; i < n; i++) {
532                        sb.append(delimiter);
533                        sb.append(Array.get(value, i));
534                    }
535                    break;
536            }
537        }
538        else {
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        }
545
546        log.trace("toString: finish");
547        return sb.toString();
548    }
549}