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.TreeMap;
053
054import javax.imageio.metadata.IIOInvalidTreeException;
055import javax.imageio.metadata.IIOMetadata;
056import javax.imageio.metadata.IIOMetadataFormatImpl;
057
058import com.github.jaiimageio.impl.plugins.tiff.TIFFIFD;
059import com.github.jaiimageio.impl.plugins.tiff.TIFFImageMetadata;
060
061/**
062 * A convenience class for simplifying interaction with TIFF native
063 * image metadata. A TIFF image metadata tree represents an Image File
064 * Directory (IFD) from a TIFF 6.0 stream. An IFD consists of a number of
065 * IFD Entries each of which associates an identifying tag number with
066 * a compatible value. A <code>TIFFDirectory</code> instance corresponds
067 * to an IFD and contains a set of {@link TIFFField}s each of which
068 * corresponds to an IFD Entry in the IFD.
069 *
070 * <p>When reading, a <code>TIFFDirectory</code> may be created by passing
071 * the value returned by {@link javax.imageio.ImageReader#getImageMetadata
072 * ImageReader.getImageMetadata()} to {@link #createFromMetadata
073 * createFromMetadata()}. The {@link TIFFField}s in the directory may then
074 * be obtained using the accessor methods provided in this class.</p>
075 *
076 * <p>When writing, an {@link IIOMetadata} object for use by one of the
077 * <core>write()</code> methods of {@link javax.imageio.ImageWriter} may be
078 * created from a <code>TIFFDirectory</code> by {@link #getAsMetadata()}.
079 * The <code>TIFFDirectory</code> itself may be created by construction or
080 * from the <code>IIOMetadata</code> object returned by
081 * {@link javax.imageio.ImageWriter#getDefaultImageMetadata
082 * ImageWriter.getDefaultImageMetadata()}. The <code>TIFFField</code>s in the
083 * directory may be set using the mutator methods provided in this class.</p>
084 *
085 * <p>A <code>TIFFDirectory</code> is aware of the tag numbers in the
086 * group of {@link TIFFTagSet}s associated with it. When
087 * a <code>TIFFDirectory</code> is created from a native image metadata
088 * object, these tag sets are derived from the <tt>tagSets</tt> attribute
089 * of the <tt>TIFFIFD</tt> node.</p>
090 *
091 * <p>A <code>TIFFDirectory</code> might also have a parent {@link TIFFTag}.
092 * This will occur if the directory represents an IFD other than the root
093 * IFD of the image. The parent tag is the tag of the IFD Entry which is a
094 * pointer to the IFD represented by this <code>TIFFDirectory</code>. The
095 * {@link TIFFTag#isIFDPointer} method of this parent <code>TIFFTag</code>
096 * must return <code>true</code>.  When a <code>TIFFDirectory</code> is
097 * created from a native image metadata object, the parent tag set is set
098 * from the <tt>parentTagName</tt> attribute of the corresponding
099 * <tt>TIFFIFD</tt> node. Note that a <code>TIFFDirectory</code> instance
100 * which has a non-<code>null</code> parent tag will be contained in the
101 * data field of a <code>TIFFField</code> instance which has a tag field
102 * equal to the contained directory's parent tag.</p>
103 * 
104 * <p>As an example consider an EXIF image. The <code>TIFFDirectory</code>
105 * instance corresponding to the EXIF IFD in the EXIF stream would have parent
106 * tag {@link EXIFParentTIFFTagSet#TAG_EXIF_IFD_POINTER TAG_EXIF_IFD_POINTER}
107 * and would include {@link EXIFTIFFTagSet} in its group of known tag sets.
108 * The <code>TIFFDirectory</code> corresponding to this EXIF IFD will be
109 * contained in the data field of a <code>TIFFField</code> which will in turn
110 * be contained in the <code>TIFFDirectory</code> corresponding to the primary
111 * IFD of the EXIF image which will itself have a <code>null</code>-valued
112 * parent tag.</p>
113 *
114 * <p><b>Note that this implementation is not synchronized.</b> If multiple
115 * threads use a <code>TIFFDirectory</code> instance concurrently, and at
116 * least one of the threads modifies the directory, for example, by adding
117 * or removing <code>TIFFField</code>s or <code>TIFFTagSet</code>s, it
118 * <i>must</i> be synchronized externally.</p>
119 *
120 * @see IIOMetadata
121 * @see TIFFField
122 * @see TIFFTag
123 * @see TIFFTagSet
124 *
125 * @since 1.1-beta
126 */
127// XXX doc: not thread safe
128public class TIFFDirectory implements Cloneable {
129
130    /** The largest low-valued tag number in the TIFF 6.0 specification. */
131    private static final int MAX_LOW_FIELD_TAG_NUM =
132        BaselineTIFFTagSet.TAG_REFERENCE_BLACK_WHITE;
133
134    /** The <code>TIFFTagSets</code> associated with this directory. */
135    private List tagSets;
136
137    /** The parent <code>TIFFTag</code> of this directory. */
138    private TIFFTag parentTag;
139
140    /**
141     * The fields in this directory which have a low tag number. These are
142     * managed as an array for efficiency as they are the most common fields.
143     */
144    private TIFFField[] lowFields = new TIFFField[MAX_LOW_FIELD_TAG_NUM + 1];
145
146    /** The number of low tag numbered fields in the directory. */
147    private int numLowFields = 0;
148
149    /**
150     * A mapping of <code>Integer</code> tag numbers to <code>TIFFField</code>s
151     * for fields which are not low tag numbered.
152     */
153    private Map highFields = new TreeMap();
154
155    /**
156     * Creates a <code>TIFFDirectory</code> instance from the contents of
157     * an image metadata object. The supplied object must support an image
158     * metadata format supported by the TIFF {@link javax.imageio.ImageWriter}
159     * plug-in. This will usually be either the TIFF native image metadata
160     * format <tt>com_sun_media_imageio_plugins_tiff_1.0</tt> or the Java
161     * Image I/O standard metadata format <tt>javax_imageio_1.0</tt>.
162     *
163     * @param tiffImageMetadata A metadata object which supports a compatible
164     * image metadata format.
165     * 
166     * @return A <code>TIFFDirectory</code> populated from the contents of
167     * the supplied metadata object.
168     *
169     * @throws IllegalArgumentException if <code>tiffImageMetadata</code>
170     * is <code>null</code>.
171     * @throws IllegalArgumentException if <code>tiffImageMetadata</code>
172     * does not support a compatible image metadata format.
173     * @throws IIOInvalidTreeException if the supplied metadata object
174     * cannot be parsed.
175     */
176    public static TIFFDirectory
177        createFromMetadata(IIOMetadata tiffImageMetadata)
178        throws IIOInvalidTreeException {
179
180        if(tiffImageMetadata == null) {
181            throw new IllegalArgumentException("tiffImageMetadata == null");
182        }
183
184        TIFFImageMetadata tim;
185        if(tiffImageMetadata instanceof TIFFImageMetadata) {
186            tim = (TIFFImageMetadata)tiffImageMetadata;
187        } else {
188            // Create a native metadata object.
189            ArrayList l = new ArrayList(1);
190            l.add(BaselineTIFFTagSet.getInstance());
191            tim = new TIFFImageMetadata(l);
192
193            // Determine the format name to use.
194            String formatName = null;
195            if(TIFFImageMetadata.nativeMetadataFormatName.equals
196               (tiffImageMetadata.getNativeMetadataFormatName())) {
197                formatName = TIFFImageMetadata.nativeMetadataFormatName;
198            } else {
199                String[] extraNames =
200                    tiffImageMetadata.getExtraMetadataFormatNames();
201                if(extraNames != null) {
202                    for(int i = 0; i < extraNames.length; i++) {
203                        if(TIFFImageMetadata.nativeMetadataFormatName.equals
204                           (extraNames[i])) {
205                            formatName = extraNames[i];
206                            break;
207                        }
208                    }
209                }
210
211                if(formatName == null) {
212                    if(tiffImageMetadata.isStandardMetadataFormatSupported()) {
213                        formatName =
214                            IIOMetadataFormatImpl.standardMetadataFormatName;
215                    } else {
216                        throw new IllegalArgumentException
217                            ("Parameter does not support required metadata format!");
218                    }
219                }
220            }
221
222            // Set the native metadata object from the tree.
223            tim.setFromTree(formatName,
224                            tiffImageMetadata.getAsTree(formatName));
225        }
226
227        return tim.getRootIFD();
228    }
229
230    /**
231     * Converts a <code>TIFFDirectory</code> to a <code>TIFFIFD</code>.
232     */
233    private static TIFFIFD getDirectoryAsIFD(TIFFDirectory dir) {
234        if(dir instanceof TIFFIFD) {
235            return (TIFFIFD)dir;
236        }
237
238        TIFFIFD ifd = new TIFFIFD(Arrays.asList(dir.getTagSets()),
239                                  dir.getParentTag());
240        TIFFField[] fields = dir.getTIFFFields();
241        int numFields = fields.length;
242        for(int i = 0; i < numFields; i++) {
243            TIFFField f = fields[i];
244            TIFFTag tag = f.getTag();
245            if(tag.isIFDPointer()) {
246                TIFFDirectory subIFD =
247                    getDirectoryAsIFD((TIFFDirectory)f.getData());
248                f = new TIFFField(tag, f.getType(), f.getCount(), subIFD);
249            }
250            ifd.addTIFFField(f);
251        }
252
253        return ifd;
254    }
255
256    /**
257     * Constructs a <code>TIFFDirectory</code> which is aware of a given
258     * group of {@link TIFFTagSet}s. An optional parent {@link TIFFTag}
259     * may also be specified.
260     *
261     * @param tagSets The <code>TIFFTagSets</code> associated with this
262     * directory.
263     * @param parentTag The parent <code>TIFFTag</code> of this directory;
264     * may be <code>null</code>.
265     * @throws IllegalArgumentException if <code>tagSets</code> is
266     * <code>null</code>.
267     */
268    public TIFFDirectory(TIFFTagSet[] tagSets, TIFFTag parentTag) {
269        if(tagSets == null) {
270            throw new IllegalArgumentException("tagSets == null!");
271        }
272        this.tagSets = new ArrayList(tagSets.length);
273        int numTagSets = tagSets.length;
274        for(int i = 0; i < numTagSets; i++) {
275            this.tagSets.add(tagSets[i]);
276        }
277        this.parentTag = parentTag;
278    }
279
280    /**
281     * Returns the {@link TIFFTagSet}s of which this directory is aware.
282     *
283     * @return The <code>TIFFTagSet</code>s associated with this
284     * <code>TIFFDirectory</code>.
285     */
286    public TIFFTagSet[] getTagSets() {
287        return (TIFFTagSet[])tagSets.toArray(new TIFFTagSet[tagSets.size()]);
288    }
289
290    /**
291     * Adds an element to the group of {@link TIFFTagSet}s of which this
292     * directory is aware.
293     *
294     * @param tagSet The <code>TIFFTagSet</code> to add.
295     * @throws IllegalArgumentException if <code>tagSet</code> is
296     * <code>null</code>.
297     */
298    public void addTagSet(TIFFTagSet tagSet) {
299        if(tagSet == null) {
300            throw new IllegalArgumentException("tagSet == null");
301        }
302
303        if(!tagSets.contains(tagSet)) {
304            tagSets.add(tagSet);
305        }
306    }
307
308    /**
309     * Removes an element from the group of {@link TIFFTagSet}s of which this
310     * directory is aware.
311     *
312     * @param tagSet The <code>TIFFTagSet</code> to remove.
313     * @throws IllegalArgumentException if <code>tagSet</code> is
314     * <code>null</code>.
315     */
316    public void removeTagSet(TIFFTagSet tagSet) {
317        if(tagSet == null) {
318            throw new IllegalArgumentException("tagSet == null");
319        }
320
321        if(tagSets.contains(tagSet)) {
322            tagSets.remove(tagSet);
323        }
324    }
325
326    /**
327     * Returns the parent {@link TIFFTag} of this directory if one
328     * has been defined or <code>null</code> otherwise.
329     *
330     * @return The parent <code>TIFFTag</code> of this
331     * <code>TIFFDiectory</code> or <code>null</code>.
332     */
333    public TIFFTag getParentTag() {
334        return parentTag;
335    }
336
337    /**
338     * Returns the {@link TIFFTag} which has tag number equal to
339     * <code>tagNumber</code> or <code>null</code> if no such tag
340     * exists in the {@link TIFFTagSet}s associated with this
341     * directory.
342     *
343     * @param tagNumber The tag number of interest.
344     * @return The corresponding <code>TIFFTag</code> or <code>null</code>.
345     */
346    public TIFFTag getTag(int tagNumber) {
347        return TIFFIFD.getTag(tagNumber, tagSets);
348    }
349
350    /**
351     * Returns the number of {@link TIFFField}s in this directory.
352     *
353     * @return The number of <code>TIFFField</code>s in this
354     * <code>TIFFDirectory</code>.
355     */
356    public int getNumTIFFFields() {
357        return numLowFields + highFields.size();
358    }
359
360    /**
361     * Determines whether a TIFF field with the given tag number is
362     * contained in this directory.
363     *
364     * @return Whether a {@link TIFFTag} with tag number equal to
365     * <code>tagNumber</code> is present in this <code>TIFFDirectory</code>.
366     */
367    public boolean containsTIFFField(int tagNumber) {
368        return (tagNumber >= 0 && tagNumber <= MAX_LOW_FIELD_TAG_NUM &&
369                lowFields[tagNumber] != null) ||
370            highFields.containsKey(new Integer(tagNumber));
371    }
372
373    /**
374     * Adds a TIFF field to the directory.
375     *
376     * @param f The field to add.
377     * @throws IllegalArgumentException if <code>f</code> is <code>null</code>.
378     */
379    public void addTIFFField(TIFFField f) {
380        if(f == null) {
381            throw new IllegalArgumentException("f == null");
382        }
383        int tagNumber = f.getTagNumber();
384        if(tagNumber >= 0 && tagNumber <= MAX_LOW_FIELD_TAG_NUM) {
385            if(lowFields[tagNumber] == null) {
386                numLowFields++;
387            }
388            lowFields[tagNumber] = f;
389        } else {
390            highFields.put(new Integer(tagNumber), f);
391        }
392    }
393
394    /**
395     * Retrieves a TIFF field from the directory.
396     *
397     * @param tagNumber The tag number of the tag associated with the field.
398     * @return A <code>TIFFField</code> with the requested tag number of
399     * <code>null</code> if no such field is present.
400     */
401    public TIFFField getTIFFField(int tagNumber) {
402        TIFFField f;
403        if(tagNumber >= 0 && tagNumber <= MAX_LOW_FIELD_TAG_NUM) {
404            f = lowFields[tagNumber];
405        } else {
406            f = (TIFFField)highFields.get(new Integer(tagNumber));
407        }
408        return f;
409    }
410
411    /**
412     * Removes a TIFF field from the directory.
413     *
414     * @param tagNumber The tag number of the tag associated with the field.
415     */
416    public void removeTIFFField(int tagNumber) {
417        if(tagNumber >= 0 && tagNumber <= MAX_LOW_FIELD_TAG_NUM) {
418            if(lowFields[tagNumber] != null) {
419                numLowFields--;
420                lowFields[tagNumber] = null;
421            }
422        } else {
423            highFields.remove(new Integer(tagNumber));
424        }
425    }
426
427    /**
428     * Retrieves all TIFF fields from the directory.
429     *
430     * @return An array of all TIFF fields in order of numerically increasing
431     * tag number.
432     */
433    public TIFFField[] getTIFFFields() {
434        // Allocate return value.
435        TIFFField[] fields = new TIFFField[numLowFields + highFields.size()];
436
437        // Copy any low-index fields.
438        int nextIndex = 0;
439        for(int i = 0; i <= MAX_LOW_FIELD_TAG_NUM; i++) {
440            if(lowFields[i] != null) {
441                fields[nextIndex++] = lowFields[i];
442                if(nextIndex == numLowFields) break;
443            }
444        }
445
446        // Copy any high-index fields.
447        if(!highFields.isEmpty()) {
448            Iterator keys = highFields.keySet().iterator();
449            while(keys.hasNext()) {
450                fields[nextIndex++] = (TIFFField)highFields.get(keys.next());
451            }
452        }
453
454        return fields;
455    }
456
457    /**
458     * Removes all TIFF fields from the directory.
459     */
460    public void removeTIFFFields() {
461        Arrays.fill(lowFields, (Object)null);
462        numLowFields = 0;
463        highFields.clear();
464    }
465
466    /**
467     * Converts the directory to a metadata object.
468     *
469     * @return A metadata instance initialized from the contents of this
470     * <code>TIFFDirectory</code>.
471     */
472    public IIOMetadata getAsMetadata() {
473        return new TIFFImageMetadata(getDirectoryAsIFD(this));
474    }
475
476    /**
477     * Clones the directory and all the fields contained therein.
478     *
479     * @return A clone of this <code>TIFFDirectory</code>.
480     */
481    public Object clone() {
482        TIFFDirectory dir = new TIFFDirectory(getTagSets(), getParentTag());
483        TIFFField[] fields = getTIFFFields();
484        int numFields = fields.length;
485        for(int i = 0; i < numFields; i++) {
486            dir.addTIFFField(fields[i]);
487        }
488
489        return dir;
490    }
491}