1 /*
   2  * Copyright (c) 2005, 2015, 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</code> 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</code> 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()</code> methods of {@link javax.imageio.ImageWriter} may be
  56  * created from a <code>TIFFDirectory</code> by {@link #getAsMetadata()}.
  57  * The <code>TIFFDirectory</code> itself may be created by construction or
  58  * from the <code>IIOMetadata</code> object returned by
  59  * {@link javax.imageio.ImageWriter#getDefaultImageMetadata
  60  * ImageWriter.getDefaultImageMetadata()}. The <code>TIFFField</code>s in the
  61  * directory may be set using the mutator methods provided in this class.</p>
  62  *
  63  * <p>A <code>TIFFDirectory</code> is aware of the tag numbers in the
  64  * group of {@link TIFFTagSet}s associated with it. When
  65  * a <code>TIFFDirectory</code> is created from a native image metadata
  66  * object, these tag sets are derived from the <tt>tagSets</tt> attribute
  67  * of the <tt>TIFFIFD</tt> node.</p>
  68  *
  69  * <p>A <code>TIFFDirectory</code> 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</code>. The
  73  * {@link TIFFTag#isIFDPointer} method of this parent <code>TIFFTag</code>
  74  * must return <code>true</code>.  When a <code>TIFFDirectory</code> is
  75  * created from a native image metadata object, the parent tag set is set
  76  * from the <tt>parentTagName</tt> attribute of the corresponding
  77  * <tt>TIFFIFD</tt> node. Note that a <code>TIFFDirectory</code> instance
  78  * which has a non-<code>null</code> parent tag will be contained in the
  79  * data field of a <code>TIFFField</code> 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</code>
  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</code> corresponding to this Exif IFD will be
  87  * contained in the data field of a <code>TIFFField</code> which will in turn
  88  * be contained in the <code>TIFFDirectory</code> corresponding to the primary
  89  * IFD of the Exif image which will itself have a <code>null</code>-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</code> instance concurrently, and at
  94  * least one of the threads modifies the directory, for example, by adding
  95  * or removing <code>TIFFField</code>s or <code>TIFFTagSet</code>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</code> associated with this directory. */
 111     private List<TIFFTagSet> tagSets;
 112 
 113     /** The parent <code>TIFFTag</code> 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</code> tag numbers to <code>TIFFField</code>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</code> 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 <tt>javax_imageio_tiff_image_1.0</tt> or the Java
 137      * Image I/O standard metadata format <tt>javax_imageio_1.0</tt>.
 138      *
 139      * @param tiffImageMetadata A metadata object which supports a compatible
 140      * image metadata format.
 141      *
 142      * @return A <code>TIFFDirectory</code> populated from the contents of
 143      * the supplied metadata object.
 144      *
 145      * @throws NullPointerException if <code>tiffImageMetadata</code>
 146      * is <code>null</code>.
 147      * @throws IllegalArgumentException if <code>tiffImageMetadata</code>
 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      * Converts a <code>TIFFDirectory</code> to a <code>TIFFIFD</code>.
 208      */
 209     private static TIFFIFD getDirectoryAsIFD(TIFFDirectory dir) {
 210         if(dir instanceof TIFFIFD) {
 211             return (TIFFIFD)dir;
 212         }
 213 
 214         TIFFIFD ifd = new TIFFIFD(Arrays.asList(dir.getTagSets()),
 215                                   dir.getParentTag());
 216         TIFFField[] fields = dir.getTIFFFields();
 217         int numFields = fields.length;
 218         for(int i = 0; i < numFields; i++) {
 219             TIFFField f = fields[i];
 220             TIFFTag tag = f.getTag();
 221             if(tag.isIFDPointer()) {
 222                 TIFFDirectory subIFD =
 223                     getDirectoryAsIFD((TIFFDirectory)f.getData());
 224                 f = new TIFFField(tag, f.getType(), (long)f.getCount(), subIFD);
 225             }
 226             ifd.addTIFFField(f);
 227         }
 228 
 229         return ifd;
 230     }
 231 
 232     /**
 233      * Constructs a <code>TIFFDirectory</code> which is aware of a given
 234      * group of {@link TIFFTagSet}s. An optional parent {@link TIFFTag}
 235      * may also be specified.
 236      *
 237      * @param tagSets The <code>TIFFTagSets</code> associated with this
 238      * directory.
 239      * @param parentTag The parent <code>TIFFTag</code> of this directory;
 240      * may be <code>null</code>.
 241      * @throws NullPointerException if <code>tagSets</code> is
 242      * <code>null</code>.
 243      */
 244     public TIFFDirectory(TIFFTagSet[] tagSets, TIFFTag parentTag) {
 245         if(tagSets == null) {
 246             throw new NullPointerException("tagSets == null!");
 247         }
 248         this.tagSets = new ArrayList<TIFFTagSet>(tagSets.length);
 249         int numTagSets = tagSets.length;
 250         for(int i = 0; i < numTagSets; i++) {
 251             this.tagSets.add(tagSets[i]);
 252         }
 253         this.parentTag = parentTag;
 254     }
 255 
 256     /**
 257      * Returns the {@link TIFFTagSet}s of which this directory is aware.
 258      *
 259      * @return The <code>TIFFTagSet</code>s associated with this
 260      * <code>TIFFDirectory</code>.
 261      */
 262     public TIFFTagSet[] getTagSets() {
 263         return tagSets.toArray(new TIFFTagSet[tagSets.size()]);
 264     }
 265 
 266     /**
 267      * Adds an element to the group of {@link TIFFTagSet}s of which this
 268      * directory is aware.
 269      *
 270      * @param tagSet The <code>TIFFTagSet</code> to add.
 271      * @throws NullPointerException if <code>tagSet</code> is
 272      * <code>null</code>.
 273      */
 274     public void addTagSet(TIFFTagSet tagSet) {
 275         if(tagSet == null) {
 276             throw new NullPointerException("tagSet == null");
 277         }
 278 
 279         if(!tagSets.contains(tagSet)) {
 280             tagSets.add(tagSet);
 281         }
 282     }
 283 
 284     /**
 285      * Removes an element from the group of {@link TIFFTagSet}s of which this
 286      * directory is aware.
 287      *
 288      * @param tagSet The <code>TIFFTagSet</code> to remove.
 289      * @throws NullPointerException if <code>tagSet</code> is
 290      * <code>null</code>.
 291      */
 292     public void removeTagSet(TIFFTagSet tagSet) {
 293         if(tagSet == null) {
 294             throw new NullPointerException("tagSet == null");
 295         }
 296 
 297         if(tagSets.contains(tagSet)) {
 298             tagSets.remove(tagSet);
 299         }
 300     }
 301 
 302     /**
 303      * Returns the parent {@link TIFFTag} of this directory if one
 304      * has been defined or <code>null</code> otherwise.
 305      *
 306      * @return The parent <code>TIFFTag</code> of this
 307      * <code>TIFFDiectory</code> or <code>null</code>.
 308      */
 309     public TIFFTag getParentTag() {
 310         return parentTag;
 311     }
 312 
 313     /**
 314      * Returns the {@link TIFFTag} which has tag number equal to
 315      * <code>tagNumber</code> or <code>null</code> if no such tag
 316      * exists in the {@link TIFFTagSet}s associated with this
 317      * directory.
 318      *
 319      * @param tagNumber The tag number of interest.
 320      * @return The corresponding <code>TIFFTag</code> or <code>null</code>.
 321      */
 322     public TIFFTag getTag(int tagNumber) {
 323         return TIFFIFD.getTag(tagNumber, tagSets);
 324     }
 325 
 326     /**
 327      * Returns the number of {@link TIFFField}s in this directory.
 328      *
 329      * @return The number of <code>TIFFField</code>s in this
 330      * <code>TIFFDirectory</code>.
 331      */
 332     public int getNumTIFFFields() {
 333         return numLowFields + highFields.size();
 334     }
 335 
 336     /**
 337      * Determines whether a TIFF field with the given tag number is
 338      * contained in this directory.
 339      *
 340      * @param tagNumber The tag number.
 341      * @return Whether a {@link TIFFTag} with tag number equal to
 342      * <code>tagNumber</code> is present in this <code>TIFFDirectory</code>.
 343      */
 344     public boolean containsTIFFField(int tagNumber) {
 345         return (tagNumber >= 0 && tagNumber <= MAX_LOW_FIELD_TAG_NUM &&
 346                 lowFields[tagNumber] != null) ||
 347             highFields.containsKey(Integer.valueOf(tagNumber));
 348     }
 349 
 350     /**
 351      * Adds a TIFF field to the directory.
 352      *
 353      * @param f The field to add.
 354      * @throws NullPointerException if <code>f</code> is <code>null</code>.
 355      */
 356     public void addTIFFField(TIFFField f) {
 357         if(f == null) {
 358             throw new NullPointerException("f == null");
 359         }
 360         int tagNumber = f.getTagNumber();
 361         if(tagNumber >= 0 && tagNumber <= MAX_LOW_FIELD_TAG_NUM) {
 362             if(lowFields[tagNumber] == null) {
 363                 numLowFields++;
 364             }
 365             lowFields[tagNumber] = f;
 366         } else {
 367             highFields.put(Integer.valueOf(tagNumber), f);
 368         }
 369     }
 370 
 371     /**
 372      * Retrieves a TIFF field from the directory.
 373      *
 374      * @param tagNumber The tag number of the tag associated with the field.
 375      * @return A <code>TIFFField</code> with the requested tag number of
 376      * <code>null</code> if no such field is present.
 377      */
 378     public TIFFField getTIFFField(int tagNumber) {
 379         TIFFField f;
 380         if(tagNumber >= 0 && tagNumber <= MAX_LOW_FIELD_TAG_NUM) {
 381             f = lowFields[tagNumber];
 382         } else {
 383             f = highFields.get(Integer.valueOf(tagNumber));
 384         }
 385         return f;
 386     }
 387 
 388     /**
 389      * Removes a TIFF field from the directory.
 390      *
 391      * @param tagNumber The tag number of the tag associated with the field.
 392      */
 393     public void removeTIFFField(int tagNumber) {
 394         if(tagNumber >= 0 && tagNumber <= MAX_LOW_FIELD_TAG_NUM) {
 395             if(lowFields[tagNumber] != null) {
 396                 numLowFields--;
 397                 lowFields[tagNumber] = null;
 398             }
 399         } else {
 400             highFields.remove(Integer.valueOf(tagNumber));
 401         }
 402     }
 403 
 404     /**
 405      * Retrieves all TIFF fields from the directory.
 406      *
 407      * @return An array of all TIFF fields in order of numerically increasing
 408      * tag number.
 409      */
 410     public TIFFField[] getTIFFFields() {
 411         // Allocate return value.
 412         TIFFField[] fields = new TIFFField[numLowFields + highFields.size()];
 413 
 414         // Copy any low-index fields.
 415         int nextIndex = 0;
 416         for(int i = 0; i <= MAX_LOW_FIELD_TAG_NUM; i++) {
 417             if(lowFields[i] != null) {
 418                 fields[nextIndex++] = lowFields[i];
 419                 if(nextIndex == numLowFields) break;
 420             }
 421         }
 422 
 423         // Copy any high-index fields.
 424         if(!highFields.isEmpty()) {
 425             Iterator<Integer> keys = highFields.keySet().iterator();
 426             while(keys.hasNext()) {
 427                 fields[nextIndex++] = highFields.get(keys.next());
 428             }
 429         }
 430 
 431         return fields;
 432     }
 433 
 434     /**
 435      * Removes all TIFF fields from the directory.
 436      */
 437     public void removeTIFFFields() {
 438         Arrays.fill(lowFields, (Object)null);
 439         numLowFields = 0;
 440         highFields.clear();
 441     }
 442 
 443     /**
 444      * Converts the directory to a metadata object.
 445      *
 446      * @return A metadata instance initialized from the contents of this
 447      * <code>TIFFDirectory</code>.
 448      */
 449     public IIOMetadata getAsMetadata() {
 450         return new TIFFImageMetadata(getDirectoryAsIFD(this));
 451     }
 452 
 453     /**
 454      * Clones the directory and all the fields contained therein.
 455      *
 456      * @return A clone of this <code>TIFFDirectory</code>.
 457      * @throws CloneNotSupportedException if the instance cannot be cloned.
 458      */
 459     @Override
 460     public TIFFDirectory clone() throws CloneNotSupportedException {
 461         TIFFDirectory dir = (TIFFDirectory) super.clone();
 462         dir.tagSets = new ArrayList<TIFFTagSet>(tagSets);
 463         dir.parentTag = getParentTag();
 464         TIFFField[] fields = getTIFFFields();
 465         for(TIFFField field : fields) {
 466             dir.addTIFFField(field.clone());
 467         }
 468 
 469         return dir;
 470     }
 471 }