8152183: [TEST] add test for TIFFField
authoravstepan
Fri, 08 Apr 2016 12:56:28 +0300
changeset 37568 9e458df677e9
parent 37567 87e77fa3bbc7
child 37569 bf2bc4d9491d
8152183: [TEST] add test for TIFFField Reviewed-by: prr, yan
jdk/test/javax/imageio/plugins/tiff/MultiPageImageTIFFFieldTest.java
jdk/test/javax/imageio/plugins/tiff/TIFFDirectoryWriteReadTest.java
jdk/test/javax/imageio/plugins/tiff/TIFFFieldTest.java
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/javax/imageio/plugins/tiff/MultiPageImageTIFFFieldTest.java	Fri Apr 08 12:56:28 2016 +0300
@@ -0,0 +1,378 @@
+/*
+ * Copyright (c) 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.
+ *
+ * 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.
+ */
+
+/**
+ * @test
+ * @ignore  8148454
+ * @bug     8152183 8148454
+ * @author  a.stepanov
+ * @summary check that TIFFields are derived properly for multi-page tiff
+ * @run     main MultiPageImageTIFFFieldTest
+ */
+
+import java.awt.*;
+import java.awt.color.*;
+import java.awt.image.BufferedImage;
+import java.io.*;
+import javax.imageio.*;
+import javax.imageio.metadata.*;
+import javax.imageio.stream.*;
+import javax.imageio.plugins.tiff.*;
+
+
+public class MultiPageImageTIFFFieldTest {
+
+    private final static String FILENAME = "test.tiff";
+    private final static int W1 = 20, H1 = 40, W2 = 100, H2 = 15;
+    private final static Color C1 = Color.BLACK, C2 = Color.RED;
+
+    private final static int N_WIDTH  = BaselineTIFFTagSet.TAG_IMAGE_WIDTH;
+    private final static int N_HEIGHT = BaselineTIFFTagSet.TAG_IMAGE_LENGTH;
+
+    private static final String DESCRIPTION_1[] = {"Description-1", "abc ABC"};
+    private static final String DESCRIPTION_2[] = {"Description-2", "1-2-3"};
+    private final static int N_DESCRIPTION =
+        BaselineTIFFTagSet.TAG_IMAGE_DESCRIPTION;
+
+    private final static String EXIF_DATA_1[] = {"2001:01:01 00:00:01"};
+    private final static String EXIF_DATA_2[] = {"2002:02:02 00:00:02"};
+    private final static int N_EXIF = ExifTIFFTagSet.TAG_DATE_TIME_ORIGINAL;
+
+    private final static String GPS_DATA[] = {
+        ExifGPSTagSet.STATUS_MEASUREMENT_IN_PROGRESS};
+    private final static int N_GPS = ExifGPSTagSet.TAG_GPS_STATUS;
+
+    private final static short FAX_DATA =
+        FaxTIFFTagSet.CLEAN_FAX_DATA_ERRORS_UNCORRECTED;
+    private final static int N_FAX = FaxTIFFTagSet.TAG_CLEAN_FAX_DATA;
+
+    private static final byte[] ICC_PROFILE_2 =
+        ICC_ProfileRGB.getInstance(ColorSpace.CS_sRGB).getData();
+    private static final int N_ICC = BaselineTIFFTagSet.TAG_ICC_PROFILE;
+
+    private static final int N_BPS = BaselineTIFFTagSet.TAG_BITS_PER_SAMPLE;
+
+    private static final int
+        COMPRESSION_1 = BaselineTIFFTagSet.COMPRESSION_DEFLATE,
+        COMPRESSION_2 = BaselineTIFFTagSet.COMPRESSION_LZW;
+    private static final int N_COMPRESSION = BaselineTIFFTagSet.TAG_COMPRESSION;
+
+    private static final int
+        GRAY_1 = BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO,
+        GRAY_2 = BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO,
+        RGB    = BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_RGB;
+
+    private static final int N_PHOTO =
+        BaselineTIFFTagSet.TAG_PHOTOMETRIC_INTERPRETATION;
+
+    private ImageWriter getTIFFWriter() {
+
+        java.util.Iterator<ImageWriter> writers =
+            ImageIO.getImageWritersByFormatName("TIFF");
+        if (!writers.hasNext()) {
+            throw new RuntimeException("No writers available for TIFF format");
+        }
+        return writers.next();
+    }
+
+    private ImageReader getTIFFReader() {
+
+        java.util.Iterator<ImageReader> readers =
+            ImageIO.getImageReadersByFormatName("TIFF");
+        if (!readers.hasNext()) {
+            throw new RuntimeException("No readers available for TIFF format");
+        }
+        return readers.next();
+    }
+
+    private void addASCIIField(TIFFDirectory d,
+                               String        name,
+                               String        data[],
+                               int           num) {
+
+        d.addTIFFField(new TIFFField(
+            new TIFFTag(name, num, 1 << TIFFTag.TIFF_ASCII),
+                TIFFTag.TIFF_ASCII, data.length, data));
+    }
+
+    private void checkASCIIField(TIFFDirectory d,
+                                 String        what,
+                                 String        data[],
+                                 int           num) {
+
+        String notFound = what + " field was not found";
+        check(d.containsTIFFField(num), notFound);
+        TIFFField f = d.getTIFFField(num);
+        check(f.getType() == TIFFTag.TIFF_ASCII, "field type != ASCII");
+        check(f.getCount() == data.length, "invalid " + what + " data count");
+        for (int i = 0; i < data.length; i++) {
+            check(f.getValueAsString(i).equals(data[i]),
+                "invalid " + what + " data");
+        }
+    }
+
+    private void writeImage() throws Exception {
+
+        OutputStream s = new BufferedOutputStream(new FileOutputStream(FILENAME));
+        try (ImageOutputStream ios = ImageIO.createImageOutputStream(s)) {
+
+            ImageWriter writer = getTIFFWriter();
+            writer.setOutput(ios);
+
+            BufferedImage img1 =
+                new BufferedImage(W1, H1, BufferedImage.TYPE_BYTE_GRAY);
+            Graphics g = img1.getGraphics();
+            g.setColor(C1);
+            g.fillRect(0, 0, W1, H1);
+            g.dispose();
+
+            BufferedImage img2 =
+                new BufferedImage(W2, H2, BufferedImage.TYPE_INT_RGB);
+            g = img2.getGraphics();
+            g.setColor(C2);
+            g.fillRect(0, 0, W2, H2);
+            g.dispose();
+
+            ImageWriteParam param1 = writer.getDefaultWriteParam();
+            param1.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
+            param1.setCompressionType("Deflate");
+            param1.setCompressionQuality(0.5f);
+
+            ImageWriteParam param2 = writer.getDefaultWriteParam();
+            param2.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
+            param2.setCompressionType("LZW");
+            param2.setCompressionQuality(0.5f);
+
+            IIOMetadata
+                md1 = writer.getDefaultImageMetadata(
+                    new ImageTypeSpecifier(img1), param1),
+                md2 = writer.getDefaultImageMetadata(
+                    new ImageTypeSpecifier(img2), param2);
+
+            TIFFDirectory
+                dir1 = TIFFDirectory.createFromMetadata(md1),
+                dir2 = TIFFDirectory.createFromMetadata(md2);
+
+            addASCIIField(dir1, "ImageDescription", DESCRIPTION_1, N_DESCRIPTION);
+            addASCIIField(dir2, "ImageDescription", DESCRIPTION_2, N_DESCRIPTION);
+
+            addASCIIField(dir1, "GPSStatus", GPS_DATA, N_GPS);
+            addASCIIField(dir2, "GPSStatus", GPS_DATA, N_GPS);
+
+            addASCIIField(dir1, "DateTimeOriginal", EXIF_DATA_1, N_EXIF);
+            addASCIIField(dir2, "DateTimeOriginal", EXIF_DATA_2, N_EXIF);
+
+            TIFFTag faxTag = new TIFFTag(
+                "CleanFaxData", N_FAX, 1 << TIFFTag.TIFF_SHORT);
+            dir1.addTIFFField(new TIFFField(faxTag, FAX_DATA));
+            dir2.addTIFFField(new TIFFField(faxTag, FAX_DATA));
+
+            dir2.addTIFFField(new TIFFField(
+                new TIFFTag("ICC Profile", N_ICC, 1 << TIFFTag.TIFF_UNDEFINED),
+                TIFFTag.TIFF_UNDEFINED, ICC_PROFILE_2.length, ICC_PROFILE_2));
+
+            writer.prepareWriteSequence(null);
+            writer.writeToSequence(
+                new IIOImage(img1, null, dir1.getAsMetadata()), param1);
+            writer.writeToSequence(
+                new IIOImage(img2, null, dir2.getAsMetadata()), param2);
+            writer.endWriteSequence();
+
+            ios.flush();
+            writer.dispose();
+        }
+        s.close();
+    }
+
+    private void checkBufferedImages(BufferedImage im1, BufferedImage im2) {
+
+        check(im1.getWidth()  == W1, "invalid width for image 1");
+        check(im1.getHeight() == H1, "invalid height for image 1");
+        check(im2.getWidth()  == W2, "invalid width for image 2");
+        check(im2.getHeight() == H2, "invalid height for image 2");
+
+        Color
+            c1 = new Color(im1.getRGB(W1 / 2, H1 / 2)),
+            c2 = new Color(im2.getRGB(W2 / 2, H2 / 2));
+
+        check(c1.equals(C1), "invalid image 1 color");
+        check(c2.equals(C2), "invalid image 2 color");
+    }
+
+    private void readAndCheckImage() throws Exception {
+
+        ImageReader reader = getTIFFReader();
+
+        ImageInputStream s = ImageIO.createImageInputStream(new File(FILENAME));
+        reader.setInput(s, false, true);
+
+        int ni = reader.getNumImages(true);
+        check(ni == 2, "invalid number of images");
+
+        // check TIFFImageReadParam for multipage image
+        TIFFImageReadParam
+            param1 = new TIFFImageReadParam(), param2 = new TIFFImageReadParam();
+
+        param1.addAllowedTagSet(ExifTIFFTagSet.getInstance());
+        param1.addAllowedTagSet(ExifGPSTagSet.getInstance());
+
+        param2.addAllowedTagSet(ExifTIFFTagSet.getInstance());
+        param2.addAllowedTagSet(GeoTIFFTagSet.getInstance());
+
+        // FaxTIFFTagSet is allowed by default
+        param2.removeAllowedTagSet(FaxTIFFTagSet.getInstance());
+
+
+        // read images and metadata
+        IIOImage i1 = reader.readAll(0, param1), i2 = reader.readAll(1, param2);
+        BufferedImage
+            bi1 = (BufferedImage) i1.getRenderedImage(),
+            bi2 = (BufferedImage) i2.getRenderedImage();
+
+        // check rendered images, just in case
+        checkBufferedImages(bi1, bi2);
+
+        TIFFDirectory
+            dir1 = TIFFDirectory.createFromMetadata(i1.getMetadata()),
+            dir2 = TIFFDirectory.createFromMetadata(i2.getMetadata());
+
+        // check ASCII fields
+        checkASCIIField(
+            dir1, "image 1 description", DESCRIPTION_1, N_DESCRIPTION);
+        checkASCIIField(
+            dir2, "image 2 description", DESCRIPTION_2, N_DESCRIPTION);
+
+        checkASCIIField(dir1, "image 1 datetime", EXIF_DATA_1, N_EXIF);
+        checkASCIIField(dir2, "image 2 datetime", EXIF_DATA_2, N_EXIF);
+
+        // check sizes
+        TIFFField f = dir1.getTIFFField(N_WIDTH);
+        check((f.getCount() == 1) && (f.getAsInt(0) == W1),
+            "invalid width field for image 1");
+        f = dir2.getTIFFField(N_WIDTH);
+        check((f.getCount() == 1) && (f.getAsInt(0) == W2),
+            "invalid width field for image 2");
+
+        f = dir1.getTIFFField(N_HEIGHT);
+        check((f.getCount() == 1) && (f.getAsInt(0) == H1),
+            "invalid height field for image 1");
+        f = dir2.getTIFFField(N_HEIGHT);
+        check((f.getCount() == 1) && (f.getAsInt(0) == H2),
+            "invalid height field for image 2");
+
+        // check fax data
+        check(dir1.containsTIFFField(N_FAX), "image 2 TIFF directory " +
+            "must contain clean fax data");
+        f = dir1.getTIFFField(N_FAX);
+        check(
+            (f.getCount() == 1) && f.isIntegral() && (f.getAsInt(0) == FAX_DATA),
+            "invalid clean fax data");
+
+        check(!dir2.containsTIFFField(N_FAX), "image 2 TIFF directory " +
+            "must not contain fax fields");
+
+        // check GPS data
+        checkASCIIField(dir1, "GPS status", GPS_DATA, N_GPS);
+
+        check(!dir2.containsTIFFField(N_GPS), "image 2 TIFF directory " +
+            "must not contain GPS fields");
+
+        // check ICC profile data
+        check(!dir1.containsTIFFField(N_ICC), "image 1 TIFF directory "
+            + "must not contain ICC Profile field");
+        check(dir2.containsTIFFField(N_ICC), "image 2 TIFF directory "
+            + "must contain ICC Profile field");
+
+        f = dir2.getTIFFField(N_ICC);
+        check(f.getType() == TIFFTag.TIFF_UNDEFINED,
+            "invalid ICC profile field type");
+        int cnt = f.getCount();
+        byte icc[] = f.getAsBytes();
+        check((cnt == ICC_PROFILE_2.length) && (cnt == icc.length),
+                "invalid ICC profile");
+        for (int i = 0; i < cnt; i++) {
+            check(icc[i] == ICC_PROFILE_2[i], "invalid ICC profile data");
+        }
+
+        // check component sizes
+        check(dir1.getTIFFField(N_BPS).isIntegral() &&
+              dir2.getTIFFField(N_BPS).isIntegral(),
+              "invalid bits per sample type");
+        int sz1[] = bi1.getColorModel().getComponentSize(),
+            sz2[] = bi2.getColorModel().getComponentSize(),
+            bps1[] = dir1.getTIFFField(N_BPS).getAsInts(),
+            bps2[] = dir2.getTIFFField(N_BPS).getAsInts();
+
+        check((bps1.length == sz1.length) && (bps2.length == sz2.length),
+            "invalid component size count");
+
+        for (int i = 0; i < bps1.length; i++) {
+            check(bps1[i] == sz1[i], "image 1: invalid bits per sample data");
+        }
+
+        for (int i = 0; i < bps2.length; i++) {
+            check(bps2[i] == sz2[i], "image 2: invalid bits per sample data");
+        }
+
+        // check compression data
+        check(dir1.containsTIFFField(N_COMPRESSION) &&
+              dir2.containsTIFFField(N_COMPRESSION),
+              "compression info lost");
+        f = dir1.getTIFFField(N_COMPRESSION);
+        check(f.isIntegral() && (f.getCount() == 1) &&
+            (f.getAsInt(0) == COMPRESSION_1), "invalid image 1 compression data");
+
+        f = dir2.getTIFFField(N_COMPRESSION);
+        check(f.isIntegral() && (f.getCount() == 1) &&
+            (f.getAsInt(0) == COMPRESSION_2), "invalid image 2 compression data");
+
+        // check photometric interpretation
+        f = dir1.getTIFFField(N_PHOTO);
+        check(f.isIntegral() && (f.getCount() == 1) &&
+            ((f.getAsInt(0) == GRAY_1) || (f.getAsInt(0) == GRAY_2)),
+            "invalid photometric interpretation for image 1");
+
+        f = dir2.getTIFFField(N_PHOTO);
+        check(f.isIntegral() && (f.getCount() == 1) && (f.getAsInt(0) == RGB),
+            "invalid photometric interpretation for image 2");
+    }
+
+    public void run() {
+
+        try {
+            writeImage();
+            readAndCheckImage();
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private void check(boolean ok, String msg) {
+
+        if (!ok) { throw new RuntimeException(msg); }
+    }
+
+    public static void main(String[] args) {
+        (new MultiPageImageTIFFFieldTest()).run();
+    }
+}
--- a/jdk/test/javax/imageio/plugins/tiff/TIFFDirectoryWriteReadTest.java	Fri Apr 08 12:00:19 2016 +0900
+++ b/jdk/test/javax/imageio/plugins/tiff/TIFFDirectoryWriteReadTest.java	Fri Apr 08 12:56:28 2016 +0300
@@ -207,7 +207,8 @@
         // RGB: PhotometricInterpretation = 2
         f = dir.getTIFFField(BaselineTIFFTagSet.TAG_PHOTOMETRIC_INTERPRETATION);
         check(f.getCount() == 1, "invalid count");
-        check(f.getAsInt(0) == 2, "invalid photometric interpretation for RGB");
+        check(f.getAsInt(0) == BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_RGB,
+            "invalid photometric interpretation value");
 
         String rat = " resolution must be rational";
         f = dir.getTIFFField(BaselineTIFFTagSet.TAG_X_RESOLUTION);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/javax/imageio/plugins/tiff/TIFFFieldTest.java	Fri Apr 08 12:56:28 2016 +0300
@@ -0,0 +1,502 @@
+/*
+ * Copyright (c) 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.
+ *
+ * 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.
+ */
+
+/**
+ * @test
+ * @bug     8152183
+ * @author  a.stepanov
+ * @summary Some checks for TIFFField methods
+ * @run     main TIFFFieldTest
+ */
+
+import java.util.List;
+import java.util.ArrayList;
+import javax.imageio.metadata.IIOMetadataNode;
+import javax.imageio.plugins.tiff.*;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+
+public class TIFFFieldTest {
+
+    private final static String NAME = "tag"; // tag name
+    private final static int    NUM  = 12345; // tag number
+    private final static int MIN_TYPE = TIFFTag.MIN_DATATYPE;
+    private final static int MAX_TYPE = TIFFTag.MAX_DATATYPE;
+    private final static String CONSTRUCT = "can construct TIFFField with ";
+
+    private void check(boolean ok, String msg) {
+        if (!ok) { throw new RuntimeException(msg); }
+    }
+
+    private void testConstructors() {
+
+        // test constructors
+
+        TIFFTag tag = new TIFFTag(
+            NAME, NUM, 1 << TIFFTag.TIFF_SHORT | 1 << TIFFTag.TIFF_LONG);
+        TIFFField f;
+
+        // constructor: TIFFField(tag, value)
+        boolean ok = false;
+        try { new TIFFField(null, 0); }
+        catch (NullPointerException e) { ok = true; }
+        check(ok, CONSTRUCT + "null tag");
+
+        ok = false;
+        try { new TIFFField(tag, -1); }
+        catch (IllegalArgumentException e) { ok = true; }
+        check(ok, CONSTRUCT + "invalid count");
+
+        // check value type recognition
+        int v = 1 << 16;
+        f = new TIFFField(tag, v - 1);
+        check(f.getType() == TIFFTag.TIFF_SHORT, "must be treated as short");
+        check(f.isIntegral(), "must be integral");
+        f = new TIFFField(tag, v);
+        check(f.getType() == TIFFTag.TIFF_LONG, "must be treated as long");
+
+        // other checks
+        check(f.getAsLongs().length == 1, "invalid long[] size");
+        check(f.isIntegral(), "must be integral");
+        check((f.getDirectory() == null) && !f.hasDirectory(),
+            "must not have directory");
+        check(f.getValueAsString(0).equals(String.valueOf(v)),
+            "invalid string representation of value");
+        check(f.getTag().getNumber() == f.getTagNumber(),
+            "invalid tag number");
+        check(f.getCount() == 1, "invalid count");
+        check(f.getTagNumber() == NUM, "invalid tag number");
+
+        // constructor: TIFFField(tag, type, count)
+        int type = TIFFTag.TIFF_SHORT;
+
+        ok = false;
+        try { new TIFFField(null, type, 1); }
+        catch (NullPointerException e) { ok = true; }
+        check(ok, CONSTRUCT + "null tag");
+
+        ok = false;
+        try { new TIFFField(tag, MAX_TYPE + 1, 1); }
+        catch (IllegalArgumentException e) { ok = true; }
+        check(ok, CONSTRUCT + "invalid type tag");
+
+        // check that count == 1 for TIFF_IFD_POINTER
+        ok = false;
+        try { new TIFFField(tag, TIFFTag.TIFF_IFD_POINTER, 0); }
+        catch (IllegalArgumentException e) { ok = true; }
+        check(ok, "only count = 1 should be allowed for IFDPointer");
+
+        ok = false;
+        try { new TIFFField(tag, TIFFTag.TIFF_IFD_POINTER, 2); }
+        catch (IllegalArgumentException e) { ok = true; }
+        check(ok, "only count = 1 should be allowed for IFDPointer");
+
+        // check that count == 0 is not allowed for TIFF_RATIONAL, TIFF_SRATIONAL
+        // (see fix for JDK-8149120)
+        ok = false;
+        try { new TIFFField(tag, TIFFTag.TIFF_RATIONAL, 0); }
+        catch (IllegalArgumentException e) { ok = true; }
+        check(ok, "count = 0 should not be allowed for Rational");
+
+        ok = false;
+        try { new TIFFField(tag, TIFFTag.TIFF_SRATIONAL, 0); }
+        catch (IllegalArgumentException e) { ok = true; }
+        check(ok, "count = 0 should not be allowed for SRational");
+
+        ok = false;
+        try { new TIFFField(tag, type, -1); }
+        catch (IllegalArgumentException e) { ok = true; }
+        check(ok, CONSTRUCT + "with invalid data count");
+
+        f = new TIFFField(tag, type, 0);
+        check(f.getCount() == 0, "invalid count");
+        check(!f.hasDirectory(), "must not have directory");
+
+        // constructor: TIFFField(tag, type, count, data)
+        double a[] = {0.1, 0.2, 0.3};
+        ok = false;
+        try { new TIFFField(null, TIFFTag.TIFF_DOUBLE, a.length, a); }
+        catch (NullPointerException e) { ok = true; }
+        check(ok, CONSTRUCT + "null tag");
+
+        ok = false;
+        try { new TIFFField(tag, type, a.length - 1, a); }
+        catch (IllegalArgumentException e) { ok = true; }
+        check(ok, CONSTRUCT + "invalid data count");
+
+        String a2[] = {"one", "two"};
+        ok = false;
+        try { new TIFFField(tag, type, 2, a2); }
+        catch (IllegalArgumentException e) { ok = true; }
+        check(ok, CONSTRUCT + "invalid data type");
+        check((f.getDirectory() == null) && !f.hasDirectory(),
+            "must not have directory");
+
+        // constructor: TIFFField(tag, type, offset, dir)
+        List<TIFFTag> tags = new ArrayList<>();
+        tags.add(tag);
+        TIFFTagSet sets[] = {new TIFFTagSet(tags)};
+        TIFFDirectory dir = new TIFFDirectory(sets, null);
+
+        ok = false;
+        try { new TIFFField(null, type, 4L, dir); }
+        catch (NullPointerException e) { ok = true; }
+        check(ok, CONSTRUCT + "null tag");
+
+        ok = false;
+        try { new TIFFField(tag, type, 0L, dir); }
+        catch (IllegalArgumentException e) { ok = true; }
+        check(ok, CONSTRUCT + "non-positive offset");
+
+        long offset = 4;
+
+        for (int t = MIN_TYPE; t <= MAX_TYPE; t++) {
+
+            tag = new TIFFTag(NAME, NUM, 1 << t);
+
+            // only TIFF_LONG and TIFF_IFD_POINTER types are allowed
+            if (t == TIFFTag.TIFF_LONG || t == TIFFTag.TIFF_IFD_POINTER) {
+
+                f = new TIFFField(tag, t, offset, dir);
+                check(f.hasDirectory(), "must have directory");
+
+                check(f.getDirectory().getTag(NUM).getName().equals(NAME),
+                    "invalid tag name");
+
+                check(f.getCount() == 1, "invalid count");
+                check(f.getAsLong(0) == offset, "invalid offset");
+            } else {
+                ok = false;
+                try { new TIFFField(tag, t, offset, dir); }
+                catch (IllegalArgumentException e) { ok = true; }
+                check(ok, CONSTRUCT + "invalid data type");
+            }
+        }
+
+        type = TIFFTag.TIFF_IFD_POINTER;
+        tag = new TIFFTag(NAME, NUM, 1 << type);
+        ok = false;
+        try { new TIFFField(tag, type, offset, null); }
+        catch (NullPointerException e) { ok = true; }
+        check(ok, CONSTRUCT + "null TIFFDirectory");
+
+        type = TIFFTag.TIFF_LONG;
+        tag = new TIFFTag(NAME, NUM, 1 << type);
+        ok = false;
+        try { new TIFFField(tag, type, offset, null); }
+        catch (NullPointerException e) { ok = true; }
+        check(ok, CONSTRUCT + "null TIFFDirectory");
+    }
+
+    private void testTypes() {
+
+        // test getTypeName(), getTypeByName() methods
+
+        boolean ok = false;
+        try { TIFFField.getTypeName(MIN_TYPE - 1); }
+        catch (IllegalArgumentException e) { ok = true; }
+        check(ok, "invalid data type number used");
+
+        ok = false;
+        try { TIFFField.getTypeName(MAX_TYPE + 1); }
+        catch (IllegalArgumentException e) { ok = true; }
+        check(ok, "invalid data type number used");
+
+        for (int type = MIN_TYPE; type <= MAX_TYPE; type++) {
+            String name = TIFFField.getTypeName(type);
+            check(TIFFField.getTypeByName(name) == type, "invalid type");
+        }
+
+        for (int type = MIN_TYPE; type <= MAX_TYPE; type++) {
+
+            TIFFTag tag = new TIFFTag(NAME, NUM, 1 << type);
+            TIFFField f = new TIFFField(tag, type, 1);
+            check(f.getType() == type, "invalid type");
+
+            // check that invalid data types can not be used
+            for (int type2 = MIN_TYPE; type2 <= MAX_TYPE; ++type2) {
+                if (type2 != type) {
+                    ok = false;
+                    try { new TIFFField(tag, type2, 1); } // invalid type
+                    catch (IllegalArgumentException e) { ok = true; }
+                    check(ok, "invalid type was successfully set");
+                }
+            }
+        }
+    }
+
+    private void testGetAs() {
+
+        // test getAs...() methods
+
+        int type = TIFFTag.TIFF_SHORT;
+        TIFFTag tag = new TIFFTag(NAME, NUM, 1 << TIFFTag.TIFF_SHORT);
+
+        short v = 123;
+        TIFFField f = new TIFFField(tag, v);
+
+        check(f.getAsInt(0)    ==    (int) v, "invalid int value");
+        check(f.getAsLong(0)   ==   (long) v, "invalid long value");
+        check(f.getAsFloat(0)  ==  (float) v, "invalid float value");
+        check(f.getAsDouble(0) == (double) v, "invalid double value");
+        check(f.getValueAsString(0).equals(Short.toString(v)),
+            "invalid string representation");
+
+        check(f.getAsInts().length == 1, "inavlid array size");
+        check((int) v == f.getAsInts()[0], "invalid int value");
+
+        float fa[] = {0.01f, 1.01f};
+        type = TIFFTag.TIFF_FLOAT;
+        f = new TIFFField(
+            new TIFFTag(NAME, NUM, 1 << type), type, fa.length, fa);
+        check(f.getCount() == fa.length, "invalid count");
+        float fa2[] = f.getAsFloats();
+        check(fa2.length == fa.length, "invalid array size");
+
+        for (int i = 0; i < fa.length; i++) {
+            check(fa2[i] == fa[i], "invalid value");
+            check(f.getAsDouble(i) == fa[i], "invalid value");
+            check(f.getAsInt(i) == (int) fa[i], "invalid value"); // cast to int
+            check(f.getValueAsString(i).equals(Float.toString(fa[i])),
+            "invalid string representation");
+        }
+
+        byte ba[] = {-1, -10, -100};
+        type = TIFFTag.TIFF_BYTE;
+        f = new TIFFField(
+            new TIFFTag(NAME, NUM, 1 << type), type, ba.length, ba);
+        check(f.getCount() == ba.length, "invalid count");
+        byte ba2[] = f.getAsBytes();
+        check(ba2.length == ba.length, "invalid count");
+
+        for (int i = 0; i < ba.length; i++) {
+            check(ba[i] == ba2[i], "invalid value");
+            check(ba[i] == (byte) f.getAsDouble(i), "invalid value");
+            check(ba[i] == (byte) f.getAsLong(i),   "invalid value");
+
+            int unsigned = ba[i] & 0xff;
+            check(f.getAsInt(i) == unsigned, "must be treated as unsigned");
+        }
+
+        char ca[] = {'a', 'z', 0xffff};
+        type = TIFFTag.TIFF_SHORT;
+        f = new TIFFField(
+            new TIFFTag(NAME, NUM, 1 << type), type, ca.length, ca);
+        check(f.getCount() == ca.length, "invalid count");
+        char ca2[] = f.getAsChars();
+        check(ba2.length == ba.length, "invalid count");
+
+        for (int i = 0; i < ca.length; i++) {
+            check(ca[i] == ca2[i], "invalid value");
+            check(ca[i] == (char) f.getAsDouble(i), "invalid value");
+            check(ca[i] == (char) f.getAsLong(i), "invalid value");
+            check(ca[i] == (char) f.getAsInt(i), "invalid value");
+        }
+
+        type = TIFFTag.TIFF_DOUBLE;
+        double da[] = {0.1, 0.2, 0.3};
+        f = new TIFFField(
+            new TIFFTag(NAME, NUM, 1 << type), type, da.length, da);
+        check(!f.isIntegral(), "isIntegral must be false");
+
+        double da2[] = f.getAsDoubles();
+        check(f.getData() instanceof double[], "invalid data type");
+        double da3[] = (double[]) f.getData();
+        check((da.length == da2.length) &&
+              (da.length == da2.length) &&
+              (da.length == f.getCount()),
+               "invalid data count");
+        for (int i = 0; i < da.length; ++i) {
+            check(da[i] == da2[i], "invalid data");
+            check(da[i] == da3[i], "invalid data");
+        }
+
+        boolean ok = false;
+        try { f.getAsShorts(); }
+        catch (ClassCastException e) { ok = true; }
+        check(ok, "invalid data cast");
+
+        ok = false;
+        try { f.getAsRationals(); }
+        catch (ClassCastException e) { ok = true; }
+        check(ok, "invalid data cast");
+
+        ok = false;
+        try { TIFFField.createArrayForType(TIFFTag.MIN_DATATYPE - 1, 1); }
+        catch (IllegalArgumentException e) { ok = true; }
+        check(ok, "can create array with invalid datatype");
+
+        ok = false;
+        try { TIFFField.createArrayForType(TIFFTag.MAX_DATATYPE + 1, 1); }
+        catch (IllegalArgumentException e) { ok = true; }
+        check(ok, "can create array with invalid datatype");
+
+        ok = false;
+        try { TIFFField.createArrayForType(TIFFTag.TIFF_FLOAT, -1); }
+        catch (IllegalArgumentException e) { ok = true; }
+        check(ok, "can create array with negative count");
+
+        int n = 3;
+        Object
+            RA  = TIFFField.createArrayForType(TIFFTag.TIFF_RATIONAL,  n),
+            SRA = TIFFField.createArrayForType(TIFFTag.TIFF_SRATIONAL, n);
+        check(RA  instanceof long[][], "invalid data type");
+        check(SRA instanceof  int[][], "invalid data type");
+
+        long ra[][] = (long[][]) RA;
+        int sra[][] = (int[][]) SRA;
+        check((ra.length == n) && (sra.length == n), "invalid data size");
+        for (int i = 0; i < n; i++) {
+            check((ra[i].length == 2) && (sra[i].length == 2),
+                "invalid data size");
+            ra[i][0]  =  1;  ra[i][1]  = 5 + i;
+            sra[i][0] = -1;  sra[i][1] = 5 + i;
+        }
+
+        type = TIFFTag.TIFF_RATIONAL;
+        TIFFField f1 = new TIFFField(
+            new TIFFTag(NAME, NUM, 1 << type), type, n, ra);
+        type = TIFFTag.TIFF_SRATIONAL;
+        TIFFField f2 = new TIFFField(
+            new TIFFTag(NAME, NUM, 1 << type), type, n, sra);
+
+        check((f1.getCount() == ra.length) && (f2.getCount() == sra.length),
+            "invalid data count");
+
+        check(f1.getAsRationals().length  == n, "invalid data count");
+        check(f2.getAsSRationals().length == n, "invalid data count");
+        for (int i = 0; i < n; i++) {
+            long r[] = f1.getAsRational(i);
+            check(r.length == 2, "invalid data format");
+            check((r[0] == 1) && (r[1] == i + 5), "invalid data");
+
+            int sr[] = f2.getAsSRational(i);
+            check(sr.length == 2, "invalid data format");
+            check((sr[0] == -1) && (sr[1] == i + 5), "invalid data");
+
+            // check string representation
+            String s = Long.toString(r[0]) + "/" + Long.toString(r[1]);
+            check(s.equals(f1.getValueAsString(i)),
+                "invalid string representation");
+
+            s = Integer.toString(sr[0]) + "/" + Integer.toString(sr[1]);
+            check(s.equals(f2.getValueAsString(i)),
+                "invalid string representation");
+
+            // see the documentation for getAsInt:
+            // TIFF_SRATIONAL or TIFF_RATIONAL format are evaluated
+            // by dividing the numerator into the denominator using
+            // double-precision arithmetic and then casting to int
+            check(f1.getAsInt(i) == (int)(r[0] / r[1]),
+                "invalid result for getAsInt");
+            check(f2.getAsInt(i) == (int)(r[0] / r[1]),
+                "invalid result for getAsInt");
+        }
+
+        ok = false;
+        try { f1.getAsRational(ra.length); }
+        catch (ArrayIndexOutOfBoundsException e) { ok = true; }
+        check(ok, "invalid index");
+
+        String sa[] = {"-1.e-25", "22", "-1.23E5"};
+        type = TIFFTag.TIFF_ASCII;
+        f = new TIFFField(
+            new TIFFTag(NAME, NUM, 1 << type), type, sa.length, sa);
+
+        // test clone() method
+        TIFFField cloned = null;
+        try { cloned = f.clone(); } catch (CloneNotSupportedException e) {
+            throw new RuntimeException(e);
+        }
+
+        check(f.getCount() == cloned.getCount(), "invalid cloned field count");
+
+        check(f.getCount() == sa.length, "invalid data count");
+        for (int i = 0; i < sa.length; i++) {
+            check(sa[i].equals(f.getAsString(i)), "invalid data");
+            // see docs: "data in TIFF_ASCII format will be parsed as by
+            // the Double.parseDouble method, with the result cast to int"
+            check(f.getAsInt(i) ==
+                (int) Double.parseDouble(sa[i]), "invalid data");
+            check(f.getAsDouble(i) == Double.parseDouble(sa[i]), "invalid data");
+
+            check(sa[i].equals(cloned.getAsString(i)), "invalid cloned data");
+        }
+    }
+
+    private void testCreateFromNode() {
+
+        int type = TIFFTag.TIFF_LONG;
+
+        List<TIFFTag> tags = new ArrayList<>();
+        int v = 1234567;
+        TIFFTag tag = new TIFFTag(NAME, NUM, 1 << type);
+        tags.add(tag);
+        TIFFTagSet ts = new TIFFTagSet(tags);
+
+        boolean ok = false;
+        try { TIFFField.createFromMetadataNode(ts, null); }
+        catch (NullPointerException e) { ok = true; }
+        check(ok, "can create TIFFField from a null node");
+
+        TIFFField f = new TIFFField(tag, v);
+        Node node = f.getAsNativeNode();
+        check(node.getNodeName().equals(f.getClass().getSimpleName()),
+            "invalid node name");
+
+        NamedNodeMap attrs = node.getAttributes();
+        for (int i = 0; i < attrs.getLength(); i++) {
+            String an = attrs.item(i).getNodeName().toLowerCase();
+            String av = attrs.item(i).getNodeValue();
+            if (an.contains("name")) {
+                check(av.equals(NAME), "invalid tag name");
+            } else if (an.contains("number")) {
+                check(av.equals(Integer.toString(NUM)), "invalid tag number");
+            }
+        }
+
+        // invalid node
+        IIOMetadataNode nok = new IIOMetadataNode("NOK");
+
+        ok = false;
+        try { TIFFField.createFromMetadataNode(ts, nok); }
+        catch (IllegalArgumentException e) { ok = true; }
+        check(ok, CONSTRUCT + "invalid node name");
+
+        TIFFField f2 = TIFFField.createFromMetadataNode(ts, node);
+        check(f2.getType() == type, "invalid type");
+        check(f2.getTagNumber() == NUM, "invalid tag number");
+        check(f2.getTag().getName().equals(NAME), "invalid tag name");
+        check(f2.getCount()  == 1, "invalid count");
+        check(f2.getAsInt(0) == v, "invalid value");
+    }
+
+    public static void main(String[] args) {
+
+        TIFFFieldTest test = new TIFFFieldTest();
+        test.testConstructors();
+        test.testCreateFromNode();
+        test.testTypes();
+        test.testGetAs();
+    }
+}