1 /*
   2  * Copyright (c) 2005, 2017, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 package javax.imageio.plugins.tiff;
  26 
  27 import java.util.ArrayList;
  28 import java.util.Arrays;
  29 import java.util.Iterator;
  30 import java.util.List;
  31 import java.util.Map;
  32 import java.util.TreeMap;
  33 import javax.imageio.metadata.IIOInvalidTreeException;
  34 import javax.imageio.metadata.IIOMetadata;
  35 import javax.imageio.metadata.IIOMetadataFormatImpl;
  36 import com.sun.imageio.plugins.tiff.TIFFIFD;
  37 import com.sun.imageio.plugins.tiff.TIFFImageMetadata;
  38 
  39 /**
  40  * A convenience class for simplifying interaction with TIFF native
  41  * image metadata. A TIFF image metadata tree represents an Image File
  42  * Directory (IFD) from a TIFF 6.0 stream. An IFD consists of a number of
  43  * IFD Entries each of which associates an identifying tag number with
  44  * a compatible value. A {@code TIFFDirectory} instance corresponds
  45  * to an IFD and contains a set of {@link TIFFField}s each of which
  46  * corresponds to an IFD Entry in the IFD.
  47  *
  48  * <p>When reading, a {@code TIFFDirectory} may be created by passing
  49  * the value returned by {@link javax.imageio.ImageReader#getImageMetadata
  50  * ImageReader.getImageMetadata()} to {@link #createFromMetadata
  51  * createFromMetadata()}. The {@link TIFFField}s in the directory may then
  52  * be obtained using the accessor methods provided in this class.</p>
  53  *
  54  * <p>When writing, an {@link IIOMetadata} object for use by one of the
  55  * {@code write()} methods of {@link javax.imageio.ImageWriter} may be
  56  * created from a {@code TIFFDirectory} by {@link #getAsMetadata()}.
  57  * The {@code TIFFDirectory} itself may be created by construction or
  58  * from the {@code IIOMetadata} object returned by
  59  * {@link javax.imageio.ImageWriter#getDefaultImageMetadata
  60  * ImageWriter.getDefaultImageMetadata()}. The {@code TIFFField}s in the
  61  * directory may be set using the mutator methods provided in this class.</p>
  62  *
  63  * <p>A {@code TIFFDirectory} is aware of the tag numbers in the
  64  * group of {@link TIFFTagSet}s associated with it. When
  65  * a {@code TIFFDirectory} is created from a native image metadata
  66  * object, these tag sets are derived from the {@code tagSets} attribute
  67  * of the {@code TIFFIFD} node.</p>
  68  *
  69  * <p>A {@code TIFFDirectory} might also have a parent {@link TIFFTag}.
  70  * This will occur if the directory represents an IFD other than the root
  71  * IFD of the image. The parent tag is the tag of the IFD Entry which is a
  72  * pointer to the IFD represented by this {@code TIFFDirectory}. The
  73  * {@link TIFFTag#isIFDPointer} method of this parent {@code TIFFTag}
  74  * must return {@code true}.  When a {@code TIFFDirectory} is
  75  * created from a native image metadata object, the parent tag set is set
  76  * from the {@code parentTagName} attribute of the corresponding
  77  * {@code TIFFIFD} node. Note that a {@code TIFFDirectory} instance
  78  * which has a non-{@code null} parent tag will be contained in the
  79  * data field of a {@code TIFFField} instance which has a tag field
  80  * equal to the contained directory's parent tag.</p>
  81  *
  82  * <p>As an example consider an Exif image. The {@code TIFFDirectory}
  83  * instance corresponding to the Exif IFD in the Exif stream would have parent
  84  * tag {@link ExifParentTIFFTagSet#TAG_EXIF_IFD_POINTER TAG_EXIF_IFD_POINTER}
  85  * and would include {@link ExifTIFFTagSet} in its group of known tag sets.
  86  * The {@code TIFFDirectory} corresponding to this Exif IFD will be
  87  * contained in the data field of a {@code TIFFField} which will in turn
  88  * be contained in the {@code TIFFDirectory} corresponding to the primary
  89  * IFD of the Exif image which will itself have a {@code null}-valued
  90  * parent tag.</p>
  91  *
  92  * <p><b>Note that this implementation is not synchronized. </b>If multiple
  93  * threads use a {@code TIFFDirectory} instance concurrently, and at
  94  * least one of the threads modifies the directory, for example, by adding
  95  * or removing {@code TIFFField}s or {@code TIFFTagSet}s, it
  96  * <i>must</i> be synchronized externally.</p>
  97  *
  98  * @since 9
  99  * @see   IIOMetadata
 100  * @see   TIFFField
 101  * @see   TIFFTag
 102  * @see   TIFFTagSet
 103  */
 104 public class TIFFDirectory implements Cloneable {
 105 
 106     /** The largest low-valued tag number in the TIFF 6.0 specification. */
 107     private static final int MAX_LOW_FIELD_TAG_NUM =
 108         BaselineTIFFTagSet.TAG_REFERENCE_BLACK_WHITE;
 109 
 110     /** The {@code TIFFTagSets} associated with this directory. */
 111     private List<TIFFTagSet> tagSets;
 112 
 113     /** The parent {@code TIFFTag} of this directory. */
 114     private TIFFTag parentTag;
 115 
 116     /**
 117      * The fields in this directory which have a low tag number. These are
 118      * managed as an array for efficiency as they are the most common fields.
 119      */
 120     private TIFFField[] lowFields = new TIFFField[MAX_LOW_FIELD_TAG_NUM + 1];
 121 
 122     /** The number of low tag numbered fields in the directory. */
 123     private int numLowFields = 0;
 124 
 125     /**
 126      * A mapping of {@code Integer} tag numbers to {@code TIFFField}s
 127      * for fields which are not low tag numbered.
 128      */
 129     private Map<Integer,TIFFField> highFields = new TreeMap<Integer,TIFFField>();
 130 
 131     /**
 132      * Creates a {@code TIFFDirectory} instance from the contents of
 133      * an image metadata object. The supplied object must support an image
 134      * metadata format supported by the TIFF {@link javax.imageio.ImageWriter}
 135      * plug-in. This will usually be either the TIFF native image metadata
 136      * format {@code javax_imageio_tiff_image_1.0} or the Java
 137      * Image I/O standard metadata format {@code javax_imageio_1.0}.
 138      *
 139      * @param tiffImageMetadata A metadata object which supports a compatible
 140      * image metadata format.
 141      *
 142      * @return A {@code TIFFDirectory} populated from the contents of
 143      * the supplied metadata object.
 144      *
 145      * @throws NullPointerException if {@code tiffImageMetadata}
 146      * is {@code null}.
 147      * @throws IllegalArgumentException if {@code tiffImageMetadata}
 148      * does not support a compatible image metadata format.
 149      * @throws IIOInvalidTreeException if the supplied metadata object
 150      * cannot be parsed.
 151      */
 152     public static TIFFDirectory
 153         createFromMetadata(IIOMetadata tiffImageMetadata)
 154         throws IIOInvalidTreeException {
 155 
 156         if(tiffImageMetadata == null) {
 157             throw new NullPointerException("tiffImageMetadata == null");
 158         }
 159 
 160         TIFFImageMetadata tim;
 161         if(tiffImageMetadata instanceof TIFFImageMetadata) {
 162             tim = (TIFFImageMetadata)tiffImageMetadata;
 163         } else {
 164             // Create a native metadata object.
 165             ArrayList<TIFFTagSet> l = new ArrayList<TIFFTagSet>(1);
 166             l.add(BaselineTIFFTagSet.getInstance());
 167             tim = new TIFFImageMetadata(l);
 168 
 169             // Determine the format name to use.
 170             String formatName = null;
 171             if(TIFFImageMetadata.NATIVE_METADATA_FORMAT_NAME.equals
 172                (tiffImageMetadata.getNativeMetadataFormatName())) {
 173                 formatName = TIFFImageMetadata.NATIVE_METADATA_FORMAT_NAME;
 174             } else {
 175                 String[] extraNames =
 176                     tiffImageMetadata.getExtraMetadataFormatNames();
 177                 if(extraNames != null) {
 178                     for(int i = 0; i < extraNames.length; i++) {
 179                         if(TIFFImageMetadata.NATIVE_METADATA_FORMAT_NAME.equals
 180                            (extraNames[i])) {
 181                             formatName = extraNames[i];
 182                             break;
 183                         }
 184                     }
 185                 }
 186 
 187                 if(formatName == null) {
 188                     if(tiffImageMetadata.isStandardMetadataFormatSupported()) {
 189                         formatName =
 190                             IIOMetadataFormatImpl.standardMetadataFormatName;
 191                     } else {
 192                         throw new IllegalArgumentException
 193                             ("Parameter does not support required metadata format!");
 194                     }
 195                 }
 196             }
 197 
 198             // Set the native metadata object from the tree.
 199             tim.setFromTree(formatName,
 200                             tiffImageMetadata.getAsTree(formatName));
 201         }
 202 
 203         return tim.getRootIFD();
 204     }
 205 
 206     /**
 207      * Constructs a {@code TIFFDirectory} which is aware of a given
 208      * group of {@link TIFFTagSet}s. An optional parent {@link TIFFTag}
 209      * may also be specified.
 210      *
 211      * @param tagSets The {@code TIFFTagSets} associated with this
 212      * directory.
 213      * @param parentTag The parent {@code TIFFTag} of this directory;
 214      * may be {@code null}.
 215      * @throws NullPointerException if {@code tagSets} is
 216      * {@code null}.
 217      */
 218     public TIFFDirectory(TIFFTagSet[] tagSets, TIFFTag parentTag) {
 219         if(tagSets == null) {
 220             throw new NullPointerException("tagSets == null!");
 221         }
 222         this.tagSets = new ArrayList<TIFFTagSet>(tagSets.length);
 223         int numTagSets = tagSets.length;
 224         for(int i = 0; i < numTagSets; i++) {
 225             this.tagSets.add(tagSets[i]);
 226         }
 227         this.parentTag = parentTag;
 228     }
 229 
 230     /**
 231      * Returns the {@link TIFFTagSet}s of which this directory is aware.
 232      *
 233      * @return The {@code TIFFTagSet}s associated with this
 234      * {@code TIFFDirectory}.
 235      */
 236     public TIFFTagSet[] getTagSets() {
 237         return tagSets.toArray(new TIFFTagSet[tagSets.size()]);
 238     }
 239 
 240     /**
 241      * Adds an element to the group of {@link TIFFTagSet}s of which this
 242      * directory is aware.
 243      *
 244      * @param tagSet The {@code TIFFTagSet} to add.
 245      * @throws NullPointerException if {@code tagSet} is
 246      * {@code null}.
 247      */
 248     public void addTagSet(TIFFTagSet tagSet) {
 249         if(tagSet == null) {
 250             throw new NullPointerException("tagSet == null");
 251         }
 252 
 253         if(!tagSets.contains(tagSet)) {
 254             tagSets.add(tagSet);
 255         }
 256     }
 257 
 258     /**
 259      * Removes an element from the group of {@link TIFFTagSet}s of which this
 260      * directory is aware.
 261      *
 262      * @param tagSet The {@code TIFFTagSet} to remove.
 263      * @throws NullPointerException if {@code tagSet} is
 264      * {@code null}.
 265      */
 266     public void removeTagSet(TIFFTagSet tagSet) {
 267         if(tagSet == null) {
 268             throw new NullPointerException("tagSet == null");
 269         }
 270 
 271         if(tagSets.contains(tagSet)) {
 272             tagSets.remove(tagSet);
 273         }
 274     }
 275 
 276     /**
 277      * Returns the parent {@link TIFFTag} of this directory if one
 278      * has been defined or {@code null} otherwise.
 279      *
 280      * @return The parent {@code TIFFTag} of this
 281      * {@code TIFFDiectory} or {@code null}.
 282      */
 283     public TIFFTag getParentTag() {
 284         return parentTag;
 285     }
 286 
 287     /**
 288      * Returns the {@link TIFFTag} which has tag number equal to
 289      * {@code tagNumber} or {@code null} if no such tag
 290      * exists in the {@link TIFFTagSet}s associated with this
 291      * directory.
 292      *
 293      * @param tagNumber The tag number of interest.
 294      * @return The corresponding {@code TIFFTag} or {@code null}.
 295      */
 296     public TIFFTag getTag(int tagNumber) {
 297         return TIFFIFD.getTag(tagNumber, tagSets);
 298     }
 299 
 300     /**
 301      * Returns the number of {@link TIFFField}s in this directory.
 302      *
 303      * @return The number of {@code TIFFField}s in this
 304      * {@code TIFFDirectory}.
 305      */
 306     public int getNumTIFFFields() {
 307         return numLowFields + highFields.size();
 308     }
 309 
 310     /**
 311      * Determines whether a TIFF field with the given tag number is
 312      * contained in this directory.
 313      *
 314      * @param tagNumber The tag number.
 315      * @return Whether a {@link TIFFTag} with tag number equal to
 316      * {@code tagNumber} is present in this {@code TIFFDirectory}.
 317      */
 318     public boolean containsTIFFField(int tagNumber) {
 319         return (tagNumber >= 0 && tagNumber <= MAX_LOW_FIELD_TAG_NUM &&
 320                 lowFields[tagNumber] != null) ||
 321             highFields.containsKey(Integer.valueOf(tagNumber));
 322     }
 323 
 324     /**
 325      * Adds a TIFF field to the directory.
 326      *
 327      * @param f The field to add.
 328      * @throws NullPointerException if {@code f} is {@code null}.
 329      */
 330     public void addTIFFField(TIFFField f) {
 331         if(f == null) {
 332             throw new NullPointerException("f == null");
 333         }
 334         int tagNumber = f.getTagNumber();
 335         if(tagNumber >= 0 && tagNumber <= MAX_LOW_FIELD_TAG_NUM) {
 336             if(lowFields[tagNumber] == null) {
 337                 numLowFields++;
 338             }
 339             lowFields[tagNumber] = f;
 340         } else {
 341             highFields.put(Integer.valueOf(tagNumber), f);
 342         }
 343     }
 344 
 345     /**
 346      * Retrieves a TIFF field from the directory.
 347      *
 348      * @param tagNumber The tag number of the tag associated with the field.
 349      * @return A {@code TIFFField} with the requested tag number of
 350      * {@code null} if no such field is present.
 351      */
 352     public TIFFField getTIFFField(int tagNumber) {
 353         TIFFField f;
 354         if(tagNumber >= 0 && tagNumber <= MAX_LOW_FIELD_TAG_NUM) {
 355             f = lowFields[tagNumber];
 356         } else {
 357             f = highFields.get(Integer.valueOf(tagNumber));
 358         }
 359         return f;
 360     }
 361 
 362     /**
 363      * Removes a TIFF field from the directory.
 364      *
 365      * @param tagNumber The tag number of the tag associated with the field.
 366      */
 367     public void removeTIFFField(int tagNumber) {
 368         if(tagNumber >= 0 && tagNumber <= MAX_LOW_FIELD_TAG_NUM) {
 369             if(lowFields[tagNumber] != null) {
 370                 numLowFields--;
 371                 lowFields[tagNumber] = null;
 372             }
 373         } else {
 374             highFields.remove(Integer.valueOf(tagNumber));
 375         }
 376     }
 377 
 378     /**
 379      * Retrieves all TIFF fields from the directory.
 380      *
 381      * @return An array of all TIFF fields in order of numerically increasing
 382      * tag number.
 383      */
 384     public TIFFField[] getTIFFFields() {
 385         // Allocate return value.
 386         TIFFField[] fields = new TIFFField[numLowFields + highFields.size()];
 387 
 388         // Copy any low-index fields.
 389         int nextIndex = 0;
 390         for(int i = 0; i <= MAX_LOW_FIELD_TAG_NUM; i++) {
 391             if(lowFields[i] != null) {
 392                 fields[nextIndex++] = lowFields[i];
 393                 if(nextIndex == numLowFields) break;
 394             }
 395         }
 396 
 397         // Copy any high-index fields.
 398         if(!highFields.isEmpty()) {
 399             Iterator<Integer> keys = highFields.keySet().iterator();
 400             while(keys.hasNext()) {
 401                 fields[nextIndex++] = highFields.get(keys.next());
 402             }
 403         }
 404 
 405         return fields;
 406     }
 407 
 408     /**
 409      * Removes all TIFF fields from the directory.
 410      */
 411     public void removeTIFFFields() {
 412         Arrays.fill(lowFields, (Object)null);
 413         numLowFields = 0;
 414         highFields.clear();
 415     }
 416 
 417     /**
 418      * Converts the directory to a metadata object.
 419      *
 420      * @return A metadata instance initialized from the contents of this
 421      * {@code TIFFDirectory}.
 422      */
 423     public IIOMetadata getAsMetadata() {
 424         return new TIFFImageMetadata(TIFFIFD.getDirectoryAsIFD(this));
 425     }
 426 
 427     /**
 428      * Clones the directory and all the fields contained therein.
 429      *
 430      * @return A clone of this {@code TIFFDirectory}.
 431      * @throws CloneNotSupportedException if the instance cannot be cloned.
 432      */
 433     @Override
 434     public TIFFDirectory clone() throws CloneNotSupportedException {
 435         TIFFDirectory dir = (TIFFDirectory) super.clone();
 436         dir.tagSets = new ArrayList<TIFFTagSet>(tagSets);
 437         dir.parentTag = getParentTag();
 438         TIFFField[] fields = getTIFFFields();
 439         for(TIFFField field : fields) {
 440             dir.addTIFFField(field.clone());
 441         }
 442 
 443         return dir;
 444     }
 445 }