jdk/src/java.desktop/share/classes/javax/imageio/plugins/tiff/TIFFField.java
author bpb
Fri, 29 Apr 2016 14:50:50 -0700
changeset 38393 2e49e69f3d64
parent 36448 a07e108d5722
child 38397 8ff4232c93c2
permissions -rw-r--r--
8149810: TIFFField#getAsLong throws ClassCastException when data is type TIFFTag.TIFF_DOUBLE or TIFFTag.FLOAT Summary: Expand the getAsLong() specification and handle the TIFF_DOUBLE and TIFF_FLOAT cases. Reviewed-by: prr

/*
 * Copyright (c) 2005, 2016, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
package javax.imageio.plugins.tiff;

import java.util.StringTokenizer;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import com.sun.imageio.plugins.tiff.TIFFFieldNode;
import com.sun.imageio.plugins.tiff.TIFFIFD;

/**
 * A class representing a field in a TIFF 6.0 Image File Directory.
 *
 * <p> A field in a TIFF Image File Directory (IFD) is defined as a
 * tag number accompanied by a sequence of values of identical data type.
 * TIFF 6.0 defines 12 data types; a 13th type {@code IFD} is
 * defined in TIFF Tech Note 1 of TIFF Specification Supplement 1. These
 * TIFF data types are referred to by Java constants and mapped internally
 * onto Java language data types and type names as follows:
 *
 * <br>
 * <br>
 * <table border="1">
 * <caption>TIFF Data Type to Java Data Type Mapping</caption>
 *
 * <tr>
 * <th>
 * <b>TIFF Data Type</b>
 * </th>
 * <th>
 * <b>Java Constant</b>
 * </th>
 * <th>
 * <b>Java Data Type</b>
 * </th>
 * <th>
 * <b>Java Type Name</b>
 * </th>
 * </tr>
 *
 * <tr>
 * <td>
 * <tt>BYTE</tt>
 * </td>
 * <td>
 * {@link TIFFTag#TIFF_BYTE}
 * </td>
 * <td>
 * {@code byte}
 * </td>
 * <td>
 * {@code "Byte"}
 * </td>
 * </tr>
 *
 * <tr>
 * <td>
 * <tt>ASCII</tt>
 * </td>
 * <td>
 * {@link TIFFTag#TIFF_ASCII}
 * </td>
 * <td>
 * {@code String}
 * </td>
 * <td>
 * {@code "Ascii"}
 * </td>
 * </tr>
 *
 * <tr>
 * <td>
 * <tt>SHORT</tt>
 * </td>
 * <td>
 * {@link TIFFTag#TIFF_SHORT}
 * </td>
 * <td>
 * {@code char}
 * </td>
 * <td>
 * {@code "Short"}
 * </td>
 * </tr>
 *
 * <tr>
 * <td>
 * <tt>LONG</tt>
 * </td>
 * <td>
 * {@link TIFFTag#TIFF_LONG}
 * </td>
 * <td>
 * {@code long}
 * </td>
 * <td>
 * {@code "Long"}
 * </td>
 * </tr>
 *
 * <tr>
 * <td>
 * <tt>RATIONAL</tt>
 * </td>
 * <td>
 * {@link TIFFTag#TIFF_RATIONAL}
 * </td>
 * <td>
 * {@code long[2]} {numerator, denominator}
 * </td>
 * <td>
 * {@code "Rational"}
 * </td>
 * </tr>
 *
 * <tr>
 * <td>
 * <tt>SBYTE</tt>
 * </td>
 * <td>
 * {@link TIFFTag#TIFF_SBYTE}
 * </td>
 * <td>
 * {@code byte}
 * </td>
 * <td>
 * {@code "SByte"}
 * </td>
 * </tr>
 *
 * <tr>
 * <td>
 * <tt>UNDEFINED</tt>
 * </td>
 * <td>
 * {@link TIFFTag#TIFF_UNDEFINED}
 * </td>
 * <td>
 * {@code byte}
 * </td>
 * <td>
 * {@code "Undefined"}
 * </td>
 * </tr>
 *
 * <tr>
 * <td>
 * <tt>SSHORT</tt>
 * </td>
 * <td>
 * {@link TIFFTag#TIFF_SSHORT}
 * </td>
 * <td>
 * {@code short}
 * </td>
 * <td>
 * {@code "SShort"}
 * </td>
 * </tr>
 *
 * <tr>
 * <td>
 * <tt>SLONG</tt>
 * </td>
 * <td>
 * {@link TIFFTag#TIFF_SLONG}
 * </td>
 * <td>
 * {@code int}
 * </td>
 * <td>
 * {@code "SLong"}
 * </td>
 * </tr>
 *
 * <tr>
 * <td>
 * <tt>SRATIONAL</tt>
 * </td>
 * <td>
 * {@link TIFFTag#TIFF_SRATIONAL}
 * </td>
 * <td>
 * {@code int[2]} {numerator, denominator}
 * </td>
 * <td>
 * {@code "SRational"}
 * </td>
 * </tr>
 *
 * <tr>
 * <td>
 * <tt>FLOAT</tt>
 * </td>
 * <td>
 * {@link TIFFTag#TIFF_FLOAT}
 * </td>
 * <td>
 * {@code float}
 * </td>
 * <td>
 * {@code "Float"}
 * </td>
 * </tr>
 *
 * <tr>
 * <td>
 * <tt>DOUBLE</tt>
 * </td>
 * <td>
 * {@link TIFFTag#TIFF_DOUBLE}
 * </td>
 * <td>
 * {@code double}
 * </td>
 * <td>
 * {@code "Double"}
 * </td>
 * </tr>
 *
 * <tr>
 * <td>
 * <tt>IFD</tt>
 * </td>
 * <td>
 * {@link TIFFTag#TIFF_IFD_POINTER}
 * </td>
 * <td>
 * {@code long}
 * </td>
 * <td>
 * {@code "IFDPointer"}
 * </td>
 * </tr>
 *
 * </table>
 *
 * @since 9
 * @see   TIFFDirectory
 * @see   TIFFTag
 */
public class TIFFField implements Cloneable {

