/*
 * Copyright (c) 2005, 2021, 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;

/**
 * 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:
 *
 * <table class="striped">
 * <caption>TIFF Data Type to Java Data Type Mapping</caption>
 * <thead>
 *   <tr>
 *     <th scope="col">TIFF Data Type
 *     <th scope="col">Java Constant
 *     <th scope="col">Java Data Type
 *     <th scope="col">Java Type Name
 * </thead>
 * <tbody>
 *   <tr>
 *     <th scope="row">{@code BYTE}
 *     <td>{@link TIFFTag#TIFF_BYTE}
 *     <td>{@code byte}
 *     <td>{@code "Byte"}
 *   <tr>
 *     <th scope="row">{@code ASCII}
 *     <td>{@link TIFFTag#TIFF_ASCII}
 *     <td>{@code String}
 *     <td>{@code "Ascii"}
 *   <tr>
 *     <th scope="row">{@code SHORT}
 *     <td>{@link TIFFTag#TIFF_SHORT}
 *     <td>{@code char}
 *     <td>{@code "Short"}
 *   <tr>
 *     <th scope="row">{@code LONG}
 *     <td>{@link TIFFTag#TIFF_LONG}
 *     <td>{@code long}
 *     <td>{@code "Long"}
 *   <tr>
 *     <th scope="row">{@code RATIONAL}
 *     <td>{@link TIFFTag#TIFF_RATIONAL}
 *     <td>{@code long[2]} {numerator, denominator}
 *     <td>{@code "Rational"}
 *   <tr>
 *     <th scope="row">{@code SBYTE}
 *     <td>{@link TIFFTag#TIFF_SBYTE}
 *     <td>{@code byte}
 *     <td>{@code "SByte"}
 *   <tr>
 *     <th scope="row">{@code UNDEFINED}
 *     <td>{@link TIFFTag#TIFF_UNDEFINED}
 *     <td>{@code byte}
 *     <td>{@code "Undefined"}
 *   <tr>
 *     <th scope="row">{@code SSHORT}
 *     <td>{@link TIFFTag#TIFF_SSHORT}
 *     <td>{@code short}
 *     <td>{@code "SShort"}
 *   <tr>
 *     <th scope="row">{@code SLONG}
 *     <td>{@link TIFFTag#TIFF_SLONG}
 *     <td>{@code int}
 *     <td>{@code "SLong"}
 *   <tr>
 *     <th scope="row">{@code SRATIONAL}
 *     <td>{@link TIFFTag#TIFF_SRATIONAL}
 *     <td>{@code int[2]} {numerator, denominator}
 *     <td>{@code "SRational"}
 *   <tr>
 *     <th scope="row">{@code FLOAT}
 *     <td>{@link TIFFTag#TIFF_FLOAT}
 *     <td>{@code float}
 *     <td>{@code "Float"}
 *   <tr>
 *     <th scope="row">{@code DOUBLE}
 *     <td>{@link TIFFTag#TIFF_DOUBLE}
 *     <td>{@code double}
 *     <td>{@code "Double"}
 *   <tr>
 *     <th scope="row">{@code IFD}
 *     <td>{@link TIFFTag#TIFF_IFD_POINTER}
 *     <td>{@code long}
 *     <td>{@code "IFDPointer"}
 *   </tr>
 * </tbody>
 * </table>
 *
 * @since 9
 * @see   TIFFDirectory
 * @see   TIFFTag
 */
public final class TIFFField implements Cloneable {

    private static final long MAX_UINT32 = 0xffffffffL;

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

    private static final boolean[] IS_INTEGRAL = {
        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 {@code "number"} 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 IllegalArgumentException If the {@code Node} parameter content
     * does not adhere to the {@code TIFFField} element structure defined by
     * the <a href="../../metadata/doc-files/tiff_metadata.html#ImageMetadata">
     * TIFF native image metadata format specification</a>, or if the
     * combination of node attributes and data is not legal per the
     * {@link #TIFFField(TIFFTag,int,int,Object)} constructor specification.
     * Note that a cause might be set on such an exception.
     * @return A new {@code TIFFField}.
     */
    public static TIFFField createFromMetadataNode(TIFFTagSet tagSet,
                                                   Node node) {
        if (node == null) {
            // This method is specified to throw only IllegalArgumentExceptions
            // so we create an IAE with a NullPointerException as its cause.
            throw new IllegalArgumentException(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);
        }

        TIFFField field;
        try {
            field = new TIFFField(tag, type, count, data);
        } catch (NullPointerException npe) {
            // This method is specified to throw only IllegalArgumentExceptions
            // so we catch the NullPointerException and set it as the cause of
            // the IAE which is thrown.
            throw new IllegalArgumentException(npe);
        }

        return field;
    }

    /**
     * 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 == 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 < 0}.
     * @throws IllegalArgumentException if {@code count < 1}
     * and {@code type} is {@code TIFF_RATIONAL} or
     * {@code TIFF_SRATIONAL}.
     * @throws IllegalArgumentException if {@code count != 1}
     * and {@code type} is {@code TIFF_IFD_POINTER}.
     * @throws NullPointerException if {@code data == 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.
     * @throws IllegalArgumentException if the type of the data array is
     * {@code TIFF_LONG}, {@code TIFF_RATIONAL}, or {@code TIFF_IFD_POINTER}
     * and any of the elements is negative or greater than {@code 0xffffffff}.
     */
    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 and 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;
            if (isDataArrayCorrect) {
                for (long datum : (long[])data) {
                    if (datum < 0) {
                        throw new IllegalArgumentException
                            ("Negative value supplied for TIFF_LONG");
                    }
                    if (datum > MAX_UINT32) {
                        throw new IllegalArgumentException
                            ("Too large value supplied for TIFF_LONG");
                    }
                }
            }
            break;
        case TIFFTag.TIFF_IFD_POINTER:
            isDataArrayCorrect = data instanceof long[]
                && ((long[])data).length == 1;
            if (((long[])data)[0] < 0) {
                throw new IllegalArgumentException
                    ("Negative value supplied for TIFF_IFD_POINTER");
            }
            if (((long[])data)[0] > MAX_UINT32) {
                throw new IllegalArgumentException
                    ("Too large value supplied for TIFF_IFD_POINTER");
            }
            break;
        case TIFFTag.TIFF_RATIONAL:
            isDataArrayCorrect = data instanceof long[][]
                && ((long[][])data).length == count;
            if (isDataArrayCorrect) {
                for (long[] datum : (long[][])data) {
                    if (datum.length != 2) {
                        isDataArrayCorrect = false;
                        break;
                    }
                    if (datum[0] < 0 || datum[1] < 0) {
                        throw new IllegalArgumentException
                            ("Negative value supplied for TIFF_RATIONAL");
                    }
                    if (datum[0] > MAX_UINT32 || datum[1] > MAX_UINT32) {
                        throw new IllegalArgumentException
                            ("Too large value supplied for TIFF_RATIONAL");
                    }
                }
            }
            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;
            if (isDataArrayCorrect) {
                for (int[] datum : (int[][])data) {
                    if (datum.length != 2) {
                        isDataArrayCorrect = false;
                        break;
                    }
                }
            }
            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 == 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 < 0}.
     * @see #TIFFField(TIFFTag,int,int,Object)
     * @throws IllegalArgumentException if {@code count < 1}
     * and {@code type} is {@code TIFF_RATIONAL} or
     * {@code TIFF_SRATIONAL}.
     * @throws IllegalArgumentException if {@code count != 1}
     * and {@code type} is {@code TIFF_IFD_POINTER}.
     */
    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 value} is in {@code [0,0xffff]}, and type
     * {@link TIFFTag#TIFF_LONG TIFF_LONG} if {@code value} is in
     * {@code [0x10000,0xffffffff]}. 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 == null}.
     * @throws IllegalArgumentException if {@code value} is not in
     * {@code [0,0xffffffff]}.
     * @throws IllegalArgumentException if {@code value} is in
     * {@code [0,0xffff]} and {@code TIFF_SHORT} is an unacceptable type
     * for the {@code TIFFTag}, or if {@code value} is in
     * {@code [0x10000,0xffffffff]} and {@code TIFF_LONG} is an unacceptable
     * type for the {@code TIFFTag}.
     */
    public TIFFField(TIFFTag tag, long value) {
        if(tag == null) {
            throw new NullPointerException("tag == null!");
        }
        if (value < 0) {
            throw new IllegalArgumentException("value < 0!");
        }
        if (value > MAX_UINT32) {
            throw new IllegalArgumentException("value > 0xffffffff!");
        }

        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 "
                    + getTypeName(TIFFTag.TIFF_SHORT) + " for tag "
                    + "\"" + tag.getName() + "\"");
            }
            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 "
                    + getTypeName(TIFFTag.TIFF_LONG) + " for tag "
                    + "\"" + tag.getName() + "\"");
            }
            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 == null}.
     * @throws IllegalArgumentException if {@code type} is an unacceptable
     * data type for the supplied {@code TIFFTag}.
     * @throws IllegalArgumentException if {@code type} is neither
     * {@code TIFFTag.TIFF_LONG} nor {@code TIFFTag.TIFF_IFD_POINTER}.
     * @throws IllegalArgumentException if {@code offset <= 0}.
     * @throws NullPointerException if {@code dir == null}.
     *
     * @see #TIFFField(TIFFTag,int,int,Object)
     */
    public TIFFField(TIFFTag tag, int type, long offset, TIFFDirectory dir) {
        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 (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.tag = tag;
        this.tagNumber = tag.getNumber();
        this.type = type;
        this.count = 1;
        this.data = new long[] {offset};

        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,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 TYPE_NAMES[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(TYPE_NAMES[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 < 0}.
     * @throws IllegalArgumentException if {@code count < 1}
     * and {@code type} is {@code TIFF_RATIONAL} or
     * {@code TIFF_SRATIONAL}.
     * @throws IllegalArgumentException if {@code count != 1}
     * and {@code type} is {@code TIFF_IFD_POINTER}.
     */
    public static Object createArrayForType(int dataType, int count) {

        if(count < 0) {
            throw new IllegalArgumentException("count < 0!");
        } else if ((dataType == TIFFTag.TIFF_RATIONAL
                   || dataType == TIFFTag.TIFF_SRATIONAL)
                  && count < 1) {
            throw new IllegalArgumentException
                ("Type is TIFF_RATIONAL or TIFF_SRATIONAL and count < 1");
        } else if (dataType == TIFFTag.TIFF_IFD_POINTER && count != 1) {
            throw new IllegalArgumentException
                ("Type is TIFF_IFD_POINTER and count != 1");
        }

        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
     * {@code "TIFFField"} or {@code "TIFFIFD"} as described in the
     * TIFF native image metadata specification. The node will be named
     * {@code "TIFFIFD"} if and only if {@link #hasDirectory()} returns
     * {@code true} and the field's type is either {@link TIFFTag#TIFF_LONG}
     * or {@link TIFFTag#TIFF_IFD_POINTER}.
     *
     * @return a {@code Node} named {@code "TIFFField"} or
     * {@code "TIFFIFD"}.
     */
    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 IS_INTEGRAL[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.  If the numerator of a
     * {@code TIFFTag.TIFF_RATIONAL} or {@code TIFF_SRATIONAL} is an integral
     * multiple of the denominator, then the value is represented as
     * {@code "q/1"} where {@code q} is the quotient of the numerator and
     * denominator.
     *
     * @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 = (ivalue[0] / ivalue[1]) + "/1";
            } else {
                // Use the values directly.
                srationalString = ivalue[0] + "/" + 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 = (lvalue[0] / lvalue[1]) + "/1";
            } else {
                // Use the values directly.
                rationalString = lvalue[0] + "/" + 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;
    }
}
