001/*
002 * $RCSfile: TIFFDirectory.java,v $
003 *
004 * 
005 * Copyright (c) 2006 Sun Microsystems, Inc. All  Rights Reserved.
006 * 
007 * Redistribution and use in source and binary forms, with or without
008 * modification, are permitted provided that the following conditions
009 * are met: 
010 * 
011 * - Redistribution of source code must retain the above copyright 
012 *   notice, this  list of conditions and the following disclaimer.
013 * 
014 * - Redistribution in binary form must reproduce the above copyright
015 *   notice, this list of conditions and the following disclaimer in 
016 *   the documentation and/or other materials provided with the
017 *   distribution.
018 * 
019 * Neither the name of Sun Microsystems, Inc. or the names of 
020 * contributors may be used to endorse or promote products derived 
021 * from this software without specific prior written permission.
022 * 
023 * This software is provided "AS IS," without a warranty of any 
024 * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND 
025 * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, 
026 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY
027 * EXCLUDED. SUN MIDROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL 
028 * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF 
029 * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
030 * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR 
031 * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL,
032 * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND
033 * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
034 * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
035 * POSSIBILITY OF SUCH DAMAGES. 
036 * 
037 * You acknowledge that this software is not designed or intended for 
038 * use in the design, construction, operation or maintenance of any 
039 * nuclear facility. 
040 *
041 * $Revision: 1.4 $
042 * $Date: 2006/08/25 00:16:49 $
043 * $State: Exp $
044 */
045package com.github.jaiimageio.plugins.tiff;
046
047import java.util.ArrayList;
048import java.util.Arrays;
049import java.util.Iterator;
050import java.util.List;
051import java.util.Map;
052import java.util.NoSuchElementException;
053import java.util.Set;
054import java.util.TreeMap;
055
056import javax.imageio.metadata.IIOInvalidTreeException;
057import javax.imageio.metadata.IIOMetadata;
058import javax.imageio.metadata.IIOMetadataFormatImpl;
059
060import org.w3c.dom.Node;
061
062import com.github.jaiimageio.impl.plugins.tiff.TIFFIFD;
063import com.github.jaiimageio.impl.plugins.tiff.TIFFImageMetadata;
064
065/**
066 * A convenience class for simplifying interaction with TIFF native
067 * image metadata. A TIFF image metadata tree represents an Image File
068 * Directory (IFD) from a TIFF 6.0 stream. An IFD consists of a number of
069 * IFD Entries each of which associates an identifying tag number with
070 * a compatible value. A <code>TIFFDirectory</code> instance corresponds
071 * to an IFD and contains a set of {@link TIFFField}s each of which
072 * corresponds to an IFD Entry in the IFD.
073 *
074 * <p>When reading, a <code>TIFFDirectory</code> may be created by passing
075 * the value returned by {@link javax.imageio.ImageReader#getImageMetadata
076 * ImageReader.getImageMetadata()} to {@link #createFromMetadata
077 * createFromMetadata()}. The {@link TIFFField}s in the directory may then
078 * be obtained using the accessor methods provided in this class.</p>
079 *
080 * <p>When writing, an {@link IIOMetadata} object for use by one of the
081 * <core>write()</code> methods of {@link javax.imageio.ImageWriter} may be
082 * created from a <code>TIFFDirectory</code> by {@link #getAsMetadata()}.
083 * The <code>TIFFDirectory</code> itself may be created by construction or
084 * from the <code>IIOMetadata</code> object returned by
085 * {@link javax.imageio.ImageWriter#getDefaultImageMetadata
086 * ImageWriter.getDefaultImageMetadata()}. The <code>TIFFField</code>s in the
087 * directory may be set using the mutator methods provided in this class.</p>
088 *
089 * <p>A <code>TIFFDirectory</code> is aware of the tag numbers in the
090 * group of {@link TIFFTagSet}s associated with it. When
091 * a <code>TIFFDirectory</code> is created from a native image metadata
092 * object, these tag sets are derived from the <tt>tagSets</tt> attribute
093 * of the <tt>TIFFIFD</tt> node.</p>
094 *
095 * <p>A <code>TIFFDirectory</code> might also have a parent {@link TIFFTag}.
096 * This will occur if the directory represents an IFD other than the root
097 * IFD of the image. The parent tag is the tag of the IFD Entry which is a
098 * pointer to the IFD represented by this <code>TIFFDirectory</code>. The
099 * {@link TIFFTag#isIFDPointer} method of this parent <code>TIFFTag</code>
100 * must return <code>true</code>.  When a <code>TIFFDirectory</code> is
101 * created from a native image metadata object, the parent tag set is set
102 * from the <tt>parentTagName</tt> attribute of the corresponding
103 * <tt>TIFFIFD</tt> node. Note that a <code>TIFFDirectory</code> instance
104 * which has a non-<code>null</code> parent tag will be contained in the
105 * data field of a <code>TIFFField</code> instance which has a tag field
106 * equal to the contained directory's parent tag.</p>
107 * 
108 * <p>As an example consider an EXIF image. The <code>TIFFDirectory</code>
109 * instance corresponding to the EXIF IFD in the EXIF stream would have parent
110 * tag {@link EXIFParentTIFFTagSet#TAG_EXIF_IFD_POINTER TAG_EXIF_IFD_POINTER}
111 * and would include {@link EXIFTIFFTagSet} in its group of known tag sets.
112 * The <code>TIFFDirectory</code> corresponding to this EXIF IFD will be
113 * contained in the data field of a <code>TIFFField</code> which will in turn
114 * be contained in the <code>TIFFDirectory</code> corresponding to the primary
115 * IFD of the EXIF image which will itself have a <code>null</code>-valued
116 * parent tag.</p>
117 *
118 * <p><b>Note that this implementation is not synchronized.</b> If multiple
119 * threads use a <code>TIFFDirectory</code> instance concurrently, and at
120 * least one of the threads modifies the directory, for example, by adding
121 * or removing <code>TIFFField</code>s or <code>TIFFTagSet</code>s, it
122 * <i>must</i> be synchronized externally.</p>
123 *
124 * @see IIOMetadata
125 * @see TIFFField
126 * @see TIFFTag
127 * @see TIFFTagSet
128 *
129 * @since 1.1-beta
130 */
131// XXX doc: not thread safe
132public class TIFFDirectory implements Cloneable {
133
134    /** The largest low-valued tag number in the TIFF 6.0 specification. */
135    private static final int MAX_LOW_FIELD_TAG_NUM =
136        BaselineTIFFTagSet.TAG_REFERENCE_BLACK_WHITE;
137
138    /** The <code>TIFFTagSets</code> associated with this directory. */
139    private List tagSets;
140
141    /** The parent <code>TIFFTag</code> of this directory. */
142    private TIFFTag parentTag;
143
144    /**
145     * The fields in this directory which have a low tag number. These are
146     * managed as an array for efficiency as they are the most common fields.
147     */
148    private TIFFField[] lowFields = new TIFFField[MAX_LOW_FIELD_TAG_NUM + 1];
149
150    /** The number of low tag numbered fields in the directory. */
151    private int numLowFields = 0;
152
153    /**
154     * A mapping of <code>Integer</code> tag numbers to <code>TIFFField</code>s
155     * for fields which are not low tag numbered.
156     */
157    private Map highFields = new TreeMap();
158
159    /**
160     * Creates a <code>TIFFDirectory</code> instance from the contents of
161     * an image metadata object. The supplied object must support an image
162     * metadata format supported by the TIFF {@link javax.imageio.ImageWriter}
163     * plug-in. This will usually be either the TIFF native image metadata
164     * format <tt>com_sun_media_imageio_plugins_tiff_1.0</tt> or the Java
165     * Image I/O standard metadata format <tt>javax_imageio_1.0</tt>.
166     *
167     * @param tiffImageMetadata A metadata object which supports a compatible
168     * image metadata format.
169     * 
170     * @return A <code>TIFFDirectory</code> populated from the contents of
171     * the supplied metadata object.
172     *
173     * @throws IllegalArgumentException if <code>tiffImageMetadata</code>
174     * is <code>null</code>.
175     * @throws IllegalArgumentException if <code>tiffImageMetadata</code>
176     * does not support a compatible image metadata format.
177     * @throws IIOInvalidTreeException if the supplied metadata object
178     * cannot be parsed.
179     */
180    public static TIFFDirectory
181        createFromMetadata(IIOMetadata tiffImageMetadata)
182        throws IIOInvalidTreeException {
183
184        if(tiffImageMetadata == null) {
185            throw new IllegalArgumentException("tiffImageMetadata == null");
186        }
187
188        TIFFImageMetadata tim;
189        if(tiffImageMetadata instanceof TIFFImageMetadata) {
190            tim = (TIFFImageMetadata)tiffImageMetadata;
191        } else {
192            // Create a native metadata object.
193            ArrayList l = new ArrayList(1);
194            l.add(BaselineTIFFTagSet.getInstance());
195            tim = new TIFFImageMetadata(l);
196
197            // Determine the format name to use.
198            String formatName = null;
199            if(TIFFImageMetadata.nativeMetadataFormatName.equals
200               (tiffImageMetadata.getNativeMetadataFormatName())) {
201                formatName = TIFFImageMetadata.nativeMetadataFormatName;
202            } else {
203                String[] extraNames =
204                    tiffImageMetadata.getExtraMetadataFormatNames();
205                if(extraNames != null) {
206                    for(int i = 0; i < extraNames.length; i++) {
207                        if(TIFFImageMetadata.nativeMetadataFormatName.equals
208                           (extraNames[i])) {
209                            formatName = extraNames[i];
210                            break;
211                        }
212                    }
213                }
214
215                if(formatName == null) {
216                    if(tiffImageMetadata.isStandardMetadataFormatSupported()) {
217                        formatName =
218                            IIOMetadataFormatImpl.standardMetadataFormatName;
219                    } else {
220                        throw new IllegalArgumentException
221                            ("Parameter does not support required metadata format!");
222                    }
223                }
224            }
225
226            // Set the native metadata object from the tree.
227            tim.setFromTree(formatName,
228                            tiffImageMetadata.getAsTree(formatName));
229        }
230
231        return tim.getRootIFD();
232    }
233
234    /**
235     * Converts a <code>TIFFDirectory</code> to a <code>TIFFIFD</code>.
236     */
237    private static TIFFIFD getDirectoryAsIFD(TIFFDirectory dir) {
238        if(dir instanceof TIFFIFD) {
239            return (TIFFIFD)dir;
240        }
241
242        TIFFIFD ifd = new TIFFIFD(Arrays.asList(dir.getTagSets()),
243                                  dir.getParentTag());
244        TIFFField[] fields = dir.getTIFFFields();
245        int numFields = fields.length;
246        for(int i = 0; i < numFields; i++) {
247            TIFFField f = fields[i];
248            TIFFTag tag = f.getTag();
249            if(tag.isIFDPointer()) {
250                TIFFDirectory subIFD =
251                    getDirectoryAsIFD((TIFFDirectory)f.getData());
252                f = new TIFFField(tag, f.getType(), f.getCount(), subIFD);
253            }
254            ifd.addTIFFField(f);
255        }
256
257        return ifd;
258    }
259
260    /**
261     * Constructs a <code>TIFFDirectory</code> which is aware of a given
262     * group of {@link TIFFTagSet}s. An optional parent {@link TIFFTag}
263     * may also be specified.
264     *
265     * @param tagSets The <code>TIFFTagSets</code> associated with this
266     * directory.
267     * @param parentTag The parent <code>TIFFTag</code> of this directory;
268     * may be <code>null</code>.
269     * @throws IllegalArgumentException if <code>tagSets</code> is
270     * <code>null</code>.
271     */
272    public TIFFDirectory(TIFFTagSet[] tagSets, TIFFTag parentTag) {
273        if(tagSets == null) {
274            throw new IllegalArgumentException("tagSets == null!");
275        }
276        this.tagSets = new ArrayList(tagSets.length);
277        int numTagSets = tagSets.length;
278        for(int i = 0; i < numTagSets; i++) {
279            this.tagSets.add(tagSets[i]);
280        }
281        this.parentTag = parentTag;
282    }
283
284    /**
285     * Returns the {@link TIFFTagSet}s of which this directory is aware.
286     *
287     * @return The <code>TIFFTagSet</code>s associated with this
288     * <code>TIFFDirectory</code>.
289     */
290    public TIFFTagSet[] getTagSets() {
291        return (TIFFTagSet[])tagSets.toArray(new TIFFTagSet[tagSets.size()]);
292    }
293
294    /**
295     * Adds an element to the group of {@link TIFFTagSet}s of which this
296     * directory is aware.
297     *
298     * @param tagSet The <code>TIFFTagSet</code> to add.
299     * @throws IllegalArgumentException if <code>tagSet</code> is
300     * <code>null</code>.
301     */
302    public void addTagSet(TIFFTagSet tagSet) {
303        if(tagSet == null) {
304            throw new IllegalArgumentException("tagSet == null");
305        }
306
307        if(!tagSets.contains(tagSet)) {
308            tagSets.add(tagSet);
309        }
310    }
311
312    /**
313     * Removes an element from the group of {@link TIFFTagSet}s of which this
314     * directory is aware.
315     *
316     * @param tagSet The <code>TIFFTagSet</code> to remove.
317     * @throws IllegalArgumentException if <code>tagSet</code> is
318     * <code>null</code>.
319     */
320    public void removeTagSet(TIFFTagSet tagSet) {
321        if(tagSet == null) {
322            throw new IllegalArgumentException("tagSet == null");
323        }
324
325        if(tagSets.contains(tagSet)) {
326            tagSets.remove(tagSet);
327        }
328    }
329
330    /**
331     * Returns the parent {@link TIFFTag} of this directory if one
332     * has been defined or <code>null</code> otherwise.
333     *
334     * @return The parent <code>TIFFTag</code> of this
335     * <code>TIFFDiectory</code> or <code>null</code>.
336     */
337    public TIFFTag getParentTag() {
338        return parentTag;
339    }
340
341    /**
342     * Returns the {@link TIFFTag} which has tag number equal to
343     * <code>tagNumber</code> or <code>null</code> if no such tag
344     * exists in the {@link TIFFTagSet}s associated with this
345     * directory.
346     *
347     * @param tagNumber The tag number of interest.
348     * @return The corresponding <code>TIFFTag</code> or <code>null</code>.
349     */
350    public TIFFTag getTag(int tagNumber) {
351        return TIFFIFD.getTag(tagNumber, tagSets);
352    }
353
354    /**
355     * Returns the number of {@link TIFFField}s in this directory.
356     *
357     * @return The number of <code>TIFFField</code>s in this
358     * <code>TIFFDirectory</code>.
359     */
360    public int getNumTIFFFields() {
361        return numLowFields + highFields.size();
362    }
363
364    /**
365     * Determines whether a TIFF field with the given tag number is
366     * contained in this directory.
367     *
368     * @return Whether a {@link TIFFTag} with tag number equal to
369     * <code>tagNumber</code> is present in this <code>TIFFDirectory</code>.
370     */
371    public boolean containsTIFFField(int tagNumber) {
372        return (tagNumber >= 0 && tagNumber <= MAX_LOW_FIELD_TAG_NUM &&
373                lowFields[tagNumber] != null) ||
374            highFields.containsKey(new Integer(tagNumber));
375    }
376
377    /**
378     * Adds a TIFF field to the directory.
379     *
380     * @param f The field to add.
381     * @throws IllegalArgumentException if <code>f</code> is <code>null</code>.
382     */
383    public void addTIFFField(TIFFField f) {
384        if(f == null) {
385            throw new IllegalArgumentException("f == null");
386        }
387        int tagNumber = f.getTagNumber();
388        if(tagNumber >= 0 && tagNumber <= MAX_LOW_FIELD_TAG_NUM) {
389            if(lowFields[tagNumber] == null) {
390                numLowFields++;
391            }
392            lowFields[tagNumber] = f;
393        } else {
394            highFields.put(new Integer(tagNumber), f);
395        }
396    }
397
398    /**
399     * Retrieves a TIFF field from the directory.
400     *
401     * @param tagNumber The tag number of the tag associated with the field.
402     * @return A <code>TIFFField</code> with the requested tag number of
403     * <code>null</code> if no such field is present.
404     */
405    public TIFFField getTIFFField(int tagNumber) {
406        TIFFField f;
407        if(tagNumber >= 0 && tagNumber <= MAX_LOW_FIELD_TAG_NUM) {
408            f = lowFields[tagNumber];
409        } else {
410            f = (TIFFField)highFields.get(new Integer(tagNumber));
411        }
412        return f;
413    }
414
415    /**
416     * Removes a TIFF field from the directory.
417     *
418     * @param tagNumber The tag number of the tag associated with the field.
419     */
420    public void removeTIFFField(int tagNumber) {
421        if(tagNumber >= 0 && tagNumber <= MAX_LOW_FIELD_TAG_NUM) {
422            if(lowFields[tagNumber] != null) {
423                numLowFields--;
424                lowFields[tagNumber] = null;
425            }
426        } else {
427            highFields.remove(new Integer(tagNumber));
428        }
429    }
430
431    /**
432     * Retrieves all TIFF fields from the directory.
433     *
434     * @return An array of all TIFF fields in order of numerically increasing
435     * tag number.
436     */
437    public TIFFField[] getTIFFFields() {
438        // Allocate return value.
439        TIFFField[] fields = new TIFFField[numLowFields + highFields.size()];
440
441        // Copy any low-index fields.
442        int nextIndex = 0;
443        for(int i = 0; i <= MAX_LOW_FIELD_TAG_NUM; i++) {
444            if(lowFields[i] != null) {
445                fields[nextIndex++] = lowFields[i];
446                if(nextIndex == numLowFields) break;
447            }
448        }
449
450        // Copy any high-index fields.
451        if(!highFields.isEmpty()) {
452            Iterator keys = highFields.keySet().iterator();
453            while(keys.hasNext()) {
454                fields[nextIndex++] = (TIFFField)highFields.get(keys.next());
455            }
456        }
457
458        return fields;
459    }
460
461    /**
462     * Removes all TIFF fields from the directory.
463     */
464    public void removeTIFFFields() {
465        Arrays.fill(lowFields, (Object)null);
466        numLowFields = 0;
467        highFields.clear();
468    }
469
470    /**
471     * Converts the directory to a metadata object.
472     *
473     * @return A metadata instance initialized from the contents of this
474     * <code>TIFFDirectory</code>.
475     */
476    public IIOMetadata getAsMetadata() {
477        return new TIFFImageMetadata(getDirectoryAsIFD(this));
478    }
479
480    /**
481     * Clones the directory and all the fields contained therein.
482     *
483     * @return A clone of this <code>TIFFDirectory</code>.
484     */
485    public Object clone() {
486        TIFFDirectory dir = new TIFFDirectory(getTagSets(), getParentTag());
487        TIFFField[] fields = getTIFFFields();
488        int numFields = fields.length;
489        for(int i = 0; i < numFields; i++) {
490            dir.addTIFFField(fields[i]);
491        }
492
493        return dir;
494    }
495}