    private static final String[] typeNames = {
        null,
        "Byte", "Ascii", "Short", "Long", "Rational",
        "SByte", "Undefined", "SShort", "SLong", "SRational",
        "Float", "Double", "IFDPointer"
    };

    private static final boolean[] isIntegral = {
        false,
        true, false, true, true, false,
        true, true, true, true, false,
        false, false, false
    };

    /** The tag. */
    private TIFFTag tag;

    /** The tag number. */
    private int tagNumber;

    /** The tag type. */
    private int type;

    /** The number of data items present in the field. */
    private int count;

    /** The field data. */
    private Object data;

    /** The IFD contents if available. This will usually be a TIFFIFD. */
    private TIFFDirectory dir;

    /** The default constructor. */
    private TIFFField() {}

    private static String getAttribute(Node node, String attrName) {
        NamedNodeMap attrs = node.getAttributes();
        return attrs.getNamedItem(attrName).getNodeValue();
    }

    private static void initData(Node node,
                                 int[] otype, int[] ocount, Object[] odata) {
        int type;
        int count;
        Object data = null;

        String typeName = node.getNodeName();
        typeName = typeName.substring(4);
        typeName = typeName.substring(0, typeName.length() - 1);
        type = TIFFField.getTypeByName(typeName);
        if (type == -1) {
            throw new IllegalArgumentException("typeName = " + typeName);
        }

        Node child = node.getFirstChild();

        count = 0;
        while (child != null) {
            String childTypeName = child.getNodeName().substring(4);
            if (!typeName.equals(childTypeName)) {
                // warning
            }

            ++count;
            child = child.getNextSibling();
        }

        if (count > 0) {
            data = createArrayForType(type, count);
            child = node.getFirstChild();
            int idx = 0;
            while (child != null) {
                String value = getAttribute(child, "value");

                String numerator, denominator;
                int slashPos;

                switch (type) {
                case TIFFTag.TIFF_ASCII:
                    ((String[])data)[idx] = value;
                    break;
                case TIFFTag.TIFF_BYTE:
                case TIFFTag.TIFF_SBYTE:
                    ((byte[])data)[idx] =
                        (byte)Integer.parseInt(value);
                    break;
                case TIFFTag.TIFF_SHORT:
                    ((char[])data)[idx] =
                        (char)Integer.parseInt(value);
                    break;
                case TIFFTag.TIFF_SSHORT:
                    ((short[])data)[idx] =
                        (short)Integer.parseInt(value);
                    break;
                case TIFFTag.TIFF_SLONG:
                    ((int[])data)[idx] =
                        Integer.parseInt(value);
                    break;
                case TIFFTag.TIFF_LONG:
                case TIFFTag.TIFF_IFD_POINTER:
                    ((long[])data)[idx] =
                        Long.parseLong(value);
                    break;
                case TIFFTag.TIFF_FLOAT:
                    ((float[])data)[idx] =
                        Float.parseFloat(value);
                    break;
                case TIFFTag.TIFF_DOUBLE:
                    ((double[])data)[idx] =
                        Double.parseDouble(value);
                    break;
                case TIFFTag.TIFF_SRATIONAL:
                    slashPos = value.indexOf("/");
                    numerator = value.substring(0, slashPos);
                    denominator = value.substring(slashPos + 1);

                    ((int[][])data)[idx] = new int[2];
                    ((int[][])data)[idx][0] =
                        Integer.parseInt(numerator);
                    ((int[][])data)[idx][1] =
                        Integer.parseInt(denominator);
                    break;
                case TIFFTag.TIFF_RATIONAL:
                    slashPos = value.indexOf("/");
                    numerator = value.substring(0, slashPos);
                    denominator = value.substring(slashPos + 1);

                    ((long[][])data)[idx] = new long[2];
                    ((long[][])data)[idx][0] =
                        Long.parseLong(numerator);
                    ((long[][])data)[idx][1] =
                        Long.parseLong(denominator);
                    break;
                default:
                    // error
                }

                idx++;
                child = child.getNextSibling();
            }
        }

        otype[0] = type;
        ocount[0] = count;
        odata[0] = data;
    }

    /**
     * Creates a {@code TIFFField} from a TIFF native image
     * metadata node. If the value of the <tt>"tagNumber"</tt> attribute
     * of the node is not found in {@code tagSet} then a new
     * {@code TIFFTag} with name {@code TIFFTag.UNKNOWN_TAG_NAME}
     * will be created and assigned to the field.
     *
     * @param tagSet The {@code TIFFTagSet} to which the
     * {@code TIFFTag} of the field belongs.
     * @param node A native TIFF image metadata {@code TIFFField} node.
     * @throws NullPointerException if {@code node} is
     * {@code null}.
     * @throws IllegalArgumentException if the name of the node is not
     * {@code "TIFFField"}.
     * @return A new {@code TIFFField}.
     */
    public static TIFFField createFromMetadataNode(TIFFTagSet tagSet,
                                                   Node node) {
        if (node == null) {
            throw new NullPointerException("node == null!");
        }
        String name = node.getNodeName();
        if (!name.equals("TIFFField")) {
            throw new IllegalArgumentException("!name.equals(\"TIFFField\")");
        }

        int tagNumber = Integer.parseInt(getAttribute(node, "number"));
        TIFFTag tag = null;
        if (tagSet != null) {
            tag = tagSet.getTag(tagNumber);
        }

        int type = TIFFTag.TIFF_UNDEFINED;
        int count = 0;
        Object data = null;

        Node child = node.getFirstChild();
        if (child != null) {
            String typeName = child.getNodeName();
            if (typeName.equals("TIFFUndefined")) {
                String values = getAttribute(child, "value");
                StringTokenizer st = new StringTokenizer(values, ",");
                count = st.countTokens();

                byte[] bdata = new byte[count];
                for (int i = 0; i < count; i++) {
                    bdata[i] = (byte)Integer.parseInt(st.nextToken());
                }

                type = TIFFTag.TIFF_UNDEFINED;
                data = bdata;
            } else {
                int[] otype = new int[1];
                int[] ocount = new int[1];
                Object[] odata = new Object[1];

                initData(node.getFirstChild(), otype, ocount, odata);
                type = otype[0];
                count = ocount[0];
                data = odata[0];
            }
        } else if (tag != null) {
            int t = TIFFTag.MAX_DATATYPE;
            while(t >= TIFFTag.MIN_DATATYPE && !tag.isDataTypeOK(t)) {
                t--;
            }
            type = t;
        }

        if (tag == null) {
            tag = new TIFFTag(TIFFTag.UNKNOWN_TAG_NAME, tagNumber, 1 << type);
        }

        return new TIFFField(tag, type, count, data);
    }

    /**
     * Constructs a {@code TIFFField} with arbitrary data. The
     * {@code type} parameter must be a value for which
     * {@link TIFFTag#isDataTypeOK tag.isDataTypeOK()}
     * returns {@code true}. The {@code data} parameter must
     * be an array of a Java type appropriate for the type of the TIFF
     * field.
     *
     * <p>Note that the value (data) of the {@code TIFFField}
     * will always be the actual field value regardless of the number of
     * bytes required for that value. This is the case despite the fact
     * that the TIFF <i>IFD Entry</i> corresponding to the field may
     * actually contain the offset to the value of the field rather than
     * the value itself (the latter occurring if and only if the
     * value fits into 4 bytes). In other words, the value of the
     * field will already have been read from the TIFF stream. (An exception
     * to this case may occur when the field represents the contents of a
     * non-baseline IFD. In that case the data will be a {@code long[]}
     * containing the offset to the IFD and the {@code TIFFDirectory}
     * returned by {@link #getDirectory()} will be its contents.)
     *
     * @param tag The tag to associated with this field.
     * @param type One of the {@code TIFFTag.TIFF_*} constants
     * indicating the data type of the field as written to the TIFF stream.
     * @param count The number of data values.
     * @param data The actual data content of the field.
     *
     * @throws NullPointerException if {@code tag&nbsp;==&nbsp;null}.
     * @throws IllegalArgumentException if {@code type} is not
     * one of the {@code TIFFTag.TIFF_*} data type constants.
     * @throws IllegalArgumentException if {@code type} is an unacceptable
     * data type for the supplied {@code TIFFTag}.
     * @throws IllegalArgumentException if {@code count&nbsp;&lt;&nbsp;0}.
     * @throws IllegalArgumentException if {@code count&nbsp;&lt;&nbsp;1}
     * and {@code type} is {@code TIFF_RATIONAL} or
     * {@code TIFF_SRATIONAL}.
     * @throws IllegalArgumentException if {@code count&nbsp;&ne;&nbsp;1}
     * and {@code type} is {@code TIFF_IFD_POINTER}.
     * @throws NullPointerException if {@code data&nbsp;==&nbsp;null}.
     * @throws IllegalArgumentException if {@code data} is an instance of
     * a class incompatible with the specified type.
     * @throws IllegalArgumentException if the size of the data array is wrong.
     */
    public TIFFField(TIFFTag tag, int type, int count, Object data) {
        if(tag == null) {
            throw new NullPointerException("tag == null!");
        } else if(type < TIFFTag.MIN_DATATYPE || type > TIFFTag.MAX_DATATYPE) {
            throw new IllegalArgumentException("Unknown data type "+type);
        } else if(!tag.isDataTypeOK(type)) {
            throw new IllegalArgumentException("Illegal data type " + type
                + " for " + tag.getName() + " tag");
        } else if(count < 0) {
            throw new IllegalArgumentException("count < 0!");
        } else if((type == TIFFTag.TIFF_RATIONAL
                   || type == TIFFTag.TIFF_SRATIONAL)
                  && count < 1) {
            throw new IllegalArgumentException
                ("Type is TIFF_RATIONAL or TIFF_SRATIONAL and count < 1");
        } else if (type == TIFFTag.TIFF_IFD_POINTER && count != 1) {
            throw new IllegalArgumentException
                ("Type is TIFF_IFD_POINTER count != 1");
        } else if(data == null) {
            throw new NullPointerException("data == null!");
        }

        boolean isDataArrayCorrect = false;

        switch (type) {
        case TIFFTag.TIFF_BYTE:
        case TIFFTag.TIFF_SBYTE:
        case TIFFTag.TIFF_UNDEFINED:
            isDataArrayCorrect = data instanceof byte[]
                && ((byte[])data).length == count;
            break;
        case TIFFTag.TIFF_ASCII:
            isDataArrayCorrect = data instanceof String[]
                && ((String[])data).length == count;
            break;
        case TIFFTag.TIFF_SHORT:
            isDataArrayCorrect = data instanceof char[]
                && ((char[])data).length == count;
            break;
        case TIFFTag.TIFF_LONG:
            isDataArrayCorrect = data instanceof long[]
                && ((long[])data).length == count;
            break;
        case TIFFTag.TIFF_IFD_POINTER:
            isDataArrayCorrect = data instanceof long[]
                && ((long[])data).length == 1;
            break;
        case TIFFTag.TIFF_RATIONAL:
            isDataArrayCorrect = data instanceof long[][]
                && ((long[][])data).length == count
                && ((long[][])data)[0].length == 2;
            break;
        case TIFFTag.TIFF_SSHORT:
            isDataArrayCorrect = data instanceof short[]
                && ((short[])data).length == count;
            break;
        case TIFFTag.TIFF_SLONG:
            isDataArrayCorrect = data instanceof int[]
                && ((int[])data).length == count;
            break;
        case TIFFTag.TIFF_SRATIONAL:
            isDataArrayCorrect = data instanceof int[][]
                && ((int[][])data).length == count
                && ((int[][])data)[0].length == 2;
            break;
        case TIFFTag.TIFF_FLOAT:
            isDataArrayCorrect = data instanceof float[]
                && ((float[])data).length == count;
            break;
        case TIFFTag.TIFF_DOUBLE:
            isDataArrayCorrect = data instanceof double[]
                && ((double[])data).length == count;
            break;
        default:
            throw new IllegalArgumentException("Unknown data type "+type);
        }

        if (!isDataArrayCorrect) {
            throw new IllegalArgumentException
                ("Illegal class or length for data array");
        }

        this.tag = tag;
        this.tagNumber = tag.getNumber();
        this.type = type;
        this.count = count;
        this.data = data;
    }

    /**
     * Constructs a data array using {@link #createArrayForType
     * createArrayForType()} and invokes
     * {@link #TIFFField(TIFFTag,int,int,Object)} with the supplied
     * parameters and the created array.
     *
     * @param tag The tag to associated with this field.
     * @param type One of the {@code TIFFTag.TIFF_*} constants
     * indicating the data type of the field as written to the TIFF stream.
     * @param count The number of data values.
     * @throws NullPointerException if {@code tag&nbsp;==&nbsp;null}.
     * @throws IllegalArgumentException if {@code type} is not
     * one of the {@code TIFFTag.TIFF_*} data type constants.
     * @throws IllegalArgumentException if {@code type} is an unacceptable
     * data type for the supplied {@code TIFFTag}.
     * @throws IllegalArgumentException if {@code count&nbsp;&lt;&nbsp;0}.
     * @see #TIFFField(TIFFTag,int,int,Object)
     */
    public TIFFField(TIFFTag tag, int type, int count) {
        this(tag, type, count, createArrayForType(type, count));
    }

    /**
     * Constructs a {@code TIFFField} with a single non-negative integral
     * value.
     * The field will have type
     * {@link TIFFTag#TIFF_SHORT  TIFF_SHORT} if
     * {@code val&nbsp;&lt;&nbsp;65536} and type
     * {@link TIFFTag#TIFF_LONG TIFF_LONG} otherwise.  The count
     * of the field will be unity.
     *
     * @param tag The tag to associate with this field.
     * @param value The value to associate with this field.
     * @throws NullPointerException if {@code tag&nbsp;==&nbsp;null}.
     * @throws IllegalArgumentException if the derived type is unacceptable
     * for the supplied {@code TIFFTag}.
     * @throws IllegalArgumentException if {@code value&nbsp;&lt;&nbsp;0}.
     */
    public TIFFField(TIFFTag tag, int value) {
        if(tag == null) {
            throw new NullPointerException("tag == null!");
        }
        if (value < 0) {
            throw new IllegalArgumentException("value < 0!");
        }

        this.tag = tag;
        this.tagNumber = tag.getNumber();
        this.count = 1;

        if (value < 65536) {
            if (!tag.isDataTypeOK(TIFFTag.TIFF_SHORT)) {
                throw new IllegalArgumentException("Illegal data type "
                    + TIFFTag.TIFF_SHORT + " for " + tag.getName() + " tag");
            }
            this.type = TIFFTag.TIFF_SHORT;
            char[] cdata = new char[1];
            cdata[0] = (char)value;
            this.data = cdata;
        } else {
            if (!tag.isDataTypeOK(TIFFTag.TIFF_LONG)) {
                throw new IllegalArgumentException("Illegal data type "
                    + TIFFTag.TIFF_LONG + " for " + tag.getName() + " tag");
            }
            this.type = TIFFTag.TIFF_LONG;
            long[] ldata = new long[1];
            ldata[0] = value;
            this.data = ldata;
        }
    }

    /**
     * Constructs a {@code TIFFField} with an IFD offset and contents.
     * The offset will be stored as the data of this field as
     * {@code long[] {offset}}. The directory will not be cloned. The count
     * of the field will be unity.
     *
     * @param tag The tag to associated with this field.
     * @param type One of the constants {@code TIFFTag.TIFF_LONG} or
     * {@code TIFFTag.TIFF_IFD_POINTER}.
     * @param offset The IFD offset.
     * @param dir The directory.
     *
     * @throws NullPointerException if {@code tag&nbsp;==&nbsp;null}.
     * @throws IllegalArgumentException if {@code type} is neither
     * {@code TIFFTag.TIFF_LONG} nor {@code TIFFTag.TIFF_IFD_POINTER}.
     * @throws IllegalArgumentException if {@code type} is an unacceptable
     * data type for the supplied {@code TIFFTag}.
     * @throws IllegalArgumentException if {@code offset} is non-positive.
     * @throws NullPointerException if {@code dir&nbsp;==&nbsp;null}.
     *
     * @see #TIFFField(TIFFTag,int,int,Object)
     */
    public TIFFField(TIFFTag tag, int type, long offset, TIFFDirectory dir) {
        this(tag, type, 1, new long[] {offset});
        if (type != TIFFTag.TIFF_LONG && type != TIFFTag.TIFF_IFD_POINTER) {
            throw new IllegalArgumentException("type " + type
                + " is neither TIFFTag.TIFF_LONG nor TIFFTag.TIFF_IFD_POINTER");
        } else if (offset <= 0) {
            throw new IllegalArgumentException("offset " + offset
                + " is non-positive");
        } else if (dir == null) {
            throw new NullPointerException("dir == null");
        }
        this.dir = dir;
    }

    /**
     * Retrieves the tag associated with this field.
     *
     * @return The associated {@code TIFFTag}.
     */
    public TIFFTag getTag() {
        return tag;
    }

    /**
     * Retrieves the tag number in the range {@code [0,&nbsp;65535]}.
     *
     * @return The tag number.
     */
    public int getTagNumber() {
        return tagNumber;
    }

    /**
     * Returns the type of the data stored in the field.  For a TIFF 6.0
     * stream, the value will equal one of the {@code TIFFTag.TIFF_*}
     * constants. For future revisions of TIFF, higher values are possible.
     *
     * @return The data type of the field value.
     */
    public int getType() {
        return type;
    }

    /**
     * Returns the name of the supplied data type constant.
     *
     * @param dataType One of the {@code TIFFTag.TIFF_*} constants
     * indicating the data type of the field as written to the TIFF stream.
     * @return The type name corresponding to the supplied type constant.
     * @throws IllegalArgumentException if {@code dataType} is not
     * one of the {@code TIFFTag.TIFF_*} data type constants.
     */
    public static String getTypeName(int dataType) {
        if (dataType < TIFFTag.MIN_DATATYPE ||
            dataType > TIFFTag.MAX_DATATYPE) {
            throw new IllegalArgumentException("Unknown data type "+dataType);
        }

        return typeNames[dataType];
    }

    /**
     * Returns the data type constant corresponding to the supplied data
     * type name. If the name is unknown {@code -1} will be returned.
     *
     * @param typeName The type name.
     * @return One of the {@code TIFFTag.TIFF_*} constants or
     * {@code -1} if the name is not recognized.
     */
    public static int getTypeByName(String typeName) {
        for (int i = TIFFTag.MIN_DATATYPE; i <= TIFFTag.MAX_DATATYPE; i++) {
            if (typeName.equals(typeNames[i])) {
                return i;
            }
        }

        return -1;
    }

    /**
     * Creates an array appropriate for the indicated data type.
     *
     * @param dataType One of the {@code TIFFTag.TIFF_*} data type
     * constants.
     * @param count The number of values in the array.
     * @return An array appropriate for the specified data type.
     *
     * @throws IllegalArgumentException if {@code dataType} is not
     * one of the {@code TIFFTag.TIFF_*} data type constants.
     * @throws IllegalArgumentException if {@code count&nbsp;&lt;&nbsp;0}.
     */
    public static Object createArrayForType(int dataType, int count) {
        if(count < 0) {
            throw new IllegalArgumentException("count < 0!");
        }
        switch (dataType) {
        case TIFFTag.TIFF_BYTE:
        case TIFFTag.TIFF_SBYTE:
        case TIFFTag.TIFF_UNDEFINED:
            return new byte[count];
        case TIFFTag.TIFF_ASCII:
            return new String[count];
        case TIFFTag.TIFF_SHORT:
            return new char[count];
        case TIFFTag.TIFF_LONG:
        case TIFFTag.TIFF_IFD_POINTER:
            return new long[count];
        case TIFFTag.TIFF_RATIONAL:
            return new long[count][2];
        case TIFFTag.TIFF_SSHORT:
            return new short[count];
        case TIFFTag.TIFF_SLONG:
            return new int[count];
        case TIFFTag.TIFF_SRATIONAL:
            return new int[count][2];
        case TIFFTag.TIFF_FLOAT:
            return new float[count];
        case TIFFTag.TIFF_DOUBLE:
            return new double[count];
        default:
            throw new IllegalArgumentException("Unknown data type "+dataType);
        }
    }

    /**
     * Returns the {@code TIFFField} as a node named either
     * <tt>"TIFFField"</tt> or <tt>"TIFFIFD"</tt> as described in the
     * TIFF native image metadata specification. The node will be named
     * <tt>"TIFFIFD"</tt> if and only if the field's data object is an
     * instance of {@link TIFFDirectory} or equivalently
     * {@link TIFFTag#isIFDPointer getTag.isIFDPointer()} returns
     * {@code true}.
     *
     * @return a {@code Node} named <tt>"TIFFField"</tt> or
     * <tt>"TIFFIFD"</tt>.
     */
    public Node getAsNativeNode() {
        return new TIFFFieldNode(this);
    }

    /**
     * Indicates whether the value associated with the field is of
     * integral data type.
     *
     * @return Whether the field type is integral.
     */
    public boolean isIntegral() {
        return isIntegral[type];
    }

    /**
     * Returns the number of data items present in the field.  For
     * {@code TIFFTag.TIFF_ASCII} fields, the value returned is the
     * number of {@code String}s, not the total length of the
     * data as in the file representation.
     *
     * @return The number of data items present in the field.
     */
    public int getCount() {
        return count;
    }

    /**
     * Returns a reference to the data object associated with the field.
     *
     * @return The data object of the field.
     */
    public Object getData() {
        return data;
    }

    /**
     * Returns the data as an uninterpreted array of
     * {@code byte}s.  The type of the field must be one of
     * {@code TIFFTag.TIFF_BYTE}, {@code TIFF_SBYTE}, or
     * {@code TIFF_UNDEFINED}.
     *
     * <p> For data in {@code TIFFTag.TIFF_BYTE} format, the application
     * must take care when promoting the data to longer integral types
     * to avoid sign extension.
     *
     * @throws ClassCastException if the field is not of type
     * {@code TIFF_BYTE}, {@code TIFF_SBYTE}, or
     * {@code TIFF_UNDEFINED}.
     * @return The data as an uninterpreted array of bytes.
     */
    public byte[] getAsBytes() {
        return (byte[])data;
    }

    /**
     * Returns {@code TIFFTag.TIFF_SHORT} data as an array of
     * {@code char}s (unsigned 16-bit integers).
     *
     * @throws ClassCastException if the field is not of type
     * {@code TIFF_SHORT}.
     * @return The data as an array of {@code char}s.
     */
    public char[] getAsChars() {
        return (char[])data;
    }

    /**
     * Returns {@code TIFFTag.TIFF_SSHORT} data as an array of
     * {@code short}s (signed 16-bit integers).
     *
     * @throws ClassCastException if the field is not of type
     * {@code TIFF_SSHORT}.
     * @return The data as an array of {@code short}s.
     */
    public short[] getAsShorts() {
        return (short[])data;
    }

    /**
     * Returns {@code TIFFTag.TIFF_SLONG} data as an array of
     * {@code int}s (signed 32-bit integers).
     *
     * @throws ClassCastException if the field is not of type
     * {@code TIFF_SHORT}, {@code TIFF_SSHORT}, or
     * {@code TIFF_SLONG}.
     * @return The data as an array of {@code int}s.
     */
    public int[] getAsInts() {
        if (data instanceof int[]) {
            return (int[])data;
        } else if (data instanceof char[]){
            char[] cdata = (char[])data;
            int[] idata = new int[cdata.length];
            for (int i = 0; i < cdata.length; i++) {
                idata[i] = cdata[i] & 0xffff;
            }
            return idata;
        } else if (data instanceof short[]){
            short[] sdata = (short[])data;
            int[] idata = new int[sdata.length];
            for (int i = 0; i < sdata.length; i++) {
                idata[i] = (int)sdata[i];
            }
            return idata;
        } else {
            throw new ClassCastException("Data not char[], short[], or int[]!");
        }
    }

    /**
     * Returns {@code TIFFTag.TIFF_LONG} or
     * {@code TIFF_IFD_POINTER} data as an array of
     * {@code long}s (signed 64-bit integers).
     *
     * @throws ClassCastException if the field is not of type
     * {@code TIFF_LONG} or {@code TIFF_IFD_POINTER}.
     * @return The data as an array of {@code long}s.
     */
    public long[] getAsLongs() {
        return (long[])data;
    }

    /**
     * Returns {@code TIFFTag.TIFF_FLOAT} data as an array of
     * {@code float}s (32-bit floating-point values).
     *
     * @throws ClassCastException if the field is not of type
     * {@code TIFF_FLOAT}.
     * @return The data as an array of {@code float}s.
     */
    public float[] getAsFloats() {
        return (float[])data;
    }

    /**
     * Returns {@code TIFFTag.TIFF_DOUBLE} data as an array of
     * {@code double}s (64-bit floating-point values).
     *
     * @throws ClassCastException if the field is not of type
     * {@code TIFF_DOUBLE}.
     * @return The data as an array of {@code double}s.
     */
    public double[] getAsDoubles() {
        return (double[])data;
    }

    /**
     * Returns {@code TIFFTag.TIFF_SRATIONAL} data as an array of
     * 2-element arrays of {@code int}s.
     *
     * @throws ClassCastException if the field is not of type
     * {@code TIFF_SRATIONAL}.
     * @return The data as an array of signed rationals.
     */
    public int[][] getAsSRationals() {
        return (int[][])data;
    }

    /**
     * Returns {@code TIFFTag.TIFF_RATIONAL} data as an array of
     * 2-element arrays of {@code long}s.
     *
     * @throws ClassCastException if the field is not of type
     * {@code TIFF_RATIONAL}.
     * @return The data as an array of unsigned rationals.
     */
    public long[][] getAsRationals() {
        return (long[][])data;
    }

    /**
     * Returns data in any format as an {@code int}.
     *
     * <p> {@code TIFFTag.TIFF_BYTE} values are treated as unsigned; that
     * is, no sign extension will take place and the returned value
     * will be in the range [0, 255].  {@code TIFF_SBYTE} data
     * will be returned in the range [-128, 127].
     *
     * <p> A {@code TIFF_UNDEFINED} value is treated as though
     * it were a {@code TIFF_BYTE}.
     *
     * <p> Data in {@code TIFF_SLONG}, {@code TIFF_LONG},
     * {@code TIFF_FLOAT}, {@code TIFF_DOUBLE} or
     * {@code TIFF_IFD_POINTER} format are simply cast to
     * {@code int} and may suffer from truncation.
     *
     * <p> Data in {@code TIFF_SRATIONAL} or
     * {@code TIFF_RATIONAL} format are evaluated by dividing the
     * numerator into the denominator using double-precision
     * arithmetic and then casting to {@code int}.  Loss of
     * precision and truncation may occur.
     *
     * <p> Data in {@code TIFF_ASCII} format will be parsed as by
     * the {@code Double.parseDouble} method, with the result
     * case to {@code int}.
     *
     * @param index The index of the data.
     * @return The data at the given index as an {@code int}.
     */
    public int getAsInt(int index) {
        switch (type) {
        case TIFFTag.TIFF_BYTE:
        case TIFFTag.TIFF_UNDEFINED:
            return ((byte[])data)[index] & 0xff;
        case TIFFTag.TIFF_SBYTE:
            return ((byte[])data)[index];
        case TIFFTag.TIFF_SHORT:
            return ((char[])data)[index] & 0xffff;
        case TIFFTag.TIFF_SSHORT:
            return ((short[])data)[index];
        case TIFFTag.TIFF_SLONG:
            return ((int[])data)[index];
        case TIFFTag.TIFF_LONG:
        case TIFFTag.TIFF_IFD_POINTER:
            return (int)((long[])data)[index];
        case TIFFTag.TIFF_FLOAT:
            return (int)((float[])data)[index];
        case TIFFTag.TIFF_DOUBLE:
            return (int)((double[])data)[index];
        case TIFFTag.TIFF_SRATIONAL:
            int[] ivalue = getAsSRational(index);
            return (int)((double)ivalue[0]/ivalue[1]);
        case TIFFTag.TIFF_RATIONAL:
            long[] lvalue = getAsRational(index);
            return (int)((double)lvalue[0]/lvalue[1]);
        case TIFFTag.TIFF_ASCII:
             String s = ((String[])data)[index];
             return (int)Double.parseDouble(s);
        default:
            throw new ClassCastException(); // should never happen
        }
    }

    /**
     * Returns data in any format as a {@code long}.
     *
     * <p> {@code TIFFTag.TIFF_BYTE} and {@code TIFF_UNDEFINED} data
     * are treated as unsigned; that is, no sign extension will take
     * place and the returned value will be in the range [0, 255].
     * {@code TIFF_SBYTE} data will be returned in the range
     * [-128, 127].
     *
     * <p> Data in {@code TIFF_FLOAT} and {@code TIFF_DOUBLE} are
     * simply cast to {@code long} and may suffer from truncation.
     *
     * <p> Data in {@code TIFF_SRATIONAL} or
     * {@code TIFF_RATIONAL} format are evaluated by dividing the
     * numerator into the denominator using double-precision
     * arithmetic and then casting to {@code long}.  Loss of
     * precision and truncation may occur.
     *
     * <p> Data in {@code TIFF_ASCII} format will be parsed as by
     * the {@code Double.parseDouble} method, with the result
     * cast to {@code long}.
     *
     * @param index The index of the data.
     * @return The data at the given index as a {@code long}.
     */
    public long getAsLong(int index) {
        switch (type) {
        case TIFFTag.TIFF_BYTE:
        case TIFFTag.TIFF_UNDEFINED:
            return ((byte[])data)[index] & 0xff;
        case TIFFTag.TIFF_SBYTE:
            return ((byte[])data)[index];
        case TIFFTag.TIFF_SHORT:
            return ((char[])data)[index] & 0xffff;
        case TIFFTag.TIFF_SSHORT:
            return ((short[])data)[index];
        case TIFFTag.TIFF_SLONG:
            return ((int[])data)[index];
        case TIFFTag.TIFF_LONG:
        case TIFFTag.TIFF_IFD_POINTER:
            return ((long[])data)[index];
        case TIFFTag.TIFF_FLOAT:
            return (long)((float[])data)[index];
        case TIFFTag.TIFF_DOUBLE:
            return (long)((double[])data)[index];
        case TIFFTag.TIFF_SRATIONAL:
            int[] ivalue = getAsSRational(index);
            return (long)((double)ivalue[0]/ivalue[1]);
        case TIFFTag.TIFF_RATIONAL:
            long[] lvalue = getAsRational(index);
            return (long)((double)lvalue[0]/lvalue[1]);
        case TIFFTag.TIFF_ASCII:
             String s = ((String[])data)[index];
             return (long)Double.parseDouble(s);
        default:
            throw new ClassCastException(); // should never happen
        }
    }

    /**
     * Returns data in any format as a {@code float}.
     *
     * <p> {@code TIFFTag.TIFF_BYTE} and {@code TIFF_UNDEFINED} data
     * are treated as unsigned; that is, no sign extension will take
     * place and the returned value will be in the range [0, 255].
     * {@code TIFF_SBYTE} data will be returned in the range
     * [-128, 127].
     *
     * <p> Data in {@code TIFF_SLONG}, {@code TIFF_LONG},
     * {@code TIFF_DOUBLE}, or {@code TIFF_IFD_POINTER} format are
     * simply cast to {@code float} and may suffer from
     * truncation.
     *
     * <p> Data in {@code TIFF_SRATIONAL} or
     * {@code TIFF_RATIONAL} format are evaluated by dividing the
     * numerator into the denominator using double-precision
     * arithmetic and then casting to {@code float}.
     *
     * <p> Data in {@code TIFF_ASCII} format will be parsed as by
     * the {@code Double.parseDouble} method, with the result
     * cast to {@code float}.
     *
     * @param index The index of the data.
     * @return The data at the given index as a {@code float}.
     */
    public float getAsFloat(int index) {
        switch (type) {
        case TIFFTag.TIFF_BYTE:
        case TIFFTag.TIFF_UNDEFINED:
            return ((byte[])data)[index] & 0xff;
        case TIFFTag.TIFF_SBYTE:
            return ((byte[])data)[index];
        case TIFFTag.TIFF_SHORT:
            return ((char[])data)[index] & 0xffff;
        case TIFFTag.TIFF_SSHORT:
            return ((short[])data)[index];
        case TIFFTag.TIFF_SLONG:
            return ((int[])data)[index];
        case TIFFTag.TIFF_LONG:
        case TIFFTag.TIFF_IFD_POINTER:
            return ((long[])data)[index];
        case TIFFTag.TIFF_FLOAT:
            return ((float[])data)[index];
        case TIFFTag.TIFF_DOUBLE:
            return (float)((double[])data)[index];
        case TIFFTag.TIFF_SRATIONAL:
            int[] ivalue = getAsSRational(index);
            return (float)((double)ivalue[0]/ivalue[1]);
        case TIFFTag.TIFF_RATIONAL:
            long[] lvalue = getAsRational(index);
            return (float)((double)lvalue[0]/lvalue[1]);
        case TIFFTag.TIFF_ASCII:
             String s = ((String[])data)[index];
             return (float)Double.parseDouble(s);
        default:
            throw new ClassCastException(); // should never happen
        }
    }

    /**
     * Returns data in any format as a {@code double}.
     *
     * <p> {@code TIFFTag.TIFF_BYTE} and {@code TIFF_UNDEFINED} data
     * are treated as unsigned; that is, no sign extension will take
     * place and the returned value will be in the range [0, 255].
     * {@code TIFF_SBYTE} data will be returned in the range
     * [-128, 127].
     *
     * <p> Data in {@code TIFF_SRATIONAL} or
     * {@code TIFF_RATIONAL} format are evaluated by dividing the
     * numerator into the denominator using double-precision
     * arithmetic.
     *
     * <p> Data in {@code TIFF_ASCII} format will be parsed as by
     * the {@code Double.parseDouble} method.
     *
     * @param index The index of the data.
     * @return The data at the given index as a {@code double}.
     */
    public double getAsDouble(int index) {
        switch (type) {
        case TIFFTag.TIFF_BYTE:
        case TIFFTag.TIFF_UNDEFINED:
            return ((byte[])data)[index] & 0xff;
        case TIFFTag.TIFF_SBYTE:
            return ((byte[])data)[index];
        case TIFFTag.TIFF_SHORT:
            return ((char[])data)[index] & 0xffff;
        case TIFFTag.TIFF_SSHORT:
            return ((short[])data)[index];
        case TIFFTag.TIFF_SLONG:
            return ((int[])data)[index];
        case TIFFTag.TIFF_LONG:
        case TIFFTag.TIFF_IFD_POINTER:
            return ((long[])data)[index];
        case TIFFTag.TIFF_FLOAT:
            return ((float[])data)[index];
        case TIFFTag.TIFF_DOUBLE:
            return ((double[])data)[index];
        case TIFFTag.TIFF_SRATIONAL:
            int[] ivalue = getAsSRational(index);
            return (double)ivalue[0]/ivalue[1];
        case TIFFTag.TIFF_RATIONAL:
            long[] lvalue = getAsRational(index);
            return (double)lvalue[0]/lvalue[1];
        case TIFFTag.TIFF_ASCII:
             String s = ((String[])data)[index];
             return Double.parseDouble(s);
        default:
            throw new ClassCastException(); // should never happen
        }
    }

    /**
     * Returns a {@code TIFFTag.TIFF_ASCII} value as a
     * {@code String}.
     *
     * @throws ClassCastException if the field is not of type
     * {@code TIFF_ASCII}.
     *
     * @param index The index of the data.
     * @return The data at the given index as a {@code String}.
     */
    public String getAsString(int index) {
        return ((String[])data)[index];
    }

    /**
     * Returns a {@code TIFFTag.TIFF_SRATIONAL} data item as a
     * two-element array of {@code int}s.
     *
     * @param index The index of the data.
     * @return The data at the given index as a signed rational.
     * @throws ClassCastException if the field is not of type
     * {@code TIFF_SRATIONAL}.
     */
    public int[] getAsSRational(int index) {
        return ((int[][])data)[index];
    }

    /**
     * Returns a TIFFTag.TIFF_RATIONAL data item as a two-element array
     * of ints.
     *
     * @param index The index of the data.
     * @return The data at the given index as an unsigned rational.
     * @throws ClassCastException if the field is not of type
     * {@code TIFF_RATIONAL}.
     */
    public long[] getAsRational(int index) {
        return ((long[][])data)[index];
    }


    /**
     * Returns a {@code String} containing a human-readable
     * version of the data item.  Data of type
     * {@code TIFFTag.TIFF_RATIONAL} or {@code TIFF_SRATIONAL} are
     * represented as a pair of integers separated by a
     * {@code '/'} character.
     *
     * @param index The index of the data.
     * @return The data at the given index as a {@code String}.
     * @throws ClassCastException if the field is not of one of the
     * legal field types.
     */
    public String getValueAsString(int index) {
        switch (type) {
        case TIFFTag.TIFF_ASCII:
            return ((String[])data)[index];
        case TIFFTag.TIFF_BYTE:
        case TIFFTag.TIFF_UNDEFINED:
            return Integer.toString(((byte[])data)[index] & 0xff);
        case TIFFTag.TIFF_SBYTE:
            return Integer.toString(((byte[])data)[index]);
        case TIFFTag.TIFF_SHORT:
            return Integer.toString(((char[])data)[index] & 0xffff);
        case TIFFTag.TIFF_SSHORT:
            return Integer.toString(((short[])data)[index]);
        case TIFFTag.TIFF_SLONG:
            return Integer.toString(((int[])data)[index]);
        case TIFFTag.TIFF_LONG:
        case TIFFTag.TIFF_IFD_POINTER:
            return Long.toString(((long[])data)[index]);
        case TIFFTag.TIFF_FLOAT:
            return Float.toString(((float[])data)[index]);
        case TIFFTag.TIFF_DOUBLE:
            return Double.toString(((double[])data)[index]);
        case TIFFTag.TIFF_SRATIONAL:
            int[] ivalue = getAsSRational(index);
            String srationalString;
            if(ivalue[1] != 0 && ivalue[0] % ivalue[1] == 0) {
                // If the denominator is a non-zero integral divisor
                // of the numerator then convert the fraction to be
                // with respect to a unity denominator.
                srationalString =
                    Integer.toString(ivalue[0] / ivalue[1]) + "/1";
            } else {
                // Use the values directly.
                srationalString =
                    Integer.toString(ivalue[0]) +
                    "/" +
                    Integer.toString(ivalue[1]);
            }
            return srationalString;
        case TIFFTag.TIFF_RATIONAL:
            long[] lvalue = getAsRational(index);
            String rationalString;
            if(lvalue[1] != 0L && lvalue[0] % lvalue[1] == 0) {
                // If the denominator is a non-zero integral divisor
                // of the numerator then convert the fraction to be
                // with respect to a unity denominator.
                rationalString =
                    Long.toString(lvalue[0] / lvalue[1]) + "/1";
            } else {
                // Use the values directly.
                rationalString =
                    Long.toString(lvalue[0]) +
                    "/" +
                    Long.toString(lvalue[1]);
            }
            return rationalString;
        default:
            throw new ClassCastException(); // should never happen
        }
    }

    /**
     * Returns whether the field has a {@code TIFFDirectory}.
     *
     * @return true if and only if getDirectory() returns non-null.
     */
    public boolean hasDirectory() {
        return getDirectory() != null;
    }

    /**
     * Returns the associated {@code TIFFDirectory}, if available. If no
     * directory is set, then {@code null} will be returned.
     *
     * @return the TIFFDirectory instance or null.
     */
    public TIFFDirectory getDirectory() {
        return dir;
    }

    /**
     * Clones the field and all the information contained therein.
     *
     * @return A clone of this {@code TIFFField}.
     * @throws CloneNotSupportedException if the instance cannot be cloned.
     */
    @Override
    public TIFFField clone() throws CloneNotSupportedException {
        TIFFField field = (TIFFField)super.clone();

        Object fieldData;
        switch (type) {
        case TIFFTag.TIFF_BYTE:
        case TIFFTag.TIFF_UNDEFINED:
        case TIFFTag.TIFF_SBYTE:
            fieldData = ((byte[])data).clone();
            break;
        case TIFFTag.TIFF_SHORT:
            fieldData = ((char[])data).clone();
            break;
        case TIFFTag.TIFF_SSHORT:
            fieldData = ((short[])data).clone();
            break;
        case TIFFTag.TIFF_SLONG:
            fieldData = ((int[])data).clone();
            break;
        case TIFFTag.TIFF_LONG:
        case TIFFTag.TIFF_IFD_POINTER:
            fieldData = ((long[])data).clone();
            break;
        case TIFFTag.TIFF_FLOAT:
            fieldData = ((float[])data).clone();
            break;
        case TIFFTag.TIFF_DOUBLE:
            fieldData = ((double[])data).clone();
            break;
        case TIFFTag.TIFF_SRATIONAL:
            fieldData = ((int[][])data).clone();
            break;
        case TIFFTag.TIFF_RATIONAL:
            fieldData = ((long[][])data).clone();
            break;
        case TIFFTag.TIFF_ASCII:
            fieldData = ((String[])data).clone();
            break;
        default:
            throw new ClassCastException(); // should never happen
        }

        field.tag = tag;
        field.tagNumber = tagNumber;
        field.type = type;
        field.count = count;
        field.data = fieldData;
        field.dir = dir != null ? dir.clone() : null;

        return field;
    }
}