Changeset 6127 in josm for trunk/src/com


Ignore:
Timestamp:
2013-08-09T18:05:11+02:00 (6 years ago)
Author:
bastiK
Message:

applied #8895 - Upgrade metadata-extractor to v. 2.6.4 (patch by ebourg)

Location:
trunk/src/com/drew
Files:
24 added
7 deleted
53 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/com/drew/imaging/PhotographicConversions.java

    r4231 r6127  
    11/*
    2  * This is public domain software - that is, you can do whatever you want
    3  * with it, and include it software that is licensed under the GNU or the
    4  * BSD license, or whatever other licence you choose, including proprietary
    5  * closed source licenses.  I do ask that you leave this header in tact.
     2 * Copyright 2002-2012 Drew Noakes
    63 *
    7  * If you make modifications to this code that you think would benefit the
    8  * wider community, please send me a copy and I'll post it on my site.
     4 *    Licensed under the Apache License, Version 2.0 (the "License");
     5 *    you may not use this file except in compliance with the License.
     6 *    You may obtain a copy of the License at
    97 *
    10  * If you make use of this code, I'd appreciate hearing about it.
    11  *   drew@drewnoakes.com
    12  * Latest version of this software kept at
    13  *   http://drewnoakes.com/
     8 *        http://www.apache.org/licenses/LICENSE-2.0
     9 *
     10 *    Unless required by applicable law or agreed to in writing, software
     11 *    distributed under the License is distributed on an "AS IS" BASIS,
     12 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13 *    See the License for the specific language governing permissions and
     14 *    limitations under the License.
     15 *
     16 * More information about this project is available at:
     17 *
     18 *    http://drewnoakes.com/code/exif/
     19 *    http://code.google.com/p/metadata-extractor/
    1420 */
    1521package com.drew.imaging;
     
    1723/**
    1824 * Contains helper methods that perform photographic conversions.
     25 *
     26 * @author Drew Noakes http://drewnoakes.com
    1927 */
    20 public class PhotographicConversions
     28public final class PhotographicConversions
    2129{
    2230    public final static double ROOT_TWO = Math.sqrt(2);
    2331
    24     private PhotographicConversions()
    25     {}
     32    private PhotographicConversions() throws Exception
     33    {
     34        throw new Exception("Not intended for instantiation.");
     35    }
    2636
    2737    /**
    2838     * Converts an aperture value to its corresponding F-stop number.
     39     *
    2940     * @param aperture the aperture value to convert
    3041     * @return the F-stop number of the specified aperture
     
    3243    public static double apertureToFStop(double aperture)
    3344    {
    34         double fStop = Math.pow(ROOT_TWO, aperture);
    35         return fStop;
     45        return Math.pow(ROOT_TWO, aperture);
    3646
    37         // Puzzle?!
    38         // jhead uses a different calculation as far as i can tell...  this confuses me...
     47        // NOTE jhead uses a different calculation as far as i can tell...  this confuses me...
    3948        // fStop = (float)Math.exp(aperture * Math.log(2) * 0.5));
    4049    }
     
    4251    /**
    4352     * Converts a shutter speed to an exposure time.
     53     *
    4454     * @param shutterSpeed the shutter speed to convert
    4555     * @return the exposure time of the specified shutter speed
     
    4757    public static double shutterSpeedToExposureTime(double shutterSpeed)
    4858    {
    49         return (float)(1 / Math.exp(shutterSpeed * Math.log(2)));
     59        return (float) (1 / Math.exp(shutterSpeed * Math.log(2)));
    5060    }
    5161}
  • trunk/src/com/drew/imaging/jpeg/JpegMetadataReader.java

    r4231 r6127  
    11/*
    2  * This is public domain software - that is, you can do whatever you want
    3  * with it, and include it software that is licensed under the GNU or the
    4  * BSD license, or whatever other licence you choose, including proprietary
    5  * closed source licenses.  I do ask that you leave this header in tact.
     2 * Copyright 2002-2012 Drew Noakes
    63 *
    7  * If you make modifications to this code that you think would benefit the
    8  * wider community, please send me a copy and I'll post it on my site.
     4 *    Licensed under the Apache License, Version 2.0 (the "License");
     5 *    you may not use this file except in compliance with the License.
     6 *    You may obtain a copy of the License at
    97 *
    10  * If you make use of this code, I'd appreciate hearing about it.
    11  *   drew@drewnoakes.com
    12  * Latest version of this software kept at
    13  *   http://drewnoakes.com/
     8 *        http://www.apache.org/licenses/LICENSE-2.0
    149 *
    15  * Created by dnoakes on 12-Nov-2002 18:51:36 using IntelliJ IDEA.
     10 *    Unless required by applicable law or agreed to in writing, software
     11 *    distributed under the License is distributed on an "AS IS" BASIS,
     12 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13 *    See the License for the specific language governing permissions and
     14 *    limitations under the License.
     15 *
     16 * More information about this project is available at:
     17 *
     18 *    http://drewnoakes.com/code/exif/
     19 *    http://code.google.com/p/metadata-extractor/
    1620 */
    1721package com.drew.imaging.jpeg;
    1822
    19 import com.drew.metadata.Directory;
     23import com.drew.lang.ByteArrayReader;
     24import com.drew.lang.annotations.NotNull;
    2025import com.drew.metadata.Metadata;
    21 import com.drew.metadata.MetadataException;
    22 import com.drew.metadata.Tag;
    23 import com.drew.metadata.exif.ExifDirectory;
    2426import com.drew.metadata.exif.ExifReader;
    2527import com.drew.metadata.iptc.IptcReader;
    2628import com.drew.metadata.jpeg.JpegCommentReader;
     29import com.drew.metadata.jpeg.JpegDirectory;
    2730import com.drew.metadata.jpeg.JpegReader;
    2831
     
    3033import java.io.IOException;
    3134import java.io.InputStream;
    32 import java.util.Iterator;
    3335
    3436/**
     37 * Obtains all available metadata from Jpeg formatted files.
    3538 *
     39 * @author Drew Noakes http://drewnoakes.com
    3640 */
    3741public class JpegMetadataReader
    3842{
     43    // TODO investigate supporting javax.imageio
    3944//    public static Metadata readMetadata(IIOMetadata metadata) throws JpegProcessingException {}
    4045//    public static Metadata readMetadata(ImageInputStream in) throws JpegProcessingException{}
     
    4247//    public static Metadata readMetadata(ImageReader reader) throws JpegProcessingException{}
    4348
    44     public static Metadata readMetadata(InputStream in) throws JpegProcessingException
     49    @NotNull
     50    public static Metadata readMetadata(@NotNull InputStream inputStream) throws JpegProcessingException
    4551    {
    46         JpegSegmentReader segmentReader = new JpegSegmentReader(in);
    47         return extractMetadataFromJpegSegmentReader(segmentReader);
     52        return readMetadata(inputStream, true);
    4853    }
    4954
    50     public static Metadata readMetadata(File file) throws JpegProcessingException
     55    @NotNull
     56    public static Metadata readMetadata(@NotNull InputStream inputStream, final boolean waitForBytes) throws JpegProcessingException
     57    {
     58        JpegSegmentReader segmentReader = new JpegSegmentReader(inputStream, waitForBytes);
     59        return extractMetadataFromJpegSegmentReader(segmentReader.getSegmentData());
     60    }
     61
     62    @NotNull
     63    public static Metadata readMetadata(@NotNull File file) throws JpegProcessingException, IOException
    5164    {
    5265        JpegSegmentReader segmentReader = new JpegSegmentReader(file);
    53         return extractMetadataFromJpegSegmentReader(segmentReader);
     66        return extractMetadataFromJpegSegmentReader(segmentReader.getSegmentData());
    5467    }
    5568
    56     public static Metadata extractMetadataFromJpegSegmentReader(JpegSegmentReader segmentReader)
     69    @NotNull
     70    public static Metadata extractMetadataFromJpegSegmentReader(@NotNull JpegSegmentData segmentReader)
    5771    {
    5872        final Metadata metadata = new Metadata();
    59         try {
    60             byte[] exifSegment = segmentReader.readSegment(JpegSegmentReader.SEGMENT_APP1);
    61             new ExifReader(exifSegment).extract(metadata);
    62         } catch (JpegProcessingException e) {
    63             // in the interests of catching as much data as possible, continue
    64             // TODO lodge error message within exif directory?
     73
     74        // Loop through looking for all SOFn segments.  When we find one, we know what type of compression
     75        // was used for the JPEG, and we can process the JPEG metadata in the segment too.
     76        for (byte i = 0; i < 16; i++) {
     77            // There are no SOF4 or SOF12 segments, so don't bother
     78            if (i == 4 || i == 12)
     79                continue;
     80            // Should never have more than one SOFn for a given 'n'.
     81            byte[] jpegSegment = segmentReader.getSegment((byte)(JpegSegmentReader.SEGMENT_SOF0 + i));
     82            if (jpegSegment == null)
     83                continue;
     84            JpegDirectory directory = metadata.getOrCreateDirectory(JpegDirectory.class);
     85            directory.setInt(JpegDirectory.TAG_JPEG_COMPRESSION_TYPE, i);
     86            new JpegReader().extract(new ByteArrayReader(jpegSegment), metadata);
     87            break;
    6588        }
    6689
    67         try {
    68             byte[] iptcSegment = segmentReader.readSegment(JpegSegmentReader.SEGMENT_APPD);
    69             new IptcReader(iptcSegment).extract(metadata);
    70         } catch (JpegProcessingException e) {
    71             // TODO lodge error message within iptc directory?
     90        // There should never be more than one COM segment.
     91        byte[] comSegment = segmentReader.getSegment(JpegSegmentReader.SEGMENT_COM);
     92        if (comSegment != null)
     93            new JpegCommentReader().extract(new ByteArrayReader(comSegment), metadata);
     94
     95        // Loop through all APP1 segments, checking the leading bytes to identify the format of each.
     96        for (byte[] app1Segment : segmentReader.getSegments(JpegSegmentReader.SEGMENT_APP1)) {
     97            if (app1Segment.length > 3 && "EXIF".equalsIgnoreCase(new String(app1Segment, 0, 4)))
     98                new ExifReader().extract(new ByteArrayReader(app1Segment), metadata);
     99
     100            //if (app1Segment.length > 27 && "http://ns.adobe.com/xap/1.0/".equalsIgnoreCase(new String(app1Segment, 0, 28)))
     101            //    new XmpReader().extract(new ByteArrayReader(app1Segment), metadata);
    72102        }
    73103
    74                 try {
    75                         byte[] jpegSegment = segmentReader.readSegment(JpegSegmentReader.SEGMENT_SOF0);
    76                         new JpegReader(jpegSegment).extract(metadata);
    77                 } catch (JpegProcessingException e) {
    78                         // TODO lodge error message within jpeg directory?
    79                 }
    80 
    81                 try {
    82                         byte[] jpegCommentSegment = segmentReader.readSegment(JpegSegmentReader.SEGMENT_COM);
    83                         new JpegCommentReader(jpegCommentSegment).extract(metadata);
    84                 } catch (JpegProcessingException e) {
    85                         // TODO lodge error message within jpegcomment directory?
    86                 }
     104        // Loop through all APPD segments, checking the leading bytes to identify the format of each.
     105        for (byte[] appdSegment : segmentReader.getSegments(JpegSegmentReader.SEGMENT_APPD)) {
     106            if (appdSegment.length > 12 && "Photoshop 3.0".compareTo(new String(appdSegment, 0, 13))==0) {
     107                //new PhotoshopReader().extract(new ByteArrayReader(appdSegment), metadata);
     108            } else {
     109                // TODO might be able to check for a leading 0x1c02 for IPTC data...
     110                new IptcReader().extract(new ByteArrayReader(appdSegment), metadata);
     111            }
     112        }
    87113
    88114        return metadata;
    89115    }
    90116
    91     private JpegMetadataReader()
     117    private JpegMetadataReader() throws Exception
    92118    {
    93     }
    94 
    95     public static void main(String[] args) throws MetadataException, IOException
    96     {
    97         Metadata metadata = null;
    98         try {
    99             metadata = JpegMetadataReader.readMetadata(new File(args[0]));
    100         } catch (Exception e) {
    101             e.printStackTrace(System.err);
    102             System.exit(1);
    103         }
    104 
    105         // iterate over the exif data and print to System.out
    106         Iterator directories = metadata.getDirectoryIterator();
    107         while (directories.hasNext()) {
    108             Directory directory = (Directory)directories.next();
    109             Iterator tags = directory.getTagIterator();
    110             while (tags.hasNext()) {
    111                 Tag tag = (Tag)tags.next();
    112                 try {
    113                     System.out.println("[" + directory.getName() + "] " + tag.getTagName() + " = " + tag.getDescription());
    114                 } catch (MetadataException e) {
    115                     System.err.println(e.getMessage());
    116                     System.err.println(tag.getDirectoryName() + " " + tag.getTagName() + " (error)");
    117                 }
    118             }
    119             if (directory.hasErrors()) {
    120                 Iterator errors = directory.getErrors();
    121                 while (errors.hasNext()) {
    122                     System.out.println("ERROR: " + errors.next());
    123                 }
    124             }
    125         }
    126 
    127         if (args.length>1 && args[1].trim().equals("/thumb"))
    128         {
    129             ExifDirectory directory = (ExifDirectory)metadata.getDirectory(ExifDirectory.class);
    130             if (directory.containsThumbnail())
    131             {
    132                 System.out.println("Writing thumbnail...");
    133                 directory.writeThumbnail(args[0].trim() + ".thumb.jpg");
    134             }
    135             else
    136             {
    137                 System.out.println("No thumbnail data exists in this image");
    138             }
    139         }
     119        throw new Exception("Not intended for instantiation");
    140120    }
    141121}
     122
  • trunk/src/com/drew/imaging/jpeg/JpegProcessingException.java

    r4231 r6127  
    11/*
    2  * JpegProcessingException.java
     2 * Copyright 2002-2012 Drew Noakes
    33 *
    4  * This class is public domain software - that is, you can do whatever you want
    5  * with it, and include it software that is licensed under the GNU or the
    6  * BSD license, or whatever other licence you choose, including proprietary
    7  * closed source licenses.  I do ask that you leave this header in tact.
     4 *    Licensed under the Apache License, Version 2.0 (the "License");
     5 *    you may not use this file except in compliance with the License.
     6 *    You may obtain a copy of the License at
    87 *
    9  * If you make modifications to this code that you think would benefit the
    10  * wider community, please send me a copy and I'll post it on my site.
     8 *        http://www.apache.org/licenses/LICENSE-2.0
    119 *
    12  * If you make use of this code, I'd appreciate hearing about it.
    13  *   drew@drewnoakes.com
    14  * Latest version of this software kept at
    15  *   http://drewnoakes.com/
     10 *    Unless required by applicable law or agreed to in writing, software
     11 *    distributed under the License is distributed on an "AS IS" BASIS,
     12 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13 *    See the License for the specific language governing permissions and
     14 *    limitations under the License.
    1615 *
    17  * Created by dnoakes on 04-Nov-2002 19:31:29 using IntelliJ IDEA.
     16 * More information about this project is available at:
     17 *
     18 *    http://drewnoakes.com/code/exif/
     19 *    http://code.google.com/p/metadata-extractor/
    1820 */
    1921package com.drew.imaging.jpeg;
    2022
    21 import com.drew.lang.CompoundException;
     23import com.drew.imaging.ImageProcessingException;
     24import com.drew.lang.annotations.Nullable;
    2225
    2326/**
    24  * An exception class thrown upon unexpected and fatal conditions while processing
    25  * a Jpeg file.
    26  * @author  Drew Noakes http://drewnoakes.com
     27 * An exception class thrown upon unexpected and fatal conditions while processing a Jpeg file.
     28 *
     29 * @author Drew Noakes http://drewnoakes.com
    2730 */
    28 public class JpegProcessingException extends CompoundException
     31public class JpegProcessingException extends ImageProcessingException
    2932{
    30     public JpegProcessingException(String message)
     33    private static final long serialVersionUID = -7870179776125450158L;
     34
     35    public JpegProcessingException(@Nullable String message)
    3136    {
    3237        super(message);
    3338    }
    3439
    35     public JpegProcessingException(String message, Throwable cause)
     40    public JpegProcessingException(@Nullable String message, @Nullable Throwable cause)
    3641    {
    3742        super(message, cause);
    3843    }
    3944
    40     public JpegProcessingException(Throwable cause)
     45    public JpegProcessingException(@Nullable Throwable cause)
    4146    {
    4247        super(cause);
  • trunk/src/com/drew/imaging/jpeg/JpegSegmentData.java

    r4231 r6127  
    11/*
    2  * This is public domain software - that is, you can do whatever you want
    3  * with it, and include it software that is licensed under the GNU or the
    4  * BSD license, or whatever other licence you choose, including proprietary
    5  * closed source licenses.  I do ask that you leave this header in tact.
    6  *
    7  * If you make modifications to this code that you think would benefit the
    8  * wider community, please send me a copy and I'll post it on my site.
    9  *
    10  * If you make use of this code, I'd appreciate hearing about it.
    11  *   drew@drewnoakes.com
    12  * Latest version of this software kept at
    13  *   http://drewnoakes.com/
     2 * Copyright 2002-2012 Drew Noakes
     3 *
     4 *    Licensed under the Apache License, Version 2.0 (the "License");
     5 *    you may not use this file except in compliance with the License.
     6 *    You may obtain a copy of the License at
     7 *
     8 *        http://www.apache.org/licenses/LICENSE-2.0
     9 *
     10 *    Unless required by applicable law or agreed to in writing, software
     11 *    distributed under the License is distributed on an "AS IS" BASIS,
     12 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13 *    See the License for the specific language governing permissions and
     14 *    limitations under the License.
     15 *
     16 * More information about this project is available at:
     17 *
     18 *    http://drewnoakes.com/code/exif/
     19 *    http://code.google.com/p/metadata-extractor/
    1420 */
    1521package com.drew.imaging.jpeg;
     22
     23import com.drew.lang.annotations.NotNull;
     24import com.drew.lang.annotations.Nullable;
    1625
    1726import java.io.*;
     
    2231/**
    2332 * Holds a collection of Jpeg data segments.  This need not necessarily be all segments
    24  * within the Jpeg.  For example, it may be convenient to port about only the non-image
     33 * within the Jpeg.  For example, it may be convenient to store only the non-image
    2534 * segments when analysing (or serializing) metadata.
     35 * <p/>
     36 * Segments are keyed via their segment marker (a byte).  Where multiple segments use the
     37 * same segment marker, they will all be stored and available.
     38 *
     39 * @author Drew Noakes http://drewnoakes.com
    2640 */
    2741public class JpegSegmentData implements Serializable
    2842{
    29     static final long serialVersionUID = 7110175216435025451L;
     43    private static final long serialVersionUID = 7110175216435025451L;
    3044   
    3145    /** A map of byte[], keyed by the segment marker */
    32     private final HashMap _segmentDataMap;
    33 
    34     public JpegSegmentData()
    35     {
    36         _segmentDataMap = new HashMap(10);
    37     }
    38 
    39     public void addSegment(byte segmentMarker, byte[] segmentBytes)
    40     {
    41         List segmentList = getOrCreateSegmentList(segmentMarker);
     46    @NotNull
     47    private final HashMap<Byte, List<byte[]>> _segmentDataMap = new HashMap<Byte, List<byte[]>>(10);
     48
     49    /**
     50     * Adds segment bytes to the collection.
     51     * @param segmentMarker
     52     * @param segmentBytes
     53     */
     54    @SuppressWarnings({ "MismatchedQueryAndUpdateOfCollection" })
     55    public void addSegment(byte segmentMarker, @NotNull byte[] segmentBytes)
     56    {
     57        final List<byte[]> segmentList = getOrCreateSegmentList(segmentMarker);
    4258        segmentList.add(segmentBytes);
    4359    }
    4460
     61    /**
     62     * Gets the first Jpeg segment data for the specified marker.
     63     * @param segmentMarker the byte identifier for the desired segment
     64     * @return a byte[] containing segment data or null if no data exists for that segment
     65     */
     66    @Nullable
    4567    public byte[] getSegment(byte segmentMarker)
    4668    {
     
    4870    }
    4971
     72    /**
     73     * Gets segment data for a specific occurrence and marker.  Use this method when more than one occurrence
     74     * of segment data for a given marker exists.
     75     * @param segmentMarker identifies the required segment
     76     * @param occurrence the zero-based index of the occurrence
     77     * @return the segment data as a byte[], or null if no segment exists for the marker & occurrence
     78     */
     79    @Nullable
    5080    public byte[] getSegment(byte segmentMarker, int occurrence)
    5181    {
    52         final List segmentList = getSegmentList(segmentMarker);
     82        final List<byte[]> segmentList = getSegmentList(segmentMarker);
    5383
    5484        if (segmentList==null || segmentList.size()<=occurrence)
    5585            return null;
    5686        else
    57             return (byte[]) segmentList.get(occurrence);
    58     }
    59 
     87            return segmentList.get(occurrence);
     88    }
     89
     90    /**
     91     * Returns all instances of a given Jpeg segment.  If no instances exist, an empty sequence is returned.
     92     *
     93     * @param segmentMarker a number which identifies the type of Jpeg segment being queried
     94     * @return zero or more byte arrays, each holding the data of a Jpeg segment
     95     */
     96    @NotNull
     97    public Iterable<byte[]> getSegments(byte segmentMarker)
     98    {
     99        final List<byte[]> segmentList = getSegmentList(segmentMarker);
     100        return segmentList==null ? new ArrayList<byte[]>() : segmentList;
     101    }
     102
     103    @Nullable
     104    public List<byte[]> getSegmentList(byte segmentMarker)
     105    {
     106        return _segmentDataMap.get(Byte.valueOf(segmentMarker));
     107    }
     108
     109    @NotNull
     110    private List<byte[]> getOrCreateSegmentList(byte segmentMarker)
     111    {
     112        List<byte[]> segmentList;
     113        if (_segmentDataMap.containsKey(segmentMarker)) {
     114            segmentList = _segmentDataMap.get(segmentMarker);
     115        } else {
     116            segmentList = new ArrayList<byte[]>();
     117            _segmentDataMap.put(segmentMarker, segmentList);
     118        }
     119        return segmentList;
     120    }
     121
     122    /**
     123     * Returns the count of segment data byte arrays stored for a given segment marker.
     124     * @param segmentMarker identifies the required segment
     125     * @return the segment count (zero if no segments exist).
     126     */
    60127    public int getSegmentCount(byte segmentMarker)
    61128    {
    62         final List segmentList = getSegmentList(segmentMarker);
    63         if (segmentList==null)
    64             return 0;
    65         else
    66             return segmentList.size();
    67     }
    68 
     129        final List<byte[]> segmentList = getSegmentList(segmentMarker);
     130        return segmentList == null ? 0 : segmentList.size();
     131    }
     132
     133    /**
     134     * Removes a specified instance of a segment's data from the collection.  Use this method when more than one
     135     * occurrence of segment data for a given marker exists.
     136     * @param segmentMarker identifies the required segment
     137     * @param occurrence the zero-based index of the segment occurrence to remove.
     138     */
     139    @SuppressWarnings({ "MismatchedQueryAndUpdateOfCollection" })
    69140    public void removeSegmentOccurrence(byte segmentMarker, int occurrence)
    70141    {
    71         final List segmentList = (List)_segmentDataMap.get(new Byte(segmentMarker));
     142        final List<byte[]> segmentList = _segmentDataMap.get(Byte.valueOf(segmentMarker));
    72143        segmentList.remove(occurrence);
    73144    }
    74145
     146    /**
     147     * Removes all segments from the collection having the specified marker.
     148     * @param segmentMarker identifies the required segment
     149     */
    75150    public void removeSegment(byte segmentMarker)
    76151    {
    77         _segmentDataMap.remove(new Byte(segmentMarker));
    78     }
    79 
    80     private List getSegmentList(byte segmentMarker)
    81     {
    82         return (List)_segmentDataMap.get(new Byte(segmentMarker));
    83     }
    84 
    85     private List getOrCreateSegmentList(byte segmentMarker)
    86     {
    87         List segmentList;
    88         Byte key = new Byte(segmentMarker);
    89         if (_segmentDataMap.containsKey(key)) {
    90             segmentList = (List)_segmentDataMap.get(key);
    91         } else {
    92             segmentList = new ArrayList();
    93             _segmentDataMap.put(key, segmentList);
    94         }
    95         return segmentList;
    96     }
    97 
     152        _segmentDataMap.remove(Byte.valueOf(segmentMarker));
     153    }
     154
     155    /**
     156     * Determines whether data is present for a given segment marker.
     157     * @param segmentMarker identifies the required segment
     158     * @return true if data exists, otherwise false
     159     */
    98160    public boolean containsSegment(byte segmentMarker)
    99161    {
    100         return _segmentDataMap.containsKey(new Byte(segmentMarker));
    101     }
    102 
    103     public static void ToFile(File file, JpegSegmentData segmentData) throws IOException
    104     {
    105         ObjectOutputStream outputStream = null;
     162        return _segmentDataMap.containsKey(Byte.valueOf(segmentMarker));
     163    }
     164
     165    /**
     166     * Serialises the contents of a JpegSegmentData to a file.
     167     * @param file to file to write from
     168     * @param segmentData the data to write
     169     * @throws IOException if problems occur while writing
     170     */
     171    public static void toFile(@NotNull File file, @NotNull JpegSegmentData segmentData) throws IOException
     172    {
     173        FileOutputStream fileOutputStream = null;
    106174        try
    107175        {
    108             outputStream = new ObjectOutputStream(new FileOutputStream(file));
    109             outputStream.writeObject(segmentData);
     176            fileOutputStream = new FileOutputStream(file);
     177            new ObjectOutputStream(fileOutputStream).writeObject(segmentData);
    110178        }
    111179        finally
    112180        {
    113             if (outputStream!=null)
    114                 outputStream.close();
    115         }
    116     }
    117 
    118     public static JpegSegmentData FromFile(File file) throws IOException, ClassNotFoundException
     181            if (fileOutputStream!=null)
     182                fileOutputStream.close();
     183        }
     184    }
     185
     186    /**
     187     * Deserialises the contents of a JpegSegmentData from a file.
     188     * @param file the file to read from
     189     * @return the JpegSegmentData as read
     190     * @throws IOException if problems occur while reading
     191     * @throws ClassNotFoundException if problems occur while deserialising
     192     */
     193    @NotNull
     194    public static JpegSegmentData fromFile(@NotNull File file) throws IOException, ClassNotFoundException
    119195    {
    120196        ObjectInputStream inputStream = null;
  • trunk/src/com/drew/imaging/jpeg/JpegSegmentReader.java

    r4231 r6127  
    11/*
    2  * JpegSegmentReader.java
    3  *
    4  * This class written by Drew Noakes, in accordance with the Jpeg specification.
    5  *
    6  * This is public domain software - that is, you can do whatever you want
    7  * with it, and include it software that is licensed under the GNU or the
    8  * BSD license, or whatever other licence you choose, including proprietary
    9  * closed source licenses.  I do ask that you leave this header in tact.
    10  *
    11  * If you make modifications to this code that you think would benefit the
    12  * wider community, please send me a copy and I'll post it on my site.
    13  *
    14  * If you make use of this code, I'd appreciate hearing about it.
    15  *   drew@drewnoakes.com
    16  * Latest version of this software kept at
    17  *   http://drewnoakes.com/
    18  *
    19  * Created by dnoakes on 04-Nov-2002 00:54:00 using IntelliJ IDEA
     2 * Copyright 2002-2012 Drew Noakes
     3 *
     4 *    Licensed under the Apache License, Version 2.0 (the "License");
     5 *    you may not use this file except in compliance with the License.
     6 *    You may obtain a copy of the License at
     7 *
     8 *        http://www.apache.org/licenses/LICENSE-2.0
     9 *
     10 *    Unless required by applicable law or agreed to in writing, software
     11 *    distributed under the License is distributed on an "AS IS" BASIS,
     12 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13 *    See the License for the specific language governing permissions and
     14 *    limitations under the License.
     15 *
     16 * More information about this project is available at:
     17 *
     18 *    http://drewnoakes.com/code/exif/
     19 *    http://code.google.com/p/metadata-extractor/
    2020 */
    2121package com.drew.imaging.jpeg;
    2222
     23import com.drew.lang.annotations.NotNull;
     24import com.drew.lang.annotations.Nullable;
     25
    2326import java.io.*;
    2427
    2528/**
    2629 * Performs read functions of Jpeg files, returning specific file segments.
    27  * TODO add a findAvailableSegments() method
    28  * TODO add more segment identifiers
    29  * TODO add a getSegmentDescription() method, returning for example 'App1 application data segment, commonly containing Exif data'
    3030 * @author  Drew Noakes http://drewnoakes.com
    3131 */
    3232public class JpegSegmentReader
    3333{
    34     // Jpeg data can be sourced from either a file, byte[] or InputStream
    35 
    36     /** Jpeg file */
    37     private final File _file;
    38     /** Jpeg data as byte array */
    39     private final byte[] _data;
    40     /** Jpeg data as an InputStream */
    41     private final InputStream _stream;
    42 
    43     private JpegSegmentData _segmentData;
    44 
    45     /**
    46      * Private, because this segment crashes my algorithm, and searching for
    47      * it doesn't work (yet).
     34    // TODO add a findAvailableSegments() method
     35    // TODO add more segment identifiers
     36    // TODO add a getSegmentDescription() method, returning for example 'App1 application data segment, commonly containing Exif data'
     37
     38    @NotNull
     39    private final JpegSegmentData _segmentData;
     40
     41    /**
     42     * Private, because this segment crashes my algorithm, and searching for it doesn't work (yet).
    4843     */
    4944    private static final byte SEGMENT_SOS = (byte)0xDA;
     
    5449    private static final byte MARKER_EOI = (byte)0xD9;
    5550
    56     /** APP0 Jpeg segment identifier -- Jfif data. */
     51    /** APP0 Jpeg segment identifier -- JFIF data (also JFXX apparently). */
    5752    public static final byte SEGMENT_APP0 = (byte)0xE0;
    58     /** APP1 Jpeg segment identifier -- where Exif data is kept. */
     53    /** APP1 Jpeg segment identifier -- where Exif data is kept.  XMP data is also kept in here, though usually in a second instance. */
    5954    public static final byte SEGMENT_APP1 = (byte)0xE1;
    6055    /** APP2 Jpeg segment identifier. */
     
    7469    /** APP9 Jpeg segment identifier. */
    7570    public static final byte SEGMENT_APP9 = (byte)0xE9;
    76     /** APPA Jpeg segment identifier -- can hold Unicode comments. */
     71    /** APPA (App10) Jpeg segment identifier -- can hold Unicode comments. */
    7772    public static final byte SEGMENT_APPA = (byte)0xEA;
    78     /** APPB Jpeg segment identifier. */
     73    /** APPB (App11) Jpeg segment identifier. */
    7974    public static final byte SEGMENT_APPB = (byte)0xEB;
    80     /** APPC Jpeg segment identifier. */
     75    /** APPC (App12) Jpeg segment identifier. */
    8176    public static final byte SEGMENT_APPC = (byte)0xEC;
    82     /** APPD Jpeg segment identifier -- IPTC data in here. */
     77    /** APPD (App13) Jpeg segment identifier -- IPTC data in here. */
    8378    public static final byte SEGMENT_APPD = (byte)0xED;
    84     /** APPE Jpeg segment identifier. */
     79    /** APPE (App14) Jpeg segment identifier. */
    8580    public static final byte SEGMENT_APPE = (byte)0xEE;
    86     /** APPF Jpeg segment identifier. */
     81    /** APPF (App15) Jpeg segment identifier. */
    8782    public static final byte SEGMENT_APPF = (byte)0xEF;
    8883    /** Start Of Image segment identifier. */
     
    10196     * @param file the Jpeg file to read segments from
    10297     */
    103     public JpegSegmentReader(File file) throws JpegProcessingException
    104     {
    105         _file = file;
    106         _data = null;
    107         _stream = null;
    108 
    109         readSegments();
     98    @SuppressWarnings({ "ConstantConditions" })
     99    public JpegSegmentReader(@NotNull File file) throws JpegProcessingException, IOException
     100    {
     101        if (file==null)
     102            throw new NullPointerException();
     103
     104        InputStream inputStream = null;
     105        try {
     106            inputStream = new FileInputStream(file);
     107            _segmentData = readSegments(new BufferedInputStream(inputStream), false);
     108        } finally {
     109            if (inputStream != null)
     110                inputStream.close();
     111        }
    110112    }
    111113
     
    114116     * @param fileContents the byte array containing Jpeg data
    115117     */
    116     public JpegSegmentReader(byte[] fileContents) throws JpegProcessingException
    117     {
    118         _file = null;
    119         _data = fileContents;
    120         _stream = null;
    121 
    122         readSegments();
    123     }
    124 
    125     public JpegSegmentReader(InputStream in) throws JpegProcessingException
    126     {
    127         _stream = in;
    128         _file = null;
    129         _data = null;
    130        
    131         readSegments();
    132     }
    133 
    134     public JpegSegmentReader(JpegSegmentData segmentData)
    135     {
    136         _file = null;
    137         _data = null;
    138         _stream = null;
    139 
    140         _segmentData = segmentData;
     118    @SuppressWarnings({ "ConstantConditions" })
     119    public JpegSegmentReader(@NotNull byte[] fileContents) throws JpegProcessingException
     120    {
     121        if (fileContents==null)
     122            throw new NullPointerException();
     123
     124        BufferedInputStream stream = new BufferedInputStream(new ByteArrayInputStream(fileContents));
     125        _segmentData = readSegments(stream, false);
     126    }
     127
     128    /**
     129     * Creates a JpegSegmentReader for an InputStream.
     130     * @param inputStream the InputStream containing Jpeg data
     131     */
     132    @SuppressWarnings({ "ConstantConditions" })
     133    public JpegSegmentReader(@NotNull InputStream inputStream, boolean waitForBytes) throws JpegProcessingException
     134    {
     135        if (inputStream==null)
     136            throw new NullPointerException();
     137
     138        BufferedInputStream bufferedInputStream = inputStream instanceof BufferedInputStream
     139                ? (BufferedInputStream)inputStream
     140                : new BufferedInputStream(inputStream);
     141
     142        _segmentData = readSegments(bufferedInputStream, waitForBytes);
    141143    }
    142144
     
    146148     * @param segmentMarker the byte identifier for the desired segment
    147149     * @return the byte array if found, else null
    148      * @throws JpegProcessingException for any problems processing the Jpeg data,
    149      *         including inner IOExceptions
    150      */
    151     public byte[] readSegment(byte segmentMarker) throws JpegProcessingException
     150     */
     151    @Nullable
     152    public byte[] readSegment(byte segmentMarker)
    152153    {
    153154        return readSegment(segmentMarker, 0);
     
    155156
    156157    /**
    157      * Reads the first instance of a given Jpeg segment, returning the contents as
    158      * a byte array.
     158     * Reads the Nth instance of a given Jpeg segment, returning the contents as a byte array.
     159     *
    159160     * @param segmentMarker the byte identifier for the desired segment
    160161     * @param occurrence the occurrence of the specified segment within the jpeg file
    161162     * @return the byte array if found, else null
    162163     */
     164    @Nullable
    163165    public byte[] readSegment(byte segmentMarker, int occurrence)
    164166    {
     
    166168    }
    167169
     170    /**
     171     * Returns all instances of a given Jpeg segment.  If no instances exist, an empty sequence is returned.
     172     *
     173     * @param segmentMarker a number which identifies the type of Jpeg segment being queried
     174     * @return zero or more byte arrays, each holding the data of a Jpeg segment
     175     */
     176    @NotNull
     177    public Iterable<byte[]> readSegments(byte segmentMarker)
     178    {
     179        return _segmentData.getSegments(segmentMarker);
     180    }
     181
     182    /**
     183     * Returns the number of segments having the specified JPEG segment marker.
     184     * @param segmentMarker the JPEG segment identifying marker.
     185     * @return the count of matching segments.
     186     */
    168187    public final int getSegmentCount(byte segmentMarker)
    169188    {
     
    171190    }
    172191
     192    /**
     193     * Returns the JpegSegmentData object used by this reader.
     194     * @return the JpegSegmentData object.
     195     */
     196    @NotNull
    173197    public final JpegSegmentData getSegmentData()
    174198    {
     
    176200    }
    177201
    178     private void readSegments() throws JpegProcessingException
    179     {
    180         _segmentData = new JpegSegmentData();
    181 
    182         BufferedInputStream inStream = getJpegInputStream();
     202    @NotNull
     203    private JpegSegmentData readSegments(@NotNull final BufferedInputStream jpegInputStream, boolean waitForBytes) throws JpegProcessingException
     204    {
     205        JpegSegmentData segmentData = new JpegSegmentData();
     206
    183207        try {
    184208            int offset = 0;
    185209            // first two bytes should be jpeg magic number
    186             if (!isValidJpegHeaderBytes(inStream)) {
     210            byte[] headerBytes = new byte[2];
     211            if (jpegInputStream.read(headerBytes, 0, 2)!=2)
    187212                throw new JpegProcessingException("not a jpeg file");
    188             }
     213            final boolean hasValidHeader = (headerBytes[0] & 0xFF) == 0xFF && (headerBytes[1] & 0xFF) == 0xD8;
     214            if (!hasValidHeader)
     215                throw new JpegProcessingException("not a jpeg file");
     216
    189217            offset += 2;
    190218            do {
     219                // need four bytes from stream for segment header before continuing
     220                if (!checkForBytesOnStream(jpegInputStream, 4, waitForBytes))
     221                    throw new JpegProcessingException("stream ended before segment header could be read");
     222
    191223                // next byte is 0xFF
    192                 byte segmentIdentifier = (byte)(inStream.read() & 0xFF);
     224                byte segmentIdentifier = (byte)(jpegInputStream.read() & 0xFF);
    193225                if ((segmentIdentifier & 0xFF) != 0xFF) {
    194226                    throw new JpegProcessingException("expected jpeg segment start identifier 0xFF at offset " + offset + ", not 0x" + Integer.toHexString(segmentIdentifier & 0xFF));
     
    196228                offset++;
    197229                // next byte is <segment-marker>
    198                 byte thisSegmentMarker = (byte)(inStream.read() & 0xFF);
     230                byte thisSegmentMarker = (byte)(jpegInputStream.read() & 0xFF);
    199231                offset++;
    200232                // next 2-bytes are <segment-size>: [high-byte] [low-byte]
    201233                byte[] segmentLengthBytes = new byte[2];
    202                 inStream.read(segmentLengthBytes, 0, 2);
     234                if (jpegInputStream.read(segmentLengthBytes, 0, 2) != 2)
     235                    throw new JpegProcessingException("Jpeg data ended unexpectedly.");
    203236                offset += 2;
    204237                int segmentLength = ((segmentLengthBytes[0] << 8) & 0xFF00) | (segmentLengthBytes[1] & 0xFF);
    205238                // segment length includes size bytes, so subtract two
    206239                segmentLength -= 2;
    207                 if (segmentLength > inStream.available())
     240                if (!checkForBytesOnStream(jpegInputStream, segmentLength, waitForBytes))
    208241                    throw new JpegProcessingException("segment size would extend beyond file stream length");
    209                 else if (segmentLength < 0)
     242                if (segmentLength < 0)
    210243                    throw new JpegProcessingException("segment size would be less than zero");
    211244                byte[] segmentBytes = new byte[segmentLength];
    212                 inStream.read(segmentBytes, 0, segmentLength);
     245                if (jpegInputStream.read(segmentBytes, 0, segmentLength) != segmentLength)
     246                    throw new JpegProcessingException("Jpeg data ended unexpectedly.");
    213247                offset += segmentLength;
    214248                if ((thisSegmentMarker & 0xFF) == (SEGMENT_SOS & 0xFF)) {
     
    216250                    // have to search for the two bytes: 0xFF 0xD9 (EOI).
    217251                    // It comes last so simply return at this point
    218                     return;
     252                    return segmentData;
    219253                } else if ((thisSegmentMarker & 0xFF) == (MARKER_EOI & 0xFF)) {
    220254                    // the 'End-Of-Image' segment -- this should never be found in this fashion
    221                     return;
     255                    return segmentData;
    222256                } else {
    223                     _segmentData.addSegment(thisSegmentMarker, segmentBytes);
     257                    segmentData.addSegment(thisSegmentMarker, segmentBytes);
    224258                }
    225                 // didn't find the one we're looking for, loop through to the next segment
    226259            } while (true);
    227260        } catch (IOException ioe) {
    228             //throw new JpegProcessingException("IOException processing Jpeg file", ioe);
    229261            throw new JpegProcessingException("IOException processing Jpeg file: " + ioe.getMessage(), ioe);
    230262        } finally {
    231263            try {
    232                 if (inStream != null) {
    233                     inStream.close();
     264                if (jpegInputStream != null) {
     265                    jpegInputStream.close();
    234266                }
    235267            } catch (IOException ioe) {
    236                 //throw new JpegProcessingException("IOException processing Jpeg file", ioe);
    237268                throw new JpegProcessingException("IOException processing Jpeg file: " + ioe.getMessage(), ioe);
    238269            }
     
    240271    }
    241272
    242     /**
    243      * Private helper method to create a BufferedInputStream of Jpeg data from whichever
    244      * data source was specified upon construction of this instance.
    245      * @return a a BufferedInputStream of Jpeg data
    246      * @throws JpegProcessingException for any problems obtaining the stream
    247      */
    248     private BufferedInputStream getJpegInputStream() throws JpegProcessingException
    249     {
    250         if (_stream!=null) {
    251             if (_stream instanceof BufferedInputStream) {
    252                 return (BufferedInputStream) _stream;
    253             } else {
    254                 return new BufferedInputStream(_stream);
     273    private boolean checkForBytesOnStream(@NotNull BufferedInputStream stream, int bytesNeeded, boolean waitForBytes) throws IOException
     274    {
     275        // NOTE  waiting is essential for network streams where data can be delayed, but it is not necessary for byte[] or filesystems
     276
     277        if (!waitForBytes)
     278            return bytesNeeded <= stream.available();
     279
     280        int count = 40; // * 100ms = approx 4 seconds
     281        while (count > 0) {
     282            if (bytesNeeded <= stream.available())
     283               return true;
     284            try {
     285                Thread.sleep(100);
     286            } catch (InterruptedException e) {
     287                // continue
    255288            }
     289            count--;
    256290        }
    257         InputStream inputStream;
    258         if (_data == null) {
    259             try {
    260                 inputStream = new FileInputStream(_file);
    261             } catch (FileNotFoundException e) {
    262                 throw new JpegProcessingException("Jpeg file does not exist", e);
    263             }
    264         } else {
    265             inputStream = new ByteArrayInputStream(_data);
    266         }
    267         return new BufferedInputStream(inputStream);
    268     }
    269 
    270     /**
    271      * Helper method that validates the Jpeg file's magic number.
    272      * @param fileStream the InputStream to read bytes from, which must be positioned
    273      *        at its start (i.e. no bytes read yet)
    274      * @return true if the magic number is Jpeg (0xFFD8)
    275      * @throws IOException for any problem in reading the file
    276      */
    277     private boolean isValidJpegHeaderBytes(InputStream fileStream) throws IOException
    278     {
    279         byte[] header = new byte[2];
    280         fileStream.read(header, 0, 2);
    281         return (header[0] & 0xFF) == 0xFF && (header[1] & 0xFF) == 0xD8;
     291        return false;
    282292    }
    283293}
  • trunk/src/com/drew/lang/CompoundException.java

    r4231 r6127  
    11/*
    2  * This is public domain software - that is, you can do whatever you want
    3  * with it, and include it software that is licensed under the GNU or the
    4  * BSD license, or whatever other licence you choose, including proprietary
    5  * closed source licenses.  I do ask that you leave this header in tact.
     2 * Copyright 2002-2012 Drew Noakes
    63 *
    7  * If you make modifications to this code that you think would benefit the
    8  * wider community, please send me a copy and I'll post it on my site.
     4 *    Licensed under the Apache License, Version 2.0 (the "License");
     5 *    you may not use this file except in compliance with the License.
     6 *    You may obtain a copy of the License at
    97 *
    10  * If you make use of this code, I'd appreciate hearing about it.
    11  *   drew@drewnoakes.com
    12  * Latest version of this software kept at
    13  *   http://drewnoakes.com/
     8 *        http://www.apache.org/licenses/LICENSE-2.0
     9 *
     10 *    Unless required by applicable law or agreed to in writing, software
     11 *    distributed under the License is distributed on an "AS IS" BASIS,
     12 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13 *    See the License for the specific language governing permissions and
     14 *    limitations under the License.
     15 *
     16 * More information about this project is available at:
     17 *
     18 *    http://drewnoakes.com/code/exif/
     19 *    http://code.google.com/p/metadata-extractor/
    1420 */
    1521package com.drew.lang;
     22
     23import com.drew.lang.annotations.NotNull;
     24import com.drew.lang.annotations.Nullable;
    1625
    1726import java.io.PrintStream;
     
    2231 * unavailable in previous versions.  This class allows support
    2332 * of these previous JDK versions.
     33 *
     34 * @author Drew Noakes http://drewnoakes.com
    2435 */
    2536public class CompoundException extends Exception
    2637{
    27     private final Throwable _innnerException;
     38    private static final long serialVersionUID = -9207883813472069925L;
    2839
    29     public CompoundException(String msg)
     40    @Nullable
     41    private final Throwable _innerException;
     42
     43    public CompoundException(@Nullable String msg)
    3044    {
    3145        this(msg, null);
    3246    }
    3347
    34     public CompoundException(Throwable exception)
     48    public CompoundException(@Nullable Throwable exception)
    3549    {
    3650        this(null, exception);
    3751    }
    3852
    39     public CompoundException(String msg, Throwable innerException)
     53    public CompoundException(@Nullable String msg, @Nullable Throwable innerException)
    4054    {
    4155        super(msg);
    42         _innnerException = innerException;
     56        _innerException = innerException;
    4357    }
    4458
     59    @Nullable
    4560    public Throwable getInnerException()
    4661    {
    47         return _innnerException;
     62        return _innerException;
    4863    }
    4964
     65    @NotNull
    5066    public String toString()
    5167    {
    52         StringBuffer sbuffer = new StringBuffer();
    53         sbuffer.append(super.toString());
    54         if (_innnerException != null) {
    55             sbuffer.append("\n");
    56             sbuffer.append("--- inner exception ---");
    57             sbuffer.append("\n");
    58             sbuffer.append(_innnerException.toString());
     68        StringBuilder string = new StringBuilder();
     69        string.append(super.toString());
     70        if (_innerException != null) {
     71            string.append("\n");
     72            string.append("--- inner exception ---");
     73            string.append("\n");
     74            string.append(_innerException.toString());
    5975        }
    60         return sbuffer.toString();
     76        return string.toString();
    6177    }
    6278
    63     public void printStackTrace(PrintStream s)
     79    public void printStackTrace(@NotNull PrintStream s)
    6480    {
    6581        super.printStackTrace(s);
    66         if (_innnerException != null) {
     82        if (_innerException != null) {
    6783            s.println("--- inner exception ---");
    68             _innnerException.printStackTrace(s);
     84            _innerException.printStackTrace(s);
    6985        }
    7086    }
    7187
    72     public void printStackTrace(PrintWriter s)
     88    public void printStackTrace(@NotNull PrintWriter s)
    7389    {
    7490        super.printStackTrace(s);
    75         if (_innnerException != null) {
     91        if (_innerException != null) {
    7692            s.println("--- inner exception ---");
    77             _innnerException.printStackTrace(s);
     93            _innerException.printStackTrace(s);
    7894        }
    7995    }
     
    8298    {
    8399        super.printStackTrace();
    84         if (_innnerException != null) {
     100        if (_innerException != null) {
    85101            System.err.println("--- inner exception ---");
    86             _innnerException.printStackTrace();
     102            _innerException.printStackTrace();
    87103        }
    88104    }
  • trunk/src/com/drew/lang/NullOutputStream.java

    r4231 r6127  
    1 /**
    2  * This is public domain software - that is, you can do whatever you want
    3  * with it, and include it software that is licensed under the GNU or the
    4  * BSD license, or whatever other licence you choose, including proprietary
    5  * closed source licenses.  I do ask that you leave this header in tact.
     1/*
     2 * Copyright 2002-2012 Drew Noakes
    63 *
    7  * If you make modifications to this code that you think would benefit the
    8  * wider community, please send me a copy and I'll post it on my site.
     4 *    Licensed under the Apache License, Version 2.0 (the "License");
     5 *    you may not use this file except in compliance with the License.
     6 *    You may obtain a copy of the License at
    97 *
    10  * If you make use of this code, I'd appreciate hearing about it.
    11  *   drew@drewnoakes.com
    12  * Latest version of this software kept at
    13  *   http://drewnoakes.com/
     8 *        http://www.apache.org/licenses/LICENSE-2.0
    149 *
    15  * Created by dnoakes on Dec 15, 2002 3:30:59 PM using IntelliJ IDEA.
     10 *    Unless required by applicable law or agreed to in writing, software
     11 *    distributed under the License is distributed on an "AS IS" BASIS,
     12 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13 *    See the License for the specific language governing permissions and
     14 *    limitations under the License.
     15 *
     16 * More information about this project is available at:
     17 *
     18 *    http://drewnoakes.com/code/exif/
     19 *    http://code.google.com/p/metadata-extractor/
    1620 */
    1721package com.drew.lang;
     
    2024import java.io.OutputStream;
    2125
     26/**
     27 * An implementation of OutputSteam that ignores write requests by doing nothing.  This class may be useful in tests.
     28 *
     29 * @author Drew Noakes http://drewnoakes.com
     30 */
    2231public class NullOutputStream extends OutputStream
    2332{
  • trunk/src/com/drew/lang/Rational.java

    r4231 r6127  
    11/*
    2  * Rational.java
    3  *
    4  * This class is public domain software - that is, you can do whatever you want
    5  * with it, and include it software that is licensed under the GNU or the
    6  * BSD license, or whatever other licence you choose, including proprietary
    7  * closed source licenses.  Similarly, I release this Java version under the
    8  * same license, though I do ask that you leave this header in tact.
    9  *
    10  * If you make modifications to this code that you think would benefit the
    11  * wider community, please send me a copy and I'll post it on my site.
    12  *
    13  * If you make use of this code, I'd appreciate hearing about it.
    14  *   drew.noakes@drewnoakes.com
    15  * Latest version of this software kept at
    16  *   http://drewnoakes.com/
    17  *
    18  * Created on 6 May 2002, 18:06
    19  * Updated 26 Aug 2002 by Drew
    20  * - Added toSimpleString() method, which returns a simplified and hopefully more
    21  *   readable version of the Rational.  i.e. 2/10 -> 1/5, and 10/2 -> 5
    22  * Modified 29 Oct 2002 (v1.2)
    23  * - Improved toSimpleString() to factor more complex rational numbers into
    24  *   a simpler form
    25  *     i.e.
    26  *       10/15 -> 2/3
    27  * - toSimpleString() now accepts a boolean flag, 'allowDecimals' which will
    28  *   display the rational number in decimal form if it fits within 5 digits
    29  *     i.e.
    30  *       3/4 -> 0.75 when allowDecimal == true
     2 * Copyright 2002-2012 Drew Noakes
     3 *
     4 *    Licensed under the Apache License, Version 2.0 (the "License");
     5 *    you may not use this file except in compliance with the License.
     6 *    You may obtain a copy of the License at
     7 *
     8 *        http://www.apache.org/licenses/LICENSE-2.0
     9 *
     10 *    Unless required by applicable law or agreed to in writing, software
     11 *    distributed under the License is distributed on an "AS IS" BASIS,
     12 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13 *    See the License for the specific language governing permissions and
     14 *    limitations under the License.
     15 *
     16 * More information about this project is available at:
     17 *
     18 *    http://drewnoakes.com/code/exif/
     19 *    http://code.google.com/p/metadata-extractor/
    3120 */
    3221
    3322package com.drew.lang;
     23
     24import com.drew.lang.annotations.NotNull;
     25import com.drew.lang.annotations.Nullable;
    3426
    3527import java.io.Serializable;
     
    3830 * Immutable class for holding a rational number without loss of precision.  Provides
    3931 * a familiar representation via toString() in form <code>numerator/denominator</code>.
    40  * <p>
    41  * @author  Drew Noakes http://drewnoakes.com
     32 *
     33 * @author Drew Noakes http://drewnoakes.com
    4234 */
    4335public class Rational extends java.lang.Number implements Serializable
    4436{
    45     /**
    46      * Holds the numerator.
    47      */
    48     private final int numerator;
    49 
    50     /**
    51      * Holds the denominator.
    52      */
    53     private final int denominator;
    54 
    55     private int maxSimplificationCalculations = 1000;
     37    private static final long serialVersionUID = 510688928138848770L;
     38
     39    /** Holds the numerator. */
     40    private final long _numerator;
     41
     42    /** Holds the denominator. */
     43    private final long _denominator;
    5644
    5745    /**
     
    6048     * with them!
    6149     */
    62     public Rational(int numerator, int denominator)
    63     {
    64         this.numerator = numerator;
    65         this.denominator = denominator;
     50    public Rational(long numerator, long denominator)
     51    {
     52        _numerator = numerator;
     53        _denominator = denominator;
    6654    }
    6755
     
    7058     * This may involve rounding.
    7159     *
    72      * @return  the numeric value represented by this object after conversion
    73      *          to type <code>double</code>.
     60     * @return the numeric value represented by this object after conversion
     61     *         to type <code>double</code>.
    7462     */
    7563    public double doubleValue()
    7664    {
    77         return (double)numerator / (double)denominator;
     65        return (double) _numerator / (double) _denominator;
    7866    }
    7967
     
    8270     * This may involve rounding.
    8371     *
    84      * @return  the numeric value represented by this object after conversion
    85      *          to type <code>float</code>.
     72     * @return the numeric value represented by this object after conversion
     73     *         to type <code>float</code>.
    8674     */
    8775    public float floatValue()
    8876    {
    89         return (float)numerator / (float)denominator;
     77        return (float) _numerator / (float) _denominator;
    9078    }
    9179
     
    9583     * casts the result of <code>doubleValue()</code> to <code>byte</code>.
    9684     *
    97      * @return  the numeric value represented by this object after conversion
    98      *          to type <code>byte</code>.
     85     * @return the numeric value represented by this object after conversion
     86     *         to type <code>byte</code>.
    9987     */
    10088    public final byte byteValue()
    10189    {
    102         return (byte)doubleValue();
     90        return (byte) doubleValue();
    10391    }
    10492
     
    10896     * casts the result of <code>doubleValue()</code> to <code>int</code>.
    10997     *
    110      * @return  the numeric value represented by this object after conversion
    111      *          to type <code>int</code>.
     98     * @return the numeric value represented by this object after conversion
     99     *         to type <code>int</code>.
    112100     */
    113101    public final int intValue()
    114102    {
    115         return (int)doubleValue();
     103        return (int) doubleValue();
    116104    }
    117105
     
    121109     * casts the result of <code>doubleValue()</code> to <code>long</code>.
    122110     *
    123      * @return  the numeric value represented by this object after conversion
    124      *          to type <code>long</code>.
     111     * @return the numeric value represented by this object after conversion
     112     *         to type <code>long</code>.
    125113     */
    126114    public final long longValue()
    127115    {
    128         return (long)doubleValue();
     116        return (long) doubleValue();
    129117    }
    130118
     
    134122     * casts the result of <code>doubleValue()</code> to <code>short</code>.
    135123     *
    136      * @return  the numeric value represented by this object after conversion
    137      *          to type <code>short</code>.
     124     * @return the numeric value represented by this object after conversion
     125     *         to type <code>short</code>.
    138126     */
    139127    public final short shortValue()
    140128    {
    141         return (short)doubleValue();
    142     }
    143 
    144 
    145     /**
    146      * Returns the denominator.
    147      */
    148     public final int getDenominator()
    149     {
    150         return this.denominator;
    151     }
    152 
    153     /**
    154      * Returns the numerator.
    155      */
    156     public final int getNumerator()
    157     {
    158         return this.numerator;
    159     }
    160 
    161     /**
    162      * Returns the reciprocal value of this obejct as a new Rational.
     129        return (short) doubleValue();
     130    }
     131
     132
     133    /** Returns the denominator. */
     134    public final long getDenominator()
     135    {
     136        return this._denominator;
     137    }
     138
     139    /** Returns the numerator. */
     140    public final long getNumerator()
     141    {
     142        return this._numerator;
     143    }
     144
     145    /**
     146     * Returns the reciprocal value of this object as a new Rational.
     147     *
    163148     * @return the reciprocal in a new object
    164149     */
     150    @NotNull
    165151    public Rational getReciprocal()
    166152    {
    167         return new Rational(this.denominator, this.numerator);
    168     }
    169 
    170     /**
    171      * Checks if this rational number is an Integer, either positive or negative.
    172      */
     153        return new Rational(this._denominator, this._numerator);
     154    }
     155
     156    /** Checks if this rational number is an Integer, either positive or negative. */
    173157    public boolean isInteger()
    174158    {
    175         if (denominator == 1 ||
    176                 (denominator != 0 && (numerator % denominator == 0)) ||
    177                 (denominator == 0 && numerator == 0)
    178         ) {
    179             return true;
    180         } else {
    181             return false;
    182         }
     159        return _denominator == 1 ||
     160                (_denominator != 0 && (_numerator % _denominator == 0)) ||
     161                (_denominator == 0 && _numerator == 0);
    183162    }
    184163
    185164    /**
    186165     * Returns a string representation of the object of form <code>numerator/denominator</code>.
    187      * @return  a string representation of the object.
    188      */
     166     *
     167     * @return a string representation of the object.
     168     */
     169    @NotNull
    189170    public String toString()
    190171    {
    191         return numerator + "/" + denominator;
    192     }
    193 
    194     /**
    195      * Returns the simplest represenation of this Rational's value possible.
    196      */
     172        return _numerator + "/" + _denominator;
     173    }
     174
     175    /** Returns the simplest representation of this Rational's value possible. */
     176    @NotNull
    197177    public String toSimpleString(boolean allowDecimal)
    198178    {
    199         if (denominator == 0 && numerator != 0) {
     179        if (_denominator == 0 && _numerator != 0) {
    200180            return toString();
    201181        } else if (isInteger()) {
    202182            return Integer.toString(intValue());
    203         } else if (numerator != 1 && denominator % numerator == 0) {
     183        } else if (_numerator != 1 && _denominator % _numerator == 0) {
    204184            // common factor between denominator and numerator
    205             int newDenominator = denominator / numerator;
     185            long newDenominator = _denominator / _numerator;
    206186            return new Rational(1, newDenominator).toSimpleString(allowDecimal);
    207187        } else {
     
    220200     * Decides whether a brute-force simplification calculation should be avoided
    221201     * by comparing the maximum number of possible calculations with some threshold.
     202     *
    222203     * @return true if the simplification should be performed, otherwise false
    223204     */
    224205    private boolean tooComplexForSimplification()
    225206    {
    226         double maxPossibleCalculations = (((double)(Math.min(denominator, numerator) - 1) / 5d) + 2);
     207        double maxPossibleCalculations = (((double) (Math.min(_denominator, _numerator) - 1) / 5d) + 2);
     208        final int maxSimplificationCalculations = 1000;
    227209        return maxPossibleCalculations > maxSimplificationCalculations;
    228210    }
     
    231213     * Compares two <code>Rational</code> instances, returning true if they are mathematically
    232214     * equivalent.
     215     *
    233216     * @param obj the Rational to compare this instance to.
    234217     * @return true if instances are mathematically equivalent, otherwise false.  Will also
    235218     *         return false if <code>obj</code> is not an instance of <code>Rational</code>.
    236219     */
    237     public boolean equals(Object obj)
    238     {
    239         if (!(obj instanceof Rational)) {
     220    @Override
     221    public boolean equals(@Nullable Object obj)
     222    {
     223        if (obj==null || !(obj instanceof Rational))
    240224            return false;
    241         }
    242         Rational that = (Rational)obj;
     225        Rational that = (Rational) obj;
    243226        return this.doubleValue() == that.doubleValue();
     227    }
     228
     229    @Override
     230    public int hashCode()
     231    {
     232        return (23 * (int)_denominator) + (int)_numerator;
    244233    }
    245234
     
    252241     * To reduce a rational, need to see if both numerator and denominator are divisible
    253242     * by a common factor.  Using the prime number series in ascending order guarantees
    254      * the minimun number of checks required.</p>
     243     * the minimum number of checks required.</p>
    255244     * <p>
    256245     * However, generating the prime number series seems to be a hefty task.  Perhaps
     
    265254     *   -- * ------------------------------------ + 2
    266255     *   10                    2
    267      *
     256     * <p/>
    268257     *   Math.min(denominator, numerator) - 1
    269258     * = ------------------------------------ + 2
    270259     *                  5
    271260     * </pre></code>
    272      * @return a simplified instance, or if the Rational could not be simpliffied,
     261     *
     262     * @return a simplified instance, or if the Rational could not be simplified,
    273263     *         returns itself (unchanged)
    274264     */
     265    @NotNull
    275266    public Rational getSimplifiedInstance()
    276267    {
     
    278269            return this;
    279270        }
    280         for (int factor = 2; factor <= Math.min(denominator, numerator); factor++) {
     271        for (int factor = 2; factor <= Math.min(_denominator, _numerator); factor++) {
    281272            if ((factor % 2 == 0 && factor > 2) || (factor % 5 == 0 && factor > 5)) {
    282273                continue;
    283274            }
    284             if (denominator % factor == 0 && numerator % factor == 0) {
     275            if (_denominator % factor == 0 && _numerator % factor == 0) {
    285276                // found a common factor
    286                 return new Rational(numerator / factor, denominator / factor);
     277                return new Rational(_numerator / factor, _denominator / factor);
    287278            }
    288279        }
  • trunk/src/com/drew/metadata/DefaultTagDescriptor.java

    r4231 r6127  
    11/*
    2  * This is public domain software - that is, you can do whatever you want
    3  * with it, and include it software that is licensed under the GNU or the
    4  * BSD license, or whatever other licence you choose, including proprietary
    5  * closed source licenses.  I do ask that you leave this header in tact.
     2 * Copyright 2002-2012 Drew Noakes
    63 *
    7  * If you make modifications to this code that you think would benefit the
    8  * wider community, please send me a copy and I'll post it on my site.
     4 *    Licensed under the Apache License, Version 2.0 (the "License");
     5 *    you may not use this file except in compliance with the License.
     6 *    You may obtain a copy of the License at
    97 *
    10  * If you make use of this code, I'd appreciate hearing about it.
    11  *   drew@drewnoakes.com
    12  * Latest version of this software kept at
    13  *   http://drewnoakes.com/
     8 *        http://www.apache.org/licenses/LICENSE-2.0
    149 *
    15  * Created by dnoakes on 22-Nov-2002 16:45:19 using IntelliJ IDEA.
     10 *    Unless required by applicable law or agreed to in writing, software
     11 *    distributed under the License is distributed on an "AS IS" BASIS,
     12 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13 *    See the License for the specific language governing permissions and
     14 *    limitations under the License.
     15 *
     16 * More information about this project is available at:
     17 *
     18 *    http://drewnoakes.com/code/exif/
     19 *    http://code.google.com/p/metadata-extractor/
    1620 */
    1721package com.drew.metadata;
    1822
     23import com.drew.lang.annotations.NotNull;
     24
    1925/**
     26 * A default implementation of the abstract TagDescriptor.  As this class is not coded with awareness of any metadata
     27 * tags, it simply reports tag names using the format 'Unknown tag 0x00' (with the corresponding tag number in hex)
     28 * and gives descriptions using the default string representation of the value.
    2029 *
     30 * @author Drew Noakes http://drewnoakes.com
    2131 */
    22 public class DefaultTagDescriptor extends TagDescriptor
     32public class DefaultTagDescriptor extends TagDescriptor<Directory>
    2333{
    24     public DefaultTagDescriptor(Directory directory)
     34    public DefaultTagDescriptor(@NotNull Directory directory)
    2535    {
    2636        super(directory);
    2737    }
    2838
     39    /**
     40     * Gets a best-effort tag name using the format 'Unknown tag 0x00' (with the corresponding tag type in hex).
     41     * @param tagType the tag type identifier.
     42     * @return a string representation of the tag name.
     43     */
     44    @NotNull
    2945    public String getTagName(int tagType)
    3046    {
     
    3349        return "Unknown tag 0x" + hex;
    3450    }
    35 
    36     public String getDescription(int tagType)
    37     {
    38         return _directory.getString(tagType);
    39     }
    4051}
  • trunk/src/com/drew/metadata/Directory.java

    r4231 r6127  
    11/*
    2  * This is public domain software - that is, you can do whatever you want
    3  * with it, and include it software that is licensed under the GNU or the
    4  * BSD license, or whatever other licence you choose, including proprietary
    5  * closed source licenses.  I do ask that you leave this header in tact.
     2 * Copyright 2002-2012 Drew Noakes
    63 *
    7  * If you make modifications to this code that you think would benefit the
    8  * wider community, please send me a copy and I'll post it on my site.
     4 *    Licensed under the Apache License, Version 2.0 (the "License");
     5 *    you may not use this file except in compliance with the License.
     6 *    You may obtain a copy of the License at
    97 *
    10  * If you make use of this code, I'd appreciate hearing about it.
    11  *   drew@drewnoakes.com
    12  * Latest version of this software kept at
    13  *   http://drewnoakes.com/
     8 *        http://www.apache.org/licenses/LICENSE-2.0
    149 *
    15  * Created by dnoakes on 25-Nov-2002 20:30:39 using IntelliJ IDEA.
     10 *    Unless required by applicable law or agreed to in writing, software
     11 *    distributed under the License is distributed on an "AS IS" BASIS,
     12 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13 *    See the License for the specific language governing permissions and
     14 *    limitations under the License.
     15 *
     16 * More information about this project is available at:
     17 *
     18 *    http://drewnoakes.com/code/exif/
     19 *    http://code.google.com/p/metadata-extractor/
    1620 */
    1721package com.drew.metadata;
    1822
    1923import com.drew.lang.Rational;
    20 
    21 import java.io.Serializable;
     24import com.drew.lang.annotations.NotNull;
     25import com.drew.lang.annotations.Nullable;
     26import com.drew.lang.annotations.SuppressWarnings;
     27
     28import java.io.UnsupportedEncodingException;
    2229import java.lang.reflect.Array;
    2330import java.text.DateFormat;
    24 import java.util.ArrayList;
    25 import java.util.HashMap;
    26 import java.util.Iterator;
    27 import java.util.List;
     31import java.text.ParseException;
     32import java.text.SimpleDateFormat;
     33import java.util.*;
    2834
    2935/**
    30  * Base class for all Metadata directory types with supporting methods for setting and
    31  * getting tag values.
     36 * Abstract base class for all directory implementations, having methods for getting and setting tag values of various
     37 * data types.
     38 *
     39 * @author Drew Noakes http://drewnoakes.com
    3240 */
    33 public abstract class Directory implements Serializable
     41public abstract class Directory
    3442{
    35     /**
    36      * Map of values hashed by type identifiers.
    37      */
    38     protected final HashMap _tagMap;
    39 
    40     /**
    41      * The descriptor used to interperet tag values.
    42      */
    43     protected TagDescriptor _descriptor;
     43    // TODO get Array methods need to return cloned data, to maintain this directory's integrity
     44
     45    /** Map of values hashed by type identifiers. */
     46    @NotNull
     47    protected final Map<Integer, Object> _tagMap = new HashMap<Integer, Object>();
    4448
    4549    /**
     
    4852     * defined tags.
    4953     */
    50     protected final List _definedTagList;
    51 
    52     private List _errorList;
     54    @NotNull
     55    protected final Collection<Tag> _definedTagList = new ArrayList<Tag>();
     56
     57    @NotNull
     58    private final Collection<String> _errorList = new ArrayList<String>(4);
     59
     60    /** The descriptor used to interpret tag values. */
     61    protected TagDescriptor _descriptor;
    5362
    5463// ABSTRACT METHODS
     
    5665    /**
    5766     * Provides the name of the directory, for display purposes.  E.g. <code>Exif</code>
     67     *
    5868     * @return the name of the directory
    5969     */
     70    @NotNull
    6071    public abstract String getName();
    6172
    6273    /**
    6374     * Provides the map of tag names, hashed by tag type identifier.
     75     *
    6476     * @return the map of tag names
    6577     */
    66     protected abstract HashMap getTagNameMap();
    67 
    68 // CONSTRUCTORS
    69 
    70     /**
    71      * Creates a new Directory.
    72      */
    73     public Directory()
    74     {
    75         _tagMap = new HashMap();
    76         _definedTagList = new ArrayList();
    77     }
     78    @NotNull
     79    protected abstract HashMap<Integer, String> getTagNameMap();
     80
     81    protected Directory()
     82    {}
    7883
    7984// VARIOUS METHODS
     
    8186    /**
    8287     * Indicates whether the specified tag type has been set.
     88     *
    8389     * @param tagType the tag type to check for
    8490     * @return true if a value exists for the specified tag type, false if not
    8591     */
     92    @java.lang.SuppressWarnings({ "UnnecessaryBoxing" })
    8693    public boolean containsTag(int tagType)
    8794    {
    88         return _tagMap.containsKey(new Integer(tagType));
     95        return _tagMap.containsKey(Integer.valueOf(tagType));
    8996    }
    9097
    9198    /**
    9299     * Returns an Iterator of Tag instances that have been set in this Directory.
     100     *
    93101     * @return an Iterator of Tag instances
    94102     */
    95     public Iterator getTagIterator()
    96     {
    97         return _definedTagList.iterator();
     103    @NotNull
     104    public Collection<Tag> getTags()
     105    {
     106        return _definedTagList;
    98107    }
    99108
    100109    /**
    101110     * Returns the number of tags set in this Directory.
     111     *
    102112     * @return the number of tags set in this Directory
    103113     */
     
    108118
    109119    /**
    110      * Sets the descriptor used to interperet tag values.
    111      * @param descriptor the descriptor used to interperet tag values
    112      */
    113     public void setDescriptor(TagDescriptor descriptor)
    114     {
    115         if (descriptor==null) {
     120     * Sets the descriptor used to interpret tag values.
     121     *
     122     * @param descriptor the descriptor used to interpret tag values
     123     */
     124    @java.lang.SuppressWarnings({ "ConstantConditions" })
     125    public void setDescriptor(@NotNull TagDescriptor descriptor)
     126    {
     127        if (descriptor == null)
    116128            throw new NullPointerException("cannot set a null descriptor");
    117         }
    118129        _descriptor = descriptor;
    119130    }
    120131
    121     public void addError(String message)
    122     {
    123         if (_errorList==null) {
    124             _errorList = new ArrayList();
    125         }
     132    /**
     133     * Registers an error message with this directory.
     134     *
     135     * @param message an error message.
     136     */
     137    public void addError(@NotNull String message)
     138    {
    126139        _errorList.add(message);
    127140    }
    128141
     142    /**
     143     * Gets a value indicating whether this directory has any error messages.
     144     *
     145     * @return true if the directory contains errors, otherwise false
     146     */
    129147    public boolean hasErrors()
    130148    {
    131         return (_errorList!=null && _errorList.size()>0);
    132     }
    133 
    134     public Iterator getErrors()
    135     {
    136         return _errorList.iterator();
    137     }
    138 
     149        return _errorList.size() > 0;
     150    }
     151
     152    /**
     153     * Used to iterate over any error messages contained in this directory.
     154     *
     155     * @return an iterable collection of error message strings.
     156     */
     157    @NotNull
     158    public Iterable<String> getErrors()
     159    {
     160        return _errorList;
     161    }
     162
     163    /** Returns the count of error messages in this directory. */
    139164    public int getErrorCount()
    140165    {
     
    145170
    146171    /**
    147      * Sets an int value for the specified tag.
     172     * Sets an <code>int</code> value for the specified tag.
     173     *
    148174     * @param tagType the tag's value as an int
    149      * @param value the value for the specified tag as an int
     175     * @param value   the value for the specified tag as an int
    150176     */
    151177    public void setInt(int tagType, int value)
    152178    {
    153         setObject(tagType, new Integer(value));
    154     }
    155 
    156     /**
    157      * Sets a double value for the specified tag.
     179        setObject(tagType, value);
     180    }
     181
     182    /**
     183     * Sets an <code>int[]</code> (array) for the specified tag.
     184     *
     185     * @param tagType the tag identifier
     186     * @param ints    the int array to store
     187     */
     188    public void setIntArray(int tagType, @NotNull int[] ints)
     189    {
     190        setObjectArray(tagType, ints);
     191    }
     192
     193    /**
     194     * Sets a <code>float</code> value for the specified tag.
     195     *
    158196     * @param tagType the tag's value as an int
    159      * @param value the value for the specified tag as a double
     197     * @param value   the value for the specified tag as a float
     198     */
     199    public void setFloat(int tagType, float value)
     200    {
     201        setObject(tagType, value);
     202    }
     203
     204    /**
     205     * Sets a <code>float[]</code> (array) for the specified tag.
     206     *
     207     * @param tagType the tag identifier
     208     * @param floats  the float array to store
     209     */
     210    public void setFloatArray(int tagType, @NotNull float[] floats)
     211    {
     212        setObjectArray(tagType, floats);
     213    }
     214
     215    /**
     216     * Sets a <code>double</code> value for the specified tag.
     217     *
     218     * @param tagType the tag's value as an int
     219     * @param value   the value for the specified tag as a double
    160220     */
    161221    public void setDouble(int tagType, double value)
    162222    {
    163         setObject(tagType, new Double(value));
    164     }
    165 
    166     /**
    167      * Sets a float value for the specified tag.
     223        setObject(tagType, value);
     224    }
     225
     226    /**
     227     * Sets a <code>double[]</code> (array) for the specified tag.
     228     *
     229     * @param tagType the tag identifier
     230     * @param doubles the double array to store
     231     */
     232    public void setDoubleArray(int tagType, @NotNull double[] doubles)
     233    {
     234        setObjectArray(tagType, doubles);
     235    }
     236
     237    /**
     238     * Sets a <code>String</code> value for the specified tag.
     239     *
    168240     * @param tagType the tag's value as an int
    169      * @param value the value for the specified tag as a float
    170      */
    171     public void setFloat(int tagType, float value)
    172     {
    173         setObject(tagType, new Float(value));
    174     }
    175 
    176     /**
    177      * Sets an int value for the specified tag.
    178      * @param tagType the tag's value as an int
    179      * @param value the value for the specified tag as a String
    180      */
    181     public void setString(int tagType, String value)
    182     {
     241     * @param value   the value for the specified tag as a String
     242     */
     243    @java.lang.SuppressWarnings({ "ConstantConditions" })
     244    public void setString(int tagType, @NotNull String value)
     245    {
     246        if (value == null)
     247            throw new NullPointerException("cannot set a null String");
    183248        setObject(tagType, value);
    184249    }
    185250
    186251    /**
    187      * Sets an int value for the specified tag.
    188      * @param tagType the tag's value as an int
    189      * @param value the value for the specified tag as a boolean
    190      */
    191     public void setBoolean(int tagType, boolean value)
    192     {
    193         setObject(tagType, new Boolean(value));
    194     }
    195 
    196     /**
    197      * Sets a long value for the specified tag.
    198      * @param tagType the tag's value as an int
    199      * @param value the value for the specified tag as a long
    200      */
    201     public void setLong(int tagType, long value)
    202     {
    203         setObject(tagType, new Long(value));
    204     }
    205 
    206     /**
    207      * Sets a java.util.Date value for the specified tag.
    208      * @param tagType the tag's value as an int
    209      * @param value the value for the specified tag as a java.util.Date
    210      */
    211     public void setDate(int tagType, java.util.Date value)
    212     {
    213         setObject(tagType, value);
    214     }
    215 
    216     /**
    217      * Sets a Rational value for the specified tag.
    218      * @param tagType the tag's value as an int
    219      * @param rational rational number
    220      */
    221     public void setRational(int tagType, Rational rational)
    222     {
    223         setObject(tagType, rational);
    224     }
    225 
    226     /**
    227      * Sets a Rational array for the specified tag.
    228      * @param tagType the tag identifier
    229      * @param rationals the Rational array to store
    230      */
    231     public void setRationalArray(int tagType, Rational[] rationals)
    232     {
    233         setObjectArray(tagType, rationals);
    234     }
    235 
    236     /**
    237      * Sets an int array for the specified tag.
    238      * @param tagType the tag identifier
    239      * @param ints the int array to store
    240      */
    241     public void setIntArray(int tagType, int[] ints)
    242     {
    243         setObjectArray(tagType, ints);
    244     }
    245 
    246     /**
    247      * Sets a byte array for the specified tag.
    248      * @param tagType the tag identifier
    249      * @param bytes the byte array to store
    250      */
    251     public void setByteArray(int tagType, byte[] bytes)
    252     {
    253         setObjectArray(tagType, bytes);
    254     }
    255 
    256     /**
    257      * Sets a String array for the specified tag.
     252     * Sets a <code>String[]</code> (array) for the specified tag.
     253     *
    258254     * @param tagType the tag identifier
    259255     * @param strings the String array to store
    260256     */
    261     public void setStringArray(int tagType, String[] strings)
     257    public void setStringArray(int tagType, @NotNull String[] strings)
    262258    {
    263259        setObjectArray(tagType, strings);
     
    265261
    266262    /**
    267      * Private helper method, containing common functionality for all 'add'
    268      * methods.
     263     * Sets a <code>boolean</code> value for the specified tag.
     264     *
    269265     * @param tagType the tag's value as an int
    270      * @param value the value for the specified tag
     266     * @param value   the value for the specified tag as a boolean
     267     */
     268    public void setBoolean(int tagType, boolean value)
     269    {
     270        setObject(tagType, value);
     271    }
     272
     273    /**
     274     * Sets a <code>long</code> value for the specified tag.
     275     *
     276     * @param tagType the tag's value as an int
     277     * @param value   the value for the specified tag as a long
     278     */
     279    public void setLong(int tagType, long value)
     280    {
     281        setObject(tagType, value);
     282    }
     283
     284    /**
     285     * Sets a <code>java.util.Date</code> value for the specified tag.
     286     *
     287     * @param tagType the tag's value as an int
     288     * @param value   the value for the specified tag as a java.util.Date
     289     */
     290    public void setDate(int tagType, @NotNull java.util.Date value)
     291    {
     292        setObject(tagType, value);
     293    }
     294
     295    /**
     296     * Sets a <code>Rational</code> value for the specified tag.
     297     *
     298     * @param tagType  the tag's value as an int
     299     * @param rational rational number
     300     */
     301    public void setRational(int tagType, @NotNull Rational rational)
     302    {
     303        setObject(tagType, rational);
     304    }
     305
     306    /**
     307     * Sets a <code>Rational[]</code> (array) for the specified tag.
     308     *
     309     * @param tagType   the tag identifier
     310     * @param rationals the Rational array to store
     311     */
     312    public void setRationalArray(int tagType, @NotNull Rational[] rationals)
     313    {
     314        setObjectArray(tagType, rationals);
     315    }
     316
     317    /**
     318     * Sets a <code>byte[]</code> (array) for the specified tag.
     319     *
     320     * @param tagType the tag identifier
     321     * @param bytes   the byte array to store
     322     */
     323    public void setByteArray(int tagType, @NotNull byte[] bytes)
     324    {
     325        setObjectArray(tagType, bytes);
     326    }
     327
     328    /**
     329     * Sets a <code>Object</code> for the specified tag.
     330     *
     331     * @param tagType the tag's value as an int
     332     * @param value   the value for the specified tag
    271333     * @throws NullPointerException if value is <code>null</code>
    272334     */
    273     public void setObject(int tagType, Object value)
    274     {
    275         if (value==null) {
     335    @java.lang.SuppressWarnings( { "ConstantConditions", "UnnecessaryBoxing" })
     336    public void setObject(int tagType, @NotNull Object value)
     337    {
     338        if (value == null)
    276339            throw new NullPointerException("cannot set a null object");
    277         }
    278 
    279         Integer key = new Integer(tagType);
    280         if (!_tagMap.containsKey(key)) {
     340
     341        if (!_tagMap.containsKey(Integer.valueOf(tagType))) {
    281342            _definedTagList.add(new Tag(tagType, this));
    282343        }
    283         _tagMap.put(key, value);
    284     }
    285 
    286     /**
    287      * Private helper method, containing common functionality for all 'add...Array'
    288      * methods.
     344//        else {
     345//            final Object oldValue = _tagMap.get(tagType);
     346//            if (!oldValue.equals(value))
     347//                addError(String.format("Overwritten tag 0x%s (%s).  Old=%s, New=%s", Integer.toHexString(tagType), getTagName(tagType), oldValue, value));
     348//        }
     349        _tagMap.put(tagType, value);
     350    }
     351
     352    /**
     353     * Sets an array <code>Object</code> for the specified tag.
     354     *
    289355     * @param tagType the tag's value as an int
    290      * @param array the array of values for the specified tag
    291      */
    292     public void setObjectArray(int tagType, Object array)
     356     * @param array   the array of values for the specified tag
     357     */
     358    public void setObjectArray(int tagType, @NotNull Object array)
    293359    {
    294360        // for now, we don't do anything special -- this method might be a candidate for removal once the dust settles
     
    299365
    300366    /**
    301      * Returns the specified tag's value as an int, if possible.
     367     * Returns the specified tag's value as an int, if possible.  Every attempt to represent the tag's value as an int
     368     * is taken.  Here is a list of the action taken depending upon the tag's original type:
     369     * <ul>
     370     * <li> int - Return unchanged.
     371     * <li> Number - Return an int value (real numbers are truncated).
     372     * <li> Rational - Truncate any fractional part and returns remaining int.
     373     * <li> String - Attempt to parse string as an int.  If this fails, convert the char[] to an int (using shifts and OR).
     374     * <li> Rational[] - Return int value of first item in array.
     375     * <li> byte[] - Return int value of first item in array.
     376     * <li> int[] - Return int value of first item in array.
     377     * </ul>
     378     *
     379     * @throws MetadataException if no value exists for tagType or if it cannot be converted to an int.
    302380     */
    303381    public int getInt(int tagType) throws MetadataException
    304382    {
    305         Object o = getObject(tagType);
    306         if (o==null) {
    307             throw new MetadataException("Tag " + getTagName(tagType) + " has not been set -- check using containsTag() first");
    308         } else if (o instanceof String) {
     383        Integer integer = getInteger(tagType);
     384        if (integer!=null)
     385            return integer;
     386
     387        Object o = getObject(tagType);
     388        if (o == null)
     389            throw new MetadataException("Tag '" + getTagName(tagType) + "' has not been set -- check using containsTag() first");
     390        throw new MetadataException("Tag '" + tagType + "' cannot be converted to int.  It is of type '" + o.getClass() + "'.");
     391    }
     392
     393    /**
     394     * Returns the specified tag's value as an Integer, if possible.  Every attempt to represent the tag's value as an
     395     * Integer is taken.  Here is a list of the action taken depending upon the tag's original type:
     396     * <ul>
     397     * <li> int - Return unchanged
     398     * <li> Number - Return an int value (real numbers are truncated)
     399     * <li> Rational - Truncate any fractional part and returns remaining int
     400     * <li> String - Attempt to parse string as an int.  If this fails, convert the char[] to an int (using shifts and OR)
     401     * <li> Rational[] - Return int value of first item in array if length &gt; 0
     402     * <li> byte[] - Return int value of first item in array if length &gt; 0
     403     * <li> int[] - Return int value of first item in array if length &gt; 0
     404     * </ul>
     405     *
     406     * If the value is not found or cannot be converted to int, <code>null</code> is returned.
     407     */
     408    @Nullable
     409    public Integer getInteger(int tagType)
     410    {
     411        Object o = getObject(tagType);
     412
     413        if (o == null)
     414            return null;
     415
     416        if (o instanceof String) {
    309417            try {
    310418                return Integer.parseInt((String)o);
     
    314422                byte[] bytes = s.getBytes();
    315423                long val = 0;
    316                 for (int i = 0; i < bytes.length; i++) {
     424                for (byte aByte : bytes) {
    317425                    val = val << 8;
    318                     val += bytes[i];
     426                    val += (aByte & 0xff);
    319427                }
    320428                return (int)val;
     
    324432        } else if (o instanceof Rational[]) {
    325433            Rational[] rationals = (Rational[])o;
    326             if (rationals.length==1)
     434            if (rationals.length == 1)
    327435                return rationals[0].intValue();
    328436        } else if (o instanceof byte[]) {
    329437            byte[] bytes = (byte[])o;
    330             if (bytes.length==1)
    331                 return bytes[0];
     438            if (bytes.length == 1)
     439                return (int)bytes[0];
    332440        } else if (o instanceof int[]) {
    333441            int[] ints = (int[])o;
    334             if (ints.length==1)
     442            if (ints.length == 1)
    335443                return ints[0];
    336444        }
    337         throw new MetadataException("Tag '" + tagType + "' cannot be cast to int.  It is of type '" + o.getClass() + "'.");
    338     }
    339 
    340     // TODO get Array methods need to return cloned data, to maintain this directory's integrity
     445        return null;
     446    }
    341447
    342448    /**
    343449     * Gets the specified tag's value as a String array, if possible.  Only supported
    344450     * where the tag is set as String[], String, int[], byte[] or Rational[].
     451     *
    345452     * @param tagType the tag identifier
    346      * @return the tag's value as an array of Strings
    347      * @throws MetadataException if the tag has not been set or cannot be represented
    348      *         as a String[]
    349      */
    350     public String[] getStringArray(int tagType) throws MetadataException
    351     {
    352         Object o = getObject(tagType);
    353         if (o==null) {
    354             throw new MetadataException("Tag " + getTagName(tagType) + " has not been set -- check using containsTag() first");
    355         } else if (o instanceof String[]) {
     453     * @return the tag's value as an array of Strings. If the value is unset or cannot be converted, <code>null</code> is returned.
     454     */
     455    @Nullable
     456    public String[] getStringArray(int tagType)
     457    {
     458        Object o = getObject(tagType);
     459        if (o == null)
     460            return null;
     461        if (o instanceof String[])
    356462            return (String[])o;
    357         } else if (o instanceof String) {
    358             String[] strings = {(String)o};
    359             return strings;
    360         } else if (o instanceof int[]) {
     463        if (o instanceof String)
     464            return new String[] { (String)o };
     465        if (o instanceof int[]) {
    361466            int[] ints = (int[])o;
    362467            String[] strings = new String[ints.length];
    363             for (int i = 0; i<strings.length; i++) {
     468            for (int i = 0; i < strings.length; i++)
    364469                strings[i] = Integer.toString(ints[i]);
    365             }
    366470            return strings;
    367471        } else if (o instanceof byte[]) {
    368472            byte[] bytes = (byte[])o;
    369473            String[] strings = new String[bytes.length];
    370             for (int i = 0; i<strings.length; i++) {
     474            for (int i = 0; i < strings.length; i++)
    371475                strings[i] = Byte.toString(bytes[i]);
    372             }
    373476            return strings;
    374477        } else if (o instanceof Rational[]) {
    375478            Rational[] rationals = (Rational[])o;
    376479            String[] strings = new String[rationals.length];
    377             for (int i = 0; i<strings.length; i++) {
     480            for (int i = 0; i < strings.length; i++)
    378481                strings[i] = rationals[i].toSimpleString(false);
    379             }
    380482            return strings;
    381483        }
    382         throw new MetadataException("Tag '" + tagType + "' cannot be cast to an String array.  It is of type '" + o.getClass() + "'.");
     484        return null;
    383485    }
    384486
    385487    /**
    386488     * Gets the specified tag's value as an int array, if possible.  Only supported
    387      * where the tag is set as String, int[], byte[] or Rational[].
     489     * where the tag is set as String, Integer, int[], byte[] or Rational[].
     490     *
    388491     * @param tagType the tag identifier
    389492     * @return the tag's value as an int array
    390      * @throws MetadataException if the tag has not been set, or cannot be converted to
    391      *         an int array
    392      */
    393     public int[] getIntArray(int tagType) throws MetadataException
    394     {
    395         Object o = getObject(tagType);
    396         if (o==null) {
    397             throw new MetadataException("Tag " + getTagName(tagType) + " has not been set -- check using containsTag() first");
    398         } else if (o instanceof Rational[]) {
     493     */
     494    @Nullable
     495    public int[] getIntArray(int tagType)
     496    {
     497        Object o = getObject(tagType);
     498        if (o == null)
     499            return null;
     500        if (o instanceof Rational[]) {
    399501            Rational[] rationals = (Rational[])o;
    400502            int[] ints = new int[rationals.length];
    401             for (int i = 0; i<ints.length; i++) {
     503            for (int i = 0; i < ints.length; i++) {
    402504                ints[i] = rationals[i].intValue();
    403505            }
    404506            return ints;
    405         } else if (o instanceof int[]) {
     507        }
     508        if (o instanceof int[])
    406509            return (int[])o;
    407         } else if (o instanceof byte[]) {
     510        if (o instanceof byte[]) {
    408511            byte[] bytes = (byte[])o;
    409512            int[] ints = new int[bytes.length];
    410             for (int i = 0; i<bytes.length; i++) {
     513            for (int i = 0; i < bytes.length; i++) {
    411514                byte b = bytes[i];
    412515                ints[i] = b;
    413516            }
    414517            return ints;
    415         } else if (o instanceof String) {
    416             String str = (String)o;
     518        }
     519        if (o instanceof CharSequence) {
     520            CharSequence str = (CharSequence)o;
    417521            int[] ints = new int[str.length()];
    418             for (int i = 0; i<str.length(); i++) {
     522            for (int i = 0; i < str.length(); i++) {
    419523                ints[i] = str.charAt(i);
    420524            }
    421525            return ints;
    422526        }
    423         throw new MetadataException("Tag '" + tagType + "' cannot be cast to an int array.  It is of type '" + o.getClass() + "'.");
     527        if (o instanceof Integer)
     528            return new int[] { (Integer)o };
     529       
     530        return null;
    424531    }
    425532
    426533    /**
    427534     * Gets the specified tag's value as an byte array, if possible.  Only supported
    428      * where the tag is set as String, int[], byte[] or Rational[].
     535     * where the tag is set as String, Integer, int[], byte[] or Rational[].
     536     *
    429537     * @param tagType the tag identifier
    430538     * @return the tag's value as a byte array
    431      * @throws MetadataException if the tag has not been set, or cannot be converted to
    432      *         a byte array
    433      */
    434     public byte[] getByteArray(int tagType) throws MetadataException
    435     {
    436         Object o = getObject(tagType);
    437         if (o==null) {
    438             throw new MetadataException("Tag " + getTagName(tagType) + " has not been set -- check using containsTag() first");
     539     */
     540    @Nullable
     541    public byte[] getByteArray(int tagType)
     542    {
     543        Object o = getObject(tagType);
     544        if (o == null) {
     545            return null;
    439546        } else if (o instanceof Rational[]) {
    440547            Rational[] rationals = (Rational[])o;
    441548            byte[] bytes = new byte[rationals.length];
    442             for (int i = 0; i<bytes.length; i++) {
     549            for (int i = 0; i < bytes.length; i++) {
    443550                bytes[i] = rationals[i].byteValue();
    444551            }
     
    449556            int[] ints = (int[])o;
    450557            byte[] bytes = new byte[ints.length];
    451             for (int i = 0; i<ints.length; i++) {
     558            for (int i = 0; i < ints.length; i++) {
    452559                bytes[i] = (byte)ints[i];
    453560            }
    454561            return bytes;
    455         } else if (o instanceof String) {
    456             String str = (String)o;
     562        } else if (o instanceof CharSequence) {
     563            CharSequence str = (CharSequence)o;
    457564            byte[] bytes = new byte[str.length()];
    458             for (int i = 0; i<str.length(); i++) {
     565            for (int i = 0; i < str.length(); i++) {
    459566                bytes[i] = (byte)str.charAt(i);
    460567            }
    461568            return bytes;
    462569        }
    463         throw new MetadataException("Tag '" + tagType + "' cannot be cast to a byte array.  It is of type '" + o.getClass() + "'.");
    464     }
    465 
    466     /**
    467      * Returns the specified tag's value as a double, if possible.
    468      */
     570        if (o instanceof Integer)
     571            return new byte[] { ((Integer)o).byteValue() };
     572
     573        return null;
     574    }
     575
     576    /** Returns the specified tag's value as a double, if possible. */
    469577    public double getDouble(int tagType) throws MetadataException
    470578    {
    471         Object o = getObject(tagType);
    472         if (o==null) {
    473             throw new MetadataException("Tag " + getTagName(tagType) + " has not been set -- check using containsTag() first");
    474         } else if (o instanceof String) {
     579        Double value = getDoubleObject(tagType);
     580        if (value!=null)
     581            return value;
     582        Object o = getObject(tagType);
     583        if (o == null)
     584            throw new MetadataException("Tag '" + getTagName(tagType) + "' has not been set -- check using containsTag() first");
     585        throw new MetadataException("Tag '" + tagType + "' cannot be converted to a double.  It is of type '" + o.getClass() + "'.");
     586    }
     587    /** Returns the specified tag's value as a Double.  If the tag is not set or cannot be converted, <code>null</code> is returned. */
     588    @Nullable
     589    public Double getDoubleObject(int tagType)
     590    {
     591        Object o = getObject(tagType);
     592        if (o == null)
     593            return null;
     594        if (o instanceof String) {
    475595            try {
    476596                return Double.parseDouble((String)o);
    477597            } catch (NumberFormatException nfe) {
    478                 throw new MetadataException("unable to parse string " + o + " as a double", nfe);
    479             }
    480         } else if (o instanceof Number) {
     598                return null;
     599            }
     600        }
     601        if (o instanceof Number)
    481602            return ((Number)o).doubleValue();
    482         }
    483         throw new MetadataException("Tag '" + tagType + "' cannot be cast to a double.  It is of type '" + o.getClass() + "'.");
    484     }
    485 
    486     /**
    487      * Returns the specified tag's value as a float, if possible.
    488      */
     603
     604        return null;
     605    }
     606
     607    /** Returns the specified tag's value as a float, if possible. */
    489608    public float getFloat(int tagType) throws MetadataException
    490609    {
    491         Object o = getObject(tagType);
    492         if (o==null) {
    493             throw new MetadataException("Tag " + getTagName(tagType) + " has not been set -- check using containsTag() first");
    494         } else if (o instanceof String) {
     610        Float value = getFloatObject(tagType);
     611        if (value!=null)
     612            return value;
     613        Object o = getObject(tagType);
     614        if (o == null)
     615            throw new MetadataException("Tag '" + getTagName(tagType) + "' has not been set -- check using containsTag() first");
     616        throw new MetadataException("Tag '" + tagType + "' cannot be converted to a float.  It is of type '" + o.getClass() + "'.");
     617    }
     618
     619    /** Returns the specified tag's value as a float.  If the tag is not set or cannot be converted, <code>null</code> is returned. */
     620    @Nullable
     621    public Float getFloatObject(int tagType)
     622    {
     623        Object o = getObject(tagType);
     624        if (o == null)
     625            return null;
     626        if (o instanceof String) {
    495627            try {
    496628                return Float.parseFloat((String)o);
    497629            } catch (NumberFormatException nfe) {
    498                 throw new MetadataException("unable to parse string " + o + " as a float", nfe);
    499             }
    500         } else if (o instanceof Number) {
     630                return null;
     631            }
     632        }
     633        if (o instanceof Number)
    501634            return ((Number)o).floatValue();
    502         }
    503         throw new MetadataException("Tag '" + tagType + "' cannot be cast to a float.  It is of type '" + o.getClass() + "'.");
    504     }
    505 
    506     /**
    507      * Returns the specified tag's value as a long, if possible.
    508      */
     635        return null;
     636    }
     637
     638    /** Returns the specified tag's value as a long, if possible. */
    509639    public long getLong(int tagType) throws MetadataException
    510640    {
    511         Object o = getObject(tagType);
    512         if (o==null) {
    513             throw new MetadataException("Tag " + getTagName(tagType) + " has not been set -- check using containsTag() first");
    514         } else if (o instanceof String) {
     641        Long value = getLongObject(tagType);
     642        if (value!=null)
     643            return value;
     644        Object o = getObject(tagType);
     645        if (o == null)
     646            throw new MetadataException("Tag '" + getTagName(tagType) + "' has not been set -- check using containsTag() first");
     647        throw new MetadataException("Tag '" + tagType + "' cannot be converted to a long.  It is of type '" + o.getClass() + "'.");
     648    }
     649
     650    /** Returns the specified tag's value as a long.  If the tag is not set or cannot be converted, <code>null</code> is returned. */
     651    @Nullable
     652    public Long getLongObject(int tagType)
     653    {
     654        Object o = getObject(tagType);
     655        if (o == null)
     656            return null;
     657        if (o instanceof String) {
    515658            try {
    516659                return Long.parseLong((String)o);
    517660            } catch (NumberFormatException nfe) {
    518                 throw new MetadataException("unable to parse string " + o + " as a long", nfe);
    519             }
    520         } else if (o instanceof Number) {
     661                return null;
     662            }
     663        }
     664        if (o instanceof Number)
    521665            return ((Number)o).longValue();
    522         }
    523         throw new MetadataException("Tag '" + tagType + "' cannot be cast to a long.  It is of type '" + o.getClass() + "'.");
    524     }
    525 
    526     /**
    527      * Returns the specified tag's value as a boolean, if possible.
    528      */
     666        return null;
     667    }
     668
     669    /** Returns the specified tag's value as a boolean, if possible. */
    529670    public boolean getBoolean(int tagType) throws MetadataException
    530671    {
    531         Object o = getObject(tagType);
    532         if (o==null) {
    533             throw new MetadataException("Tag " + getTagName(tagType) + " has not been set -- check using containsTag() first");
    534         } else if (o instanceof Boolean) {
    535             return ((Boolean)o).booleanValue();
    536         } else if (o instanceof String) {
     672        Boolean value = getBooleanObject(tagType);
     673        if (value!=null)
     674            return value;
     675        Object o = getObject(tagType);
     676        if (o == null)
     677            throw new MetadataException("Tag '" + getTagName(tagType) + "' has not been set -- check using containsTag() first");
     678        throw new MetadataException("Tag '" + tagType + "' cannot be converted to a boolean.  It is of type '" + o.getClass() + "'.");
     679    }
     680
     681    /** Returns the specified tag's value as a boolean.  If the tag is not set or cannot be converted, <code>null</code> is returned. */
     682    @Nullable
     683    @SuppressWarnings(value = "NP_BOOLEAN_RETURN_NULL", justification = "keep API interface consistent")
     684    public Boolean getBooleanObject(int tagType)
     685    {
     686        Object o = getObject(tagType);
     687        if (o == null)
     688            return null;
     689        if (o instanceof Boolean)
     690            return (Boolean)o;
     691        if (o instanceof String) {
    537692            try {
    538693                return Boolean.getBoolean((String)o);
    539694            } catch (NumberFormatException nfe) {
    540                 throw new MetadataException("unable to parse string " + o + " as a boolean", nfe);
    541             }
    542         } else if (o instanceof Number) {
    543             return (((Number)o).doubleValue()!=0);
    544         }
    545         throw new MetadataException("Tag '" + tagType + "' cannot be cast to a boolean.  It is of type '" + o.getClass() + "'.");
    546     }
    547 
    548     /**
    549      * Returns the specified tag's value as a java.util.Date, if possible.
    550      */
    551     public java.util.Date getDate(int tagType) throws MetadataException
    552     {
    553         Object o = getObject(tagType);
    554         if (o==null) {
    555             throw new MetadataException("Tag " + getTagName(tagType) + " has not been set -- check using containsTag() first");
    556         } else if (o instanceof java.util.Date) {
     695                return null;
     696            }
     697        }
     698        if (o instanceof Number)
     699            return (((Number)o).doubleValue() != 0);
     700        return null;
     701    }
     702
     703    /**
     704     * Returns the specified tag's value as a java.util.Date.  If the value is unset or cannot be converted, <code>null</code> is returned.
     705     * <p/>
     706     * If the underlying value is a {@link String}, then attempts will be made to parse the string as though it is in
     707     * the current {@link TimeZone}.  If the {@link TimeZone} is known, call the overload that accepts one as an argument.
     708     */
     709    @Nullable
     710    public java.util.Date getDate(int tagType)
     711    {
     712        return getDate(tagType, null);
     713    }
     714   
     715    /**
     716     * Returns the specified tag's value as a java.util.Date.  If the value is unset or cannot be converted, <code>null</code> is returned.
     717     * <p/>
     718     * If the underlying value is a {@link String}, then attempts will be made to parse the string as though it is in
     719     * the {@link TimeZone} represented by the {@code timeZone} parameter (if it is non-null).  Note that this parameter
     720     * is only considered if the underlying value is a string and parsing occurs, otherwise it has no effect.
     721     */
     722    @Nullable
     723    public java.util.Date getDate(int tagType, @Nullable TimeZone timeZone)
     724    {
     725        Object o = getObject(tagType);
     726
     727        if (o == null)
     728            return null;
     729
     730        if (o instanceof java.util.Date)
    557731            return (java.util.Date)o;
    558         } else if (o instanceof String) {
    559             // add new dateformat strings to make this method even smarter
    560             // so far, this seems to cover all known date strings
    561             // (for example, AM and PM strings are not supported...)
     732
     733        if (o instanceof String) {
     734            // This seems to cover all known Exif date strings
     735            // Note that "    :  :     :  :  " is a valid date string according to the Exif spec (which means 'unknown date'): http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif/datetimeoriginal.html
    562736            String datePatterns[] = {
    563                 "yyyy:MM:dd HH:mm:ss",
    564                 "yyyy:MM:dd HH:mm",
    565                 "yyyy-MM-dd HH:mm:ss",
    566                 "yyyy-MM-dd HH:mm"};
     737                    "yyyy:MM:dd HH:mm:ss",
     738                    "yyyy:MM:dd HH:mm",
     739                    "yyyy-MM-dd HH:mm:ss",
     740                    "yyyy-MM-dd HH:mm",
     741                    "yyyy.MM.dd HH:mm:ss",
     742                    "yyyy.MM.dd HH:mm" };
    567743            String dateString = (String)o;
    568             for (int i = 0; i<datePatterns.length; i++) {
     744            for (String datePattern : datePatterns) {
    569745                try {
    570                     DateFormat parser = new java.text.SimpleDateFormat(datePatterns[i]);
     746                    DateFormat parser = new SimpleDateFormat(datePattern);
     747                    if (timeZone != null)
     748                        parser.setTimeZone(timeZone);
    571749                    return parser.parse(dateString);
    572                 } catch (java.text.ParseException ex) {
     750                } catch (ParseException ex) {
    573751                    // simply try the next pattern
    574752                }
    575753            }
    576754        }
    577         throw new MetadataException("Tag '" + tagType + "' cannot be cast to a java.util.Date.  It is of type '" + o.getClass() + "'.");
    578     }
    579 
    580     /**
    581      * Returns the specified tag's value as a Rational, if possible.
    582      */
    583     public Rational getRational(int tagType) throws MetadataException
    584     {
    585         Object o = getObject(tagType);
    586         if (o==null) {
    587             throw new MetadataException("Tag " + getTagName(tagType) + " has not been set -- check using containsTag() first");
    588         } else if (o instanceof Rational) {
     755        return null;
     756    }
     757
     758    /** Returns the specified tag's value as a Rational.  If the value is unset or cannot be converted, <code>null</code> is returned. */
     759    @Nullable
     760    public Rational getRational(int tagType)
     761    {
     762        Object o = getObject(tagType);
     763
     764        if (o == null)
     765            return null;
     766
     767        if (o instanceof Rational)
    589768            return (Rational)o;
    590         }
    591         throw new MetadataException("Tag '" + tagType + "' cannot be cast to a Rational.  It is of type '" + o.getClass() + "'.");
    592     }
    593 
    594     public Rational[] getRationalArray(int tagType) throws MetadataException
    595     {
    596         Object o = getObject(tagType);
    597         if (o==null) {
    598             throw new MetadataException("Tag " + getTagName(tagType) + " has not been set -- check using containsTag() first");
    599         } else if (o instanceof Rational[]) {
     769        if (o instanceof Integer)
     770            return new Rational((Integer)o, 1);
     771        if (o instanceof Long)
     772            return new Rational((Long)o, 1);
     773
     774        // NOTE not doing conversions for real number types
     775
     776        return null;
     777    }
     778
     779    /** Returns the specified tag's value as an array of Rational.  If the value is unset or cannot be converted, <code>null</code> is returned. */
     780    @Nullable
     781    public Rational[] getRationalArray(int tagType)
     782    {
     783        Object o = getObject(tagType);
     784        if (o == null)
     785            return null;
     786
     787        if (o instanceof Rational[])
    600788            return (Rational[])o;
    601         }
    602         throw new MetadataException("Tag '" + tagType + "' cannot be cast to a Rational array.  It is of type '" + o.getClass() + "'.");
     789
     790        return null;
    603791    }
    604792
     
    606794     * Returns the specified tag's value as a String.  This value is the 'raw' value.  A more presentable decoding
    607795     * of this value may be obtained from the corresponding Descriptor.
    608      * @return the String reprensentation of the tag's value, or
     796     *
     797     * @return the String representation of the tag's value, or
    609798     *         <code>null</code> if the tag hasn't been defined.
    610799     */
     800    @Nullable
    611801    public String getString(int tagType)
    612802    {
    613803        Object o = getObject(tagType);
    614         if (o==null)
     804        if (o == null)
    615805            return null;
    616806
     
    618808            return ((Rational)o).toSimpleString(true);
    619809
    620         if (o.getClass().isArray())
    621         {
     810        if (o.getClass().isArray()) {
    622811            // handle arrays of objects and primitives
    623812            int arrayLength = Array.getLength(o);
    624             // determine if this is an array of objects i.e. [Lcom.drew.blah
    625             boolean isObjectArray = o.getClass().toString().startsWith("class [L");
    626             StringBuffer sbuffer = new StringBuffer();
    627             for (int i = 0; i<arrayLength; i++)
    628             {
    629                 if (i!=0)
    630                     sbuffer.append(' ');
     813            final Class<?> componentType = o.getClass().getComponentType();
     814            boolean isObjectArray = Object.class.isAssignableFrom(componentType);
     815            boolean isFloatArray = componentType.getName().equals("float");
     816            boolean isDoubleArray = componentType.getName().equals("double");
     817            boolean isIntArray = componentType.getName().equals("int");
     818            boolean isLongArray = componentType.getName().equals("long");
     819            boolean isByteArray = componentType.getName().equals("byte");
     820            StringBuilder string = new StringBuilder();
     821            for (int i = 0; i < arrayLength; i++) {
     822                if (i != 0)
     823                    string.append(' ');
    631824                if (isObjectArray)
    632                     sbuffer.append(Array.get(o, i).toString());
     825                    string.append(Array.get(o, i).toString());
     826                else if (isIntArray)
     827                    string.append(Array.getInt(o, i));
     828                else if (isLongArray)
     829                    string.append(Array.getLong(o, i));
     830                else if (isFloatArray)
     831                    string.append(Array.getFloat(o, i));
     832                else if (isDoubleArray)
     833                    string.append(Array.getDouble(o, i));
     834                else if (isByteArray)
     835                    string.append(Array.getByte(o, i));
    633836                else
    634                     sbuffer.append(Array.getInt(o, i));
    635             }
    636             return sbuffer.toString();
    637         }
    638 
     837                    addError("Unexpected array component type: " + componentType.getName());
     838            }
     839            return string.toString();
     840        }
     841
     842        // Note that several cameras leave trailing spaces (Olympus, Nikon) but this library is intended to show
     843        // the actual data within the file.  It is not inconceivable that whitespace may be significant here, so we
     844        // do not trim.  Also, if support is added for writing data back to files, this may cause issues.
     845        // We leave trimming to the presentation layer.
    639846        return o.toString();
    640847    }
    641848
     849    @Nullable
     850    public String getString(int tagType, String charset)
     851    {
     852        byte[] bytes = getByteArray(tagType);
     853        if (bytes==null)
     854            return null;
     855        try {
     856            return new String(bytes, charset);
     857        } catch (UnsupportedEncodingException e) {
     858            return null;
     859        }
     860    }
     861
    642862    /**
    643863     * Returns the object hashed for the particular tag type specified, if available.
     864     *
    644865     * @param tagType the tag type identifier
    645      * @return the tag's value as an Object if available, else null
    646      */
     866     * @return the tag's value as an Object if available, else <code>null</code>
     867     */
     868    @java.lang.SuppressWarnings({ "UnnecessaryBoxing" })
     869    @Nullable
    647870    public Object getObject(int tagType)
    648871    {
    649         return _tagMap.get(new Integer(tagType));
     872        return _tagMap.get(Integer.valueOf(tagType));
    650873    }
    651874
     
    654877    /**
    655878     * Returns the name of a specified tag as a String.
     879     *
    656880     * @param tagType the tag type identifier
    657881     * @return the tag's name as a String
    658882     */
     883    @NotNull
    659884    public String getTagName(int tagType)
    660885    {
    661         Integer key = new Integer(tagType);
    662         HashMap nameMap = getTagNameMap();
    663         if (!nameMap.containsKey(key)) {
     886        HashMap<Integer, String> nameMap = getTagNameMap();
     887        if (!nameMap.containsKey(tagType)) {
    664888            String hex = Integer.toHexString(tagType);
    665             while (hex.length()<4) {
     889            while (hex.length() < 4) {
    666890                hex = "0" + hex;
    667891            }
    668892            return "Unknown tag (0x" + hex + ")";
    669893        }
    670         return (String)nameMap.get(key);
     894        return nameMap.get(tagType);
    671895    }
    672896
     
    674898     * Provides a description of a tag's value using the descriptor set by
    675899     * <code>setDescriptor(Descriptor)</code>.
     900     *
    676901     * @param tagType the tag type identifier
    677902     * @return the tag value's description as a String
    678      * @throws MetadataException if a descriptor hasn't been set, or if an error
    679      * occurs during calculation of the description within the Descriptor
    680      */
    681     public String getDescription(int tagType) throws MetadataException
    682     {
    683         if (_descriptor==null) {
    684             throw new MetadataException("a descriptor must be set using setDescriptor(...) before descriptions can be provided");
    685         }
    686 
     903     */
     904    @Nullable
     905    public String getDescription(int tagType)
     906    {
     907        assert(_descriptor != null);
    687908        return _descriptor.getDescription(tagType);
    688909    }
  • trunk/src/com/drew/metadata/Metadata.java

    r4231 r6127  
    11/*
    2  * Metadata.java
     2 * Copyright 2002-2012 Drew Noakes
    33 *
    4  * This class is public domain software - that is, you can do whatever you want
    5  * with it, and include it software that is licensed under the GNU or the
    6  * BSD license, or whatever other licence you choose, including proprietary
    7  * closed source licenses.  Similarly, I release this Java version under the
    8  * same license, though I do ask that you leave this header in tact.
     4 *    Licensed under the Apache License, Version 2.0 (the "License");
     5 *    you may not use this file except in compliance with the License.
     6 *    You may obtain a copy of the License at
    97 *
    10  * If you make modifications to this code that you think would benefit the
    11  * wider community, please send me a copy and I'll post it on my site.
     8 *        http://www.apache.org/licenses/LICENSE-2.0
    129 *
    13  * If you make use of this code, I'd appreciate hearing about it.
    14  *   drew.noakes@drewnoakes.com
    15  * Latest version of this software kept at
    16  *   http://drewnoakes.com/
     10 *    Unless required by applicable law or agreed to in writing, software
     11 *    distributed under the License is distributed on an "AS IS" BASIS,
     12 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13 *    See the License for the specific language governing permissions and
     14 *    limitations under the License.
    1715 *
    18  * Created on 28 April 2002, 17:40
    19  * Modified 04 Aug 2002
    20  * - Adjusted javadoc
    21  * - Added
    22  * Modified 29 Oct 2002 (v1.2)
    23  * - Stored IFD directories in separate tag-spaces
    24  * - iterator() now returns an Iterator over a list of TagValue objects
    25  * - More get*Description() methods to detail GPS tags, among others
    26  * - Put spaces between words of tag name for presentation reasons (they had no
    27  *   significance in compound form)
     16 * More information about this project is available at:
     17 *
     18 *    http://drewnoakes.com/code/exif/
     19 *    http://code.google.com/p/metadata-extractor/
    2820 */
    2921package com.drew.metadata;
    3022
    31 import java.io.Serializable;
     23import com.drew.lang.annotations.NotNull;
     24import com.drew.lang.annotations.Nullable;
     25
    3226import java.util.ArrayList;
     27import java.util.Collection;
    3328import java.util.HashMap;
    34 import java.util.Iterator;
     29import java.util.Map;
    3530
    3631/**
    37  * Result from an exif extraction operation, containing all tags, their
    38  * values and support for retrieving them.
    39  * @author  Drew Noakes http://drewnoakes.com
     32 * A top-level object to hold the various types of metadata (Exif/IPTC/etc) related to one entity (such as a file
     33 * or stream).
     34 * <p/>
     35 * Metadata objects may contain zero or more directories.  Each directory may contain zero or more tags with
     36 * corresponding values.
     37 *
     38 * @author Drew Noakes http://drewnoakes.com
    4039 */
    41 public final class Metadata implements Serializable
     40public final class Metadata
    4241{
    43     /**
    44      *
    45      */
    46     private final HashMap directoryMap;
    47 
     42    @NotNull
     43    private final Map<Class<? extends Directory>,Directory> _directoryByClass = new HashMap<Class<? extends Directory>, Directory>();
     44   
    4845    /**
    4946     * List of Directory objects set against this object.  Keeping a list handy makes
    5047     * creation of an Iterator and counting tags simple.
    5148     */
    52     private final ArrayList directoryList;
     49    @NotNull
     50    private final Collection<Directory> _directoryList = new ArrayList<Directory>();
    5351
    5452    /**
    55      * Creates a new instance of Metadata.  Package private.
     53     * Returns an objects for iterating over Directory objects in the order in which they were added.
     54     *
     55     * @return an iterable collection of directories
    5656     */
    57     public Metadata()
     57    @NotNull
     58    public Iterable<Directory> getDirectories()
    5859    {
    59         directoryMap = new HashMap();
    60         directoryList = new ArrayList();
    61     }
    62 
    63 
    64 // OTHER METHODS
    65 
    66     /**
    67      * Creates an Iterator over the tag types set against this image, preserving the order
    68      * in which they were set.  Should the same tag have been set more than once, it's first
    69      * position is maintained, even though the final value is used.
    70      * @return an Iterator of tag types set for this image
    71      */
    72     public Iterator getDirectoryIterator()
    73     {
    74         return directoryList.iterator();
     60        return _directoryList;
    7561    }
    7662
    7763    /**
    7864     * Returns a count of unique directories in this metadata collection.
     65     *
    7966     * @return the number of unique directory types set for this metadata collection
    8067     */
    8168    public int getDirectoryCount()
    8269    {
    83         return directoryList.size();
     70        return _directoryList.size();
    8471    }
    8572
     
    8875     * such a directory, it is returned.  Otherwise a new instance of this directory will be created and stored within
    8976     * this Metadata object.
     77     *
    9078     * @param type the type of the Directory implementation required.
    9179     * @return a directory of the specified type.
    9280     */
    93     public Directory getDirectory(Class type)
     81    @NotNull
     82    @SuppressWarnings("unchecked")
     83    public <T extends Directory> T getOrCreateDirectory(@NotNull Class<T> type)
    9484    {
    95         if (!Directory.class.isAssignableFrom(type)) {
    96             throw new RuntimeException("Class type passed to getDirectory must be an implementation of com.drew.metadata.Directory");
    97         }
     85        // We suppress the warning here as the code asserts a map signature of Class<T>,T.
     86        // So after get(Class<T>) it is for sure the result is from type T.
     87
    9888        // check if we've already issued this type of directory
    99         if (directoryMap.containsKey(type)) {
    100             return (Directory)directoryMap.get(type);
    101         }
    102         Object directory;
     89        if (_directoryByClass.containsKey(type))
     90            return (T)_directoryByClass.get(type);
     91
     92        T directory;
    10393        try {
    10494            directory = type.newInstance();
     
    10696            throw new RuntimeException("Cannot instantiate provided Directory type: " + type.toString());
    10797        }
    108         // store the directory in case it's requested later
    109         directoryMap.put(type, directory);
    110         directoryList.add(directory);
    111         return (Directory)directory;
     98        // store the directory
     99        _directoryByClass.put(type, directory);
     100        _directoryList.add(directory);
     101
     102        return directory;
     103    }
     104
     105    /**
     106     * If this <code>Metadata</code> object contains a <code>Directory</code> of the specified type, it is returned.
     107     * Otherwise <code>null</code> is returned.
     108     *
     109     * @param type the Directory type
     110     * @param <T> the Directory type
     111     * @return a Directory of type T if it exists in this Metadata object, otherwise <code>null</code>.
     112     */
     113    @Nullable
     114    @SuppressWarnings("unchecked")
     115    public <T extends Directory> T getDirectory(@NotNull Class<T> type)
     116    {
     117        // We suppress the warning here as the code asserts a map signature of Class<T>,T.
     118        // So after get(Class<T>) it is for sure the result is from type T.
     119
     120        return (T)_directoryByClass.get(type);
    112121    }
    113122
    114123    /**
    115124     * Indicates whether a given directory type has been created in this metadata
    116      * repository.  Directories are created by calling getDirectory(Class).
     125     * repository.  Directories are created by calling <code>getOrCreateDirectory(Class)</code>.
     126     *
    117127     * @param type the Directory type
    118128     * @return true if the metadata directory has been created
    119129     */
    120     public boolean containsDirectory(Class type)
     130    public boolean containsDirectory(Class<? extends Directory> type)
    121131    {
    122         return directoryMap.containsKey(type);
     132        return _directoryByClass.containsKey(type);
     133    }
     134
     135    /**
     136     * Indicates whether any errors were reported during the reading of metadata values.
     137     * This value will be true if Directory.hasErrors() is true for one of the contained Directory objects.
     138     *
     139     * @return whether one of the contained directories has an error
     140     */
     141    public boolean hasErrors()
     142    {
     143        for (Directory directory : _directoryList) {
     144            if (directory.hasErrors())
     145                return true;
     146        }
     147        return false;
    123148    }
    124149}
  • trunk/src/com/drew/metadata/MetadataException.java

    r4231 r6127  
    11/*
    2  * This is public domain software - that is, you can do whatever you want
    3  * with it, and include it software that is licensed under the GNU or the
    4  * BSD license, or whatever other licence you choose, including proprietary
    5  * closed source licenses.  I do ask that you leave this header in tact.
     2 * Copyright 2002-2012 Drew Noakes
    63 *
    7  * If you make modifications to this code that you think would benefit the
    8  * wider community, please send me a copy and I'll post it on my site.
     4 *    Licensed under the Apache License, Version 2.0 (the "License");
     5 *    you may not use this file except in compliance with the License.
     6 *    You may obtain a copy of the License at
    97 *
    10  * If you make use of this code, I'd appreciate hearing about it.
    11  *   drew@drewnoakes.com
    12  * Latest version of this software kept at
    13  *   http://drewnoakes.com/
     8 *        http://www.apache.org/licenses/LICENSE-2.0
    149 *
    15  * Created by dnoakes on 13-Nov-2002 18:10:23 using IntelliJ IDEA.
     10 *    Unless required by applicable law or agreed to in writing, software
     11 *    distributed under the License is distributed on an "AS IS" BASIS,
     12 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13 *    See the License for the specific language governing permissions and
     14 *    limitations under the License.
     15 *
     16 * More information about this project is available at:
     17 *
     18 *    http://drewnoakes.com/code/exif/
     19 *    http://code.google.com/p/metadata-extractor/
    1620 */
    1721package com.drew.metadata;
    1822
    1923import com.drew.lang.CompoundException;
     24import com.drew.lang.annotations.Nullable;
    2025
    2126/**
     27 * Base class for all metadata specific exceptions.
    2228 *
     29 * @author Drew Noakes http://drewnoakes.com
    2330 */
    2431public class MetadataException extends CompoundException
    2532{
    26     public MetadataException(String msg)
     33    private static final long serialVersionUID = 8612756143363919682L;
     34
     35    public MetadataException(@Nullable String msg)
    2736    {
    2837        super(msg);
    2938    }
    3039
    31     public MetadataException(Throwable exception)
     40    public MetadataException(@Nullable Throwable exception)
    3241    {
    3342        super(exception);
    3443    }
    3544
    36     public MetadataException(String msg, Throwable innerException)
     45    public MetadataException(@Nullable String msg, @Nullable Throwable innerException)
    3746    {
    3847        super(msg, innerException);
  • trunk/src/com/drew/metadata/MetadataReader.java

    r4231 r6127  
    11/*
    2  * This is public domain software - that is, you can do whatever you want
    3  * with it, and include it software that is licensed under the GNU or the
    4  * BSD license, or whatever other licence you choose, including proprietary
    5  * closed source licenses.  I do ask that you leave this header in tact.
     2 * Copyright 2002-2012 Drew Noakes
    63 *
    7  * If you make modifications to this code that you think would benefit the
    8  * wider community, please send me a copy and I'll post it on my site.
     4 *    Licensed under the Apache License, Version 2.0 (the "License");
     5 *    you may not use this file except in compliance with the License.
     6 *    You may obtain a copy of the License at
    97 *
    10  * If you make use of this code, I'd appreciate hearing about it.
    11  *   drew@drewnoakes.com
    12  * Latest version of this software kept at
    13  *   http://drewnoakes.com/
     8 *        http://www.apache.org/licenses/LICENSE-2.0
    149 *
    15  * Created by dnoakes on 26-Nov-2002 11:21:43 using IntelliJ IDEA.
     10 *    Unless required by applicable law or agreed to in writing, software
     11 *    distributed under the License is distributed on an "AS IS" BASIS,
     12 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13 *    See the License for the specific language governing permissions and
     14 *    limitations under the License.
     15 *
     16 * More information about this project is available at:
     17 *
     18 *    http://drewnoakes.com/code/exif/
     19 *    http://code.google.com/p/metadata-extractor/
    1620 */
    1721package com.drew.metadata;
    1822
     23import com.drew.lang.BufferReader;
     24import com.drew.lang.annotations.NotNull;
     25
    1926/**
     27 * Interface through which all classes responsible for decoding a particular type of metadata may be called.
     28 * Note that the data source is not specified on this interface.  Instead it is suggested that implementations
     29 * take their data within a constructor.  Constructors might be overloaded to allow for different sources, such as
     30 * files, streams and byte arrays.  As such, instances of implementations of this interface would be single-use and
     31 * not thread-safe.
    2032 *
     33 * @author Drew Noakes http://drewnoakes.com
    2134 */
    2235public interface MetadataReader
    2336{
    24     public Metadata extract();
    25 
    26     public Metadata extract(Metadata metadata);
     37    /**
     38     * Extract metadata from the source and merge it into an existing Metadata object.
     39     *
     40     * @param reader   The reader from which the metadata should be extracted.
     41     * @param metadata The Metadata object into which extracted values should be merged.
     42     */
     43    public void extract(@NotNull final BufferReader reader, @NotNull final Metadata metadata);
    2744}
  • trunk/src/com/drew/metadata/Tag.java

    r4231 r6127  
    11/*
    2  * This is public domain software - that is, you can do whatever you want
    3  * with it, and include it software that is licensed under the GNU or the
    4  * BSD license, or whatever other licence you choose, including proprietary
    5  * closed source licenses.  I do ask that you leave this header in tact.
     2 * Copyright 2002-2012 Drew Noakes
    63 *
    7  * If you make modifications to this code that you think would benefit the
    8  * wider community, please send me a copy and I'll post it on my site.
     4 *    Licensed under the Apache License, Version 2.0 (the "License");
     5 *    you may not use this file except in compliance with the License.
     6 *    You may obtain a copy of the License at
    97 *
    10  * If you make use of this code, I'd appreciate hearing about it.
    11  *   drew@drewnoakes.com
    12  * Latest version of this software kept at
    13  *   http://drewnoakes.com/
     8 *        http://www.apache.org/licenses/LICENSE-2.0
    149 *
    15  * Created by dnoakes on 26-Nov-2002 18:29:12 using IntelliJ IDEA.
     10 *    Unless required by applicable law or agreed to in writing, software
     11 *    distributed under the License is distributed on an "AS IS" BASIS,
     12 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13 *    See the License for the specific language governing permissions and
     14 *    limitations under the License.
     15 *
     16 * More information about this project is available at:
     17 *
     18 *    http://drewnoakes.com/code/exif/
     19 *    http://code.google.com/p/metadata-extractor/
    1620 */
    1721package com.drew.metadata;
    1822
    19 import java.io.Serializable;
     23import com.drew.lang.annotations.NotNull;
     24import com.drew.lang.annotations.Nullable;
    2025
    2126/**
     27 * Models a particular tag within a directory and provides methods for obtaining its value.  Note that a Tag instance is
     28 * specific to a particular metadata extraction and cannot be reused.
    2229 *
     30 * @author Drew Noakes http://drewnoakes.com
    2331 */
    24 public class Tag implements Serializable
     32public class Tag
    2533{
    2634    private final int _tagType;
     35    @NotNull
    2736    private final Directory _directory;
    2837
    29     public Tag(int tagType, Directory directory)
     38    public Tag(int tagType, @NotNull Directory directory)
    3039    {
    3140        _tagType = tagType;
     
    3544    /**
    3645     * Gets the tag type as an int
     46     *
    3747     * @return the tag type as an int
    3848     */
     
    4555     * Gets the tag type in hex notation as a String with padded leading
    4656     * zeroes if necessary (i.e. <code>0x100E</code>).
     57     *
    4758     * @return the tag type as a string in hexadecimal notation
    4859     */
     60    @NotNull
    4961    public String getTagTypeHex()
    5062    {
     
    5769     * Get a description of the tag's value, considering enumerated values
    5870     * and units.
     71     *
    5972     * @return a description of the tag's value
    6073     */
    61     public String getDescription() throws MetadataException
     74    @Nullable
     75    public String getDescription()
    6276    {
    6377        return _directory.getDescription(_tagType);
     
    6781     * Get the name of the tag, such as <code>Aperture</code>, or
    6882     * <code>InteropVersion</code>.
     83     *
    6984     * @return the tag's name
    7085     */
     86    @NotNull
    7187    public String getTagName()
    7288    {
     
    7793     * Get the name of the directory in which the tag exists, such as
    7894     * <code>Exif</code>, <code>GPS</code> or <code>Interoperability</code>.
     95     *
    7996     * @return name of the directory in which this tag exists
    8097     */
     98    @NotNull
    8199    public String getDirectoryName()
    82100    {
     
    85103
    86104    /**
    87      * A basic representation of the tag's type and value in format:
    88      * <code>FNumber - F2.8</code>.
     105     * A basic representation of the tag's type and value.  EG: <code>[FNumber] F2.8</code>.
     106     *
    89107     * @return the tag's type and value
    90108     */
     109    @NotNull
    91110    public String toString()
    92111    {
    93         String description;
    94         try {
    95             description = getDescription();
    96         } catch (MetadataException e) {
     112        String description = getDescription();
     113        if (description==null)
    97114            description = _directory.getString(getTagType()) + " (unable to formulate description)";
    98         }
    99115        return "[" + _directory.getName() + "] " + getTagName() + " - " + description;
    100116    }
  • trunk/src/com/drew/metadata/TagDescriptor.java

    r4231 r6127  
    11/*
    2  * This is public domain software - that is, you can do whatever you want
    3  * with it, and include it software that is licensed under the GNU or the
    4  * BSD license, or whatever other licence you choose, including proprietary
    5  * closed source licenses.  I do ask that you leave this header in tact.
     2 * Copyright 2002-2012 Drew Noakes
    63 *
    7  * If you make modifications to this code that you think would benefit the
    8  * wider community, please send me a copy and I'll post it on my site.
     4 *    Licensed under the Apache License, Version 2.0 (the "License");
     5 *    you may not use this file except in compliance with the License.
     6 *    You may obtain a copy of the License at
    97 *
    10  * If you make use of this code, I'd appreciate hearing about it.
    11  *   drew@drewnoakes.com
    12  * Latest version of this software kept at
    13  *   http://drewnoakes.com/
     8 *        http://www.apache.org/licenses/LICENSE-2.0
     9 *
     10 *    Unless required by applicable law or agreed to in writing, software
     11 *    distributed under the License is distributed on an "AS IS" BASIS,
     12 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13 *    See the License for the specific language governing permissions and
     14 *    limitations under the License.
     15 *
     16 * More information about this project is available at:
     17 *
     18 *    http://drewnoakes.com/code/exif/
     19 *    http://code.google.com/p/metadata-extractor/
    1420 */
    1521package com.drew.metadata;
    1622
    17 import java.io.Serializable;
     23import com.drew.lang.annotations.NotNull;
     24import com.drew.lang.annotations.Nullable;
     25
     26import java.lang.reflect.Array;
    1827
    1928/**
    2029 * Abstract base class for all tag descriptor classes.  Implementations are responsible for
    21  * providing the human-readable string represenation of tag values stored in a directory.
     30 * providing the human-readable string representation of tag values stored in a directory.
    2231 * The directory is provided to the tag descriptor via its constructor.
     32 *
     33 * @author Drew Noakes http://drewnoakes.com
    2334 */
    24 public abstract class TagDescriptor implements Serializable
     35public abstract class TagDescriptor<T extends Directory>
    2536{
    26     protected final Directory _directory;
     37    @NotNull
     38    protected final T _directory;
    2739
    28     public TagDescriptor(Directory directory)
     40    public TagDescriptor(@NotNull T directory)
    2941    {
    3042        _directory = directory;
     
    3446     * Returns a descriptive value of the the specified tag for this image.
    3547     * Where possible, known values will be substituted here in place of the raw
    36      * tokens actually kept in the Exif segment.  If no substitution is
    37      * available, the value provided by getString(int) will be returned.
    38      * <p>
    39      * This and getString(int) are the only 'get' methods that won't throw an
    40      * exception.
     48     * tokens actually kept in the metadata segment.  If no substitution is
     49     * available, the value provided by <code>getString(tagType)</code> will be returned.
     50     *
    4151     * @param tagType the tag to find a description for
    4252     * @return a description of the image's value for the specified tag, or
    4353     *         <code>null</code> if the tag hasn't been defined.
    4454     */
    45     public abstract String getDescription(int tagType) throws MetadataException;
     55    @Nullable
     56    public String getDescription(int tagType)
     57    {
     58        Object object = _directory.getObject(tagType);
     59
     60        if (object==null)
     61            return null;
     62
     63        // special presentation for long arrays
     64        if (object.getClass().isArray()) {
     65            final int length = Array.getLength(object);
     66            if (length > 16) {
     67                final String componentTypeName = object.getClass().getComponentType().getName();
     68                return String.format("[%d %s%s]", length, componentTypeName, length==1 ? "" : "s");
     69            }
     70        }
     71
     72        // no special handling required, so use default conversion to a string
     73        return _directory.getString(tagType);
     74    }
     75
     76    /**
     77     * Takes a series of 4 bytes from the specified offset, and converts these to a
     78     * well-known version number, where possible.
     79     * <p/>
     80     * Two different formats are processed:
     81     * <ul>
     82     *     <li>[30 32 31 30] -&gt; 2.10</li>
     83     *     <li>[0 1 0 0] -&gt; 1.00</li>
     84     * </ul>
     85     * @param components the four version values
     86     * @param majorDigits the number of components to be
     87     * @return the version as a string of form "2.10" or null if the argument cannot be converted
     88     */
     89    @Nullable
     90    public static String convertBytesToVersionString(@Nullable int[] components, final int majorDigits)
     91    {
     92        if (components==null)
     93            return null;
     94        StringBuilder version = new StringBuilder();
     95        for (int i = 0; i < 4 && i < components.length; i++) {
     96            if (i == majorDigits)
     97                version.append('.');
     98            char c = (char)components[i];
     99            if (c < '0')
     100                c += '0';
     101            if (i == 0 && c=='0')
     102                continue;
     103            version.append(c);
     104        }
     105        return version.toString();
     106    }
    46107}
  • trunk/src/com/drew/metadata/exif/CanonMakernoteDescriptor.java

    r4231 r6127  
    11/*
    2  * This is public domain software - that is, you can do whatever you want
    3  * with it, and include it software that is licensed under the GNU or the
    4  * BSD license, or whatever other licence you choose, including proprietary
    5  * closed source licenses.  I do ask that you leave this header in tact.
     2 * Copyright 2002-2012 Drew Noakes
    63 *
    7  * If you make modifications to this code that you think would benefit the
    8  * wider community, please send me a copy and I'll post it on my site.
     4 *    Licensed under the Apache License, Version 2.0 (the "License");
     5 *    you may not use this file except in compliance with the License.
     6 *    You may obtain a copy of the License at
    97 *
    10  * If you make use of this code, I'd appreciate hearing about it.
    11  *   drew@drewnoakes.com
    12  * Latest version of this software kept at
    13  *   http://drewnoakes.com/
     8 *        http://www.apache.org/licenses/LICENSE-2.0
    149 *
    15  * Created by dnoakes on 27-Nov-2002 10:12:05 using IntelliJ IDEA.
     10 *    Unless required by applicable law or agreed to in writing, software
     11 *    distributed under the License is distributed on an "AS IS" BASIS,
     12 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13 *    See the License for the specific language governing permissions and
     14 *    limitations under the License.
     15 *
     16 * More information about this project is available at:
     17 *
     18 *    http://drewnoakes.com/code/exif/
     19 *    http://code.google.com/p/metadata-extractor/
    1620 */
    1721package com.drew.metadata.exif;
    1822
    19 import com.drew.metadata.Directory;
    20 import com.drew.metadata.MetadataException;
     23import com.drew.lang.annotations.NotNull;
     24import com.drew.lang.annotations.Nullable;
    2125import com.drew.metadata.TagDescriptor;
    2226
    2327/**
     28 * Provides human-readable string representations of tag values stored in a <code>CanonMakernoteDirectory</code>.
    2429 *
     30 * @author Drew Noakes http://drewnoakes.com
    2531 */
    26 public class CanonMakernoteDescriptor extends TagDescriptor
     32public class CanonMakernoteDescriptor extends TagDescriptor<CanonMakernoteDirectory>
    2733{
    28     public CanonMakernoteDescriptor(Directory directory)
     34    public CanonMakernoteDescriptor(@NotNull CanonMakernoteDirectory directory)
    2935    {
    3036        super(directory);
    3137    }
    3238
    33     public String getDescription(int tagType) throws MetadataException
     39    @Nullable
     40    public String getDescription(int tagType)
    3441    {
    3542        switch (tagType) {
    36             case CanonMakernoteDirectory.TAG_CANON_STATE1_FLASH_ACTIVITY:
     43            case CanonMakernoteDirectory.TAG_CANON_SERIAL_NUMBER:
     44                return getSerialNumberDescription();
     45            case CanonMakernoteDirectory.CameraSettings.TAG_FLASH_ACTIVITY:
    3746                return getFlashActivityDescription();
    38             case CanonMakernoteDirectory.TAG_CANON_STATE1_FOCUS_TYPE:
     47            case CanonMakernoteDirectory.CameraSettings.TAG_FOCUS_TYPE:
    3948                return getFocusTypeDescription();
    40             case CanonMakernoteDirectory.TAG_CANON_STATE1_DIGITAL_ZOOM:
     49            case CanonMakernoteDirectory.CameraSettings.TAG_DIGITAL_ZOOM:
    4150                return getDigitalZoomDescription();
    42             case CanonMakernoteDirectory.TAG_CANON_STATE1_QUALITY:
     51            case CanonMakernoteDirectory.CameraSettings.TAG_QUALITY:
    4352                return getQualityDescription();
    44             case CanonMakernoteDirectory.TAG_CANON_STATE1_MACRO_MODE:
     53            case CanonMakernoteDirectory.CameraSettings.TAG_MACRO_MODE:
    4554                return getMacroModeDescription();
    46             case CanonMakernoteDirectory.TAG_CANON_STATE1_SELF_TIMER_DELAY:
     55            case CanonMakernoteDirectory.CameraSettings.TAG_SELF_TIMER_DELAY:
    4756                return getSelfTimerDelayDescription();
    48             case CanonMakernoteDirectory.TAG_CANON_STATE1_FLASH_MODE:
     57            case CanonMakernoteDirectory.CameraSettings.TAG_FLASH_MODE:
    4958                return getFlashModeDescription();
    50             case CanonMakernoteDirectory.TAG_CANON_STATE1_CONTINUOUS_DRIVE_MODE:
     59            case CanonMakernoteDirectory.CameraSettings.TAG_CONTINUOUS_DRIVE_MODE:
    5160                return getContinuousDriveModeDescription();
    52             case CanonMakernoteDirectory.TAG_CANON_STATE1_FOCUS_MODE_1:
     61            case CanonMakernoteDirectory.CameraSettings.TAG_FOCUS_MODE_1:
    5362                return getFocusMode1Description();
    54             case CanonMakernoteDirectory.TAG_CANON_STATE1_IMAGE_SIZE:
     63            case CanonMakernoteDirectory.CameraSettings.TAG_IMAGE_SIZE:
    5564                return getImageSizeDescription();
    56             case CanonMakernoteDirectory.TAG_CANON_STATE1_EASY_SHOOTING_MODE:
     65            case CanonMakernoteDirectory.CameraSettings.TAG_EASY_SHOOTING_MODE:
    5766                return getEasyShootingModeDescription();
    58             case CanonMakernoteDirectory.TAG_CANON_STATE1_CONTRAST:
     67            case CanonMakernoteDirectory.CameraSettings.TAG_CONTRAST:
    5968                return getContrastDescription();
    60             case CanonMakernoteDirectory.TAG_CANON_STATE1_SATURATION:
     69            case CanonMakernoteDirectory.CameraSettings.TAG_SATURATION:
    6170                return getSaturationDescription();
    62             case CanonMakernoteDirectory.TAG_CANON_STATE1_SHARPNESS:
     71            case CanonMakernoteDirectory.CameraSettings.TAG_SHARPNESS:
    6372                return getSharpnessDescription();
    64             case CanonMakernoteDirectory.TAG_CANON_STATE1_ISO:
     73            case CanonMakernoteDirectory.CameraSettings.TAG_ISO:
    6574                return getIsoDescription();
    66             case CanonMakernoteDirectory.TAG_CANON_STATE1_METERING_MODE:
     75            case CanonMakernoteDirectory.CameraSettings.TAG_METERING_MODE:
    6776                return getMeteringModeDescription();
    68             case CanonMakernoteDirectory.TAG_CANON_STATE1_AF_POINT_SELECTED:
     77            case CanonMakernoteDirectory.CameraSettings.TAG_AF_POINT_SELECTED:
    6978                return getAfPointSelectedDescription();
    70             case CanonMakernoteDirectory.TAG_CANON_STATE1_EXPOSURE_MODE:
     79            case CanonMakernoteDirectory.CameraSettings.TAG_EXPOSURE_MODE:
    7180                return getExposureModeDescription();
    72             case CanonMakernoteDirectory.TAG_CANON_STATE1_LONG_FOCAL_LENGTH:
     81            case CanonMakernoteDirectory.CameraSettings.TAG_LONG_FOCAL_LENGTH:
    7382                return getLongFocalLengthDescription();
    74             case CanonMakernoteDirectory.TAG_CANON_STATE1_SHORT_FOCAL_LENGTH:
     83            case CanonMakernoteDirectory.CameraSettings.TAG_SHORT_FOCAL_LENGTH:
    7584                return getShortFocalLengthDescription();
    76             case CanonMakernoteDirectory.TAG_CANON_STATE1_FOCAL_UNITS_PER_MM:
     85            case CanonMakernoteDirectory.CameraSettings.TAG_FOCAL_UNITS_PER_MM:
    7786                return getFocalUnitsPerMillimetreDescription();
    78             case CanonMakernoteDirectory.TAG_CANON_STATE1_FLASH_DETAILS:
     87            case CanonMakernoteDirectory.CameraSettings.TAG_FLASH_DETAILS:
    7988                return getFlashDetailsDescription();
    80             case CanonMakernoteDirectory.TAG_CANON_STATE1_FOCUS_MODE_2:
     89            case CanonMakernoteDirectory.CameraSettings.TAG_FOCUS_MODE_2:
    8190                return getFocusMode2Description();
    82             case CanonMakernoteDirectory.TAG_CANON_STATE2_WHITE_BALANCE:
     91            case CanonMakernoteDirectory.FocalLength.TAG_WHITE_BALANCE:
    8392                return getWhiteBalanceDescription();
    84             case CanonMakernoteDirectory.TAG_CANON_STATE2_AF_POINT_USED:
     93            case CanonMakernoteDirectory.FocalLength.TAG_AF_POINT_USED:
    8594                return getAfPointUsedDescription();
    86             case CanonMakernoteDirectory.TAG_CANON_STATE2_FLASH_BIAS:
     95            case CanonMakernoteDirectory.FocalLength.TAG_FLASH_BIAS:
    8796                return getFlashBiasDescription();
    88             case CanonMakernoteDirectory.TAG_CANON_CUSTOM_FUNCTION_LONG_EXPOSURE_NOISE_REDUCTION:
    89                 return getLongExposureNoiseReductionDescription();
    90             case CanonMakernoteDirectory.TAG_CANON_CUSTOM_FUNCTION_SHUTTER_AUTO_EXPOSURE_LOCK_BUTTONS:
    91                 return getShutterAutoExposureLockButtonDescription();
    92             case CanonMakernoteDirectory.TAG_CANON_CUSTOM_FUNCTION_MIRROR_LOCKUP:
    93                 return getMirrorLockupDescription();
    94             case CanonMakernoteDirectory.TAG_CANON_CUSTOM_FUNCTION_TV_AV_AND_EXPOSURE_LEVEL:
    95                 return getTvAndAvExposureLevelDescription();
    96             case CanonMakernoteDirectory.TAG_CANON_CUSTOM_FUNCTION_AF_ASSIST_LIGHT:
    97                 return getAutoFocusAssistLightDescription();
    98             case CanonMakernoteDirectory.TAG_CANON_CUSTOM_FUNCTION_SHUTTER_SPEED_IN_AV_MODE:
    99                 return getShutterSpeedInAvModeDescription();
    100             case CanonMakernoteDirectory.TAG_CANON_CUSTOM_FUNCTION_BRACKETTING:
    101                 return getAutoExposureBrackettingSequenceAndAutoCancellationDescription();
    102             case CanonMakernoteDirectory.TAG_CANON_CUSTOM_FUNCTION_SHUTTER_CURTAIN_SYNC:
    103                 return getShutterCurtainSyncDescription();
    104             case CanonMakernoteDirectory.TAG_CANON_CUSTOM_FUNCTION_AF_STOP:
    105                 return getLensAutoFocusStopButtonDescription();
    106             case CanonMakernoteDirectory.TAG_CANON_CUSTOM_FUNCTION_FILL_FLASH_REDUCTION:
    107                 return getFillFlashReductionDescription();
    108             case CanonMakernoteDirectory.TAG_CANON_CUSTOM_FUNCTION_MENU_BUTTON_RETURN:
    109                 return getMenuButtonReturnPositionDescription();
    110             case CanonMakernoteDirectory.TAG_CANON_CUSTOM_FUNCTION_SET_BUTTON_FUNCTION:
    111                 return getSetButtonFunctionWhenShootingDescription();
    112             case CanonMakernoteDirectory.TAG_CANON_CUSTOM_FUNCTION_SENSOR_CLEANING:
    113                 return getSensorCleaningDescription();
    114             default:
    115                 return _directory.getString(tagType);
    116         }
    117     }
    118 
    119     public String getLongExposureNoiseReductionDescription() throws MetadataException
    120     {
    121         if (!_directory.containsTag(CanonMakernoteDirectory.TAG_CANON_CUSTOM_FUNCTION_LONG_EXPOSURE_NOISE_REDUCTION)) return null;
    122         int value = _directory.getInt(CanonMakernoteDirectory.TAG_CANON_CUSTOM_FUNCTION_LONG_EXPOSURE_NOISE_REDUCTION);
     97
     98            // It turns out that these values are dependent upon the camera model and therefore the below code was
     99            // incorrect for some Canon models.  This needs to be revisited.
     100
     101//            case CanonMakernoteDirectory.TAG_CANON_CUSTOM_FUNCTION_LONG_EXPOSURE_NOISE_REDUCTION:
     102//                return getLongExposureNoiseReductionDescription();
     103//            case CanonMakernoteDirectory.TAG_CANON_CUSTOM_FUNCTION_SHUTTER_AUTO_EXPOSURE_LOCK_BUTTONS:
     104//                return getShutterAutoExposureLockButtonDescription();
     105//            case CanonMakernoteDirectory.TAG_CANON_CUSTOM_FUNCTION_MIRROR_LOCKUP:
     106//                return getMirrorLockupDescription();
     107//            case CanonMakernoteDirectory.TAG_CANON_CUSTOM_FUNCTION_TV_AV_AND_EXPOSURE_LEVEL:
     108//                return getTvAndAvExposureLevelDescription();
     109//            case CanonMakernoteDirectory.TAG_CANON_CUSTOM_FUNCTION_AF_ASSIST_LIGHT:
     110//                return getAutoFocusAssistLightDescription();
     111//            case CanonMakernoteDirectory.TAG_CANON_CUSTOM_FUNCTION_SHUTTER_SPEED_IN_AV_MODE:
     112//                return getShutterSpeedInAvModeDescription();
     113//            case CanonMakernoteDirectory.TAG_CANON_CUSTOM_FUNCTION_BRACKETTING:
     114//                return getAutoExposureBrackettingSequenceAndAutoCancellationDescription();
     115//            case CanonMakernoteDirectory.TAG_CANON_CUSTOM_FUNCTION_SHUTTER_CURTAIN_SYNC:
     116//                return getShutterCurtainSyncDescription();
     117//            case CanonMakernoteDirectory.TAG_CANON_CUSTOM_FUNCTION_AF_STOP:
     118//                return getLensAutoFocusStopButtonDescription();
     119//            case CanonMakernoteDirectory.TAG_CANON_CUSTOM_FUNCTION_FILL_FLASH_REDUCTION:
     120//                return getFillFlashReductionDescription();
     121//            case CanonMakernoteDirectory.TAG_CANON_CUSTOM_FUNCTION_MENU_BUTTON_RETURN:
     122//                return getMenuButtonReturnPositionDescription();
     123//            case CanonMakernoteDirectory.TAG_CANON_CUSTOM_FUNCTION_SET_BUTTON_FUNCTION:
     124//                return getSetButtonFunctionWhenShootingDescription();
     125//            case CanonMakernoteDirectory.TAG_CANON_CUSTOM_FUNCTION_SENSOR_CLEANING:
     126//                return getSensorCleaningDescription();
     127            default:
     128                return super.getDescription(tagType);
     129        }
     130    }
     131
     132    @Nullable
     133    public String getSerialNumberDescription()
     134    {
     135        Integer value = _directory.getInteger(CanonMakernoteDirectory.TAG_CANON_SERIAL_NUMBER);
     136        if (value==null)
     137            return null;
     138        return String.format("%04X%05d", (value >> 8) & 0xFF, value & 0xFF);
     139    }
     140
     141/*
     142    @Nullable
     143    public String getLongExposureNoiseReductionDescription()
     144    {
     145        Integer value = _directory.getInteger(CanonMakernoteDirectory.TAG_CANON_CUSTOM_FUNCTION_LONG_EXPOSURE_NOISE_REDUCTION);
     146        if (value==null)
     147            return null;
    123148        switch (value) {
    124149            case 0:     return "Off";
     
    127152        }
    128153    }
    129     public String getShutterAutoExposureLockButtonDescription() throws MetadataException
    130     {
    131         if (!_directory.containsTag(CanonMakernoteDirectory.TAG_CANON_CUSTOM_FUNCTION_SHUTTER_AUTO_EXPOSURE_LOCK_BUTTONS)) return null;
    132         int value = _directory.getInt(CanonMakernoteDirectory.TAG_CANON_CUSTOM_FUNCTION_SHUTTER_AUTO_EXPOSURE_LOCK_BUTTONS);
     154
     155    @Nullable
     156    public String getShutterAutoExposureLockButtonDescription()
     157    {
     158        Integer value = _directory.getInteger(CanonMakernoteDirectory.TAG_CANON_CUSTOM_FUNCTION_SHUTTER_AUTO_EXPOSURE_LOCK_BUTTONS);
     159        if (value==null)
     160            return null;
    133161        switch (value) {
    134162            case 0:     return "AF/AE lock";
     
    139167        }
    140168    }
    141     public String getMirrorLockupDescription() throws MetadataException
    142     {
    143         if (!_directory.containsTag(CanonMakernoteDirectory.TAG_CANON_CUSTOM_FUNCTION_MIRROR_LOCKUP)) return null;
    144         int value = _directory.getInt(CanonMakernoteDirectory.TAG_CANON_CUSTOM_FUNCTION_MIRROR_LOCKUP);
     169
     170    @Nullable
     171    public String getMirrorLockupDescription()
     172    {
     173        Integer value = _directory.getInteger(CanonMakernoteDirectory.TAG_CANON_CUSTOM_FUNCTION_MIRROR_LOCKUP);
     174        if (value==null)
     175            return null;
    145176        switch (value) {
    146177            case 0:     return "Disabled";
     
    149180        }
    150181    }
    151     public String getTvAndAvExposureLevelDescription() throws MetadataException
    152     {
    153         if (!_directory.containsTag(CanonMakernoteDirectory.TAG_CANON_CUSTOM_FUNCTION_TV_AV_AND_EXPOSURE_LEVEL)) return null;
    154         int value = _directory.getInt(CanonMakernoteDirectory.TAG_CANON_CUSTOM_FUNCTION_TV_AV_AND_EXPOSURE_LEVEL);
     182
     183    @Nullable
     184    public String getTvAndAvExposureLevelDescription()
     185    {
     186        Integer value = _directory.getInteger(CanonMakernoteDirectory.TAG_CANON_CUSTOM_FUNCTION_TV_AV_AND_EXPOSURE_LEVEL);
     187        if (value==null)
     188            return null;
    155189        switch (value) {
    156190            case 0:     return "1/2 stop";
     
    159193        }
    160194    }
    161     public String getAutoFocusAssistLightDescription() throws MetadataException
    162     {
    163         if (!_directory.containsTag(CanonMakernoteDirectory.TAG_CANON_CUSTOM_FUNCTION_AF_ASSIST_LIGHT)) return null;
    164         int value = _directory.getInt(CanonMakernoteDirectory.TAG_CANON_CUSTOM_FUNCTION_AF_ASSIST_LIGHT);
     195
     196    @Nullable
     197    public String getAutoFocusAssistLightDescription()
     198    {
     199        Integer value = _directory.getInteger(CanonMakernoteDirectory.TAG_CANON_CUSTOM_FUNCTION_AF_ASSIST_LIGHT);
     200        if (value==null)
     201            return null;
    165202        switch (value) {
    166203            case 0:     return "On (Auto)";
     
    169206        }
    170207    }
    171     public String getShutterSpeedInAvModeDescription() throws MetadataException
    172     {
    173         if (!_directory.containsTag(CanonMakernoteDirectory.TAG_CANON_CUSTOM_FUNCTION_SHUTTER_SPEED_IN_AV_MODE)) return null;
    174         int value = _directory.getInt(CanonMakernoteDirectory.TAG_CANON_CUSTOM_FUNCTION_SHUTTER_SPEED_IN_AV_MODE);
     208
     209    @Nullable
     210    public String getShutterSpeedInAvModeDescription()
     211    {
     212        Integer value = _directory.getInteger(CanonMakernoteDirectory.TAG_CANON_CUSTOM_FUNCTION_SHUTTER_SPEED_IN_AV_MODE);
     213        if (value==null)
     214            return null;
    175215        switch (value) {
    176216            case 0:     return "Automatic";
     
    179219        }
    180220    }
    181     public String getAutoExposureBrackettingSequenceAndAutoCancellationDescription() throws MetadataException
    182     {
    183         if (!_directory.containsTag(CanonMakernoteDirectory.TAG_CANON_CUSTOM_FUNCTION_BRACKETTING)) return null;
    184         int value = _directory.getInt(CanonMakernoteDirectory.TAG_CANON_CUSTOM_FUNCTION_BRACKETTING);
     221
     222    @Nullable
     223    public String getAutoExposureBrackettingSequenceAndAutoCancellationDescription()
     224    {
     225        Integer value = _directory.getInteger(CanonMakernoteDirectory.TAG_CANON_CUSTOM_FUNCTION_BRACKETTING);
     226        if (value==null)
     227            return null;
    185228        switch (value) {
    186229            case 0:     return "0,-,+ / Enabled";
     
    191234        }
    192235    }
    193     public String getShutterCurtainSyncDescription() throws MetadataException
    194     {
    195         if (!_directory.containsTag(CanonMakernoteDirectory.TAG_CANON_CUSTOM_FUNCTION_SHUTTER_CURTAIN_SYNC)) return null;
    196         int value = _directory.getInt(CanonMakernoteDirectory.TAG_CANON_CUSTOM_FUNCTION_SHUTTER_CURTAIN_SYNC);
     236
     237    @Nullable
     238    public String getShutterCurtainSyncDescription()
     239    {
     240        Integer value = _directory.getInteger(CanonMakernoteDirectory.TAG_CANON_CUSTOM_FUNCTION_SHUTTER_CURTAIN_SYNC);
     241        if (value==null)
     242            return null;
    197243        switch (value) {
    198244            case 0:     return "1st Curtain Sync";
     
    201247        }
    202248    }
    203     public String getLensAutoFocusStopButtonDescription() throws MetadataException
    204     {
    205         if (!_directory.containsTag(CanonMakernoteDirectory.TAG_CANON_CUSTOM_FUNCTION_AF_STOP)) return null;
    206         int value = _directory.getInt(CanonMakernoteDirectory.TAG_CANON_CUSTOM_FUNCTION_AF_STOP);
     249
     250    @Nullable
     251    public String getLensAutoFocusStopButtonDescription()
     252    {
     253        Integer value = _directory.getInteger(CanonMakernoteDirectory.TAG_CANON_CUSTOM_FUNCTION_AF_STOP);
     254        if (value==null)
     255            return null;
    207256        switch (value) {
    208257            case 0:     return "AF stop";
     
    212261        }
    213262    }
    214     public String getFillFlashReductionDescription() throws MetadataException
    215     {
    216         if (!_directory.containsTag(CanonMakernoteDirectory.TAG_CANON_CUSTOM_FUNCTION_FILL_FLASH_REDUCTION)) return null;
    217         int value = _directory.getInt(CanonMakernoteDirectory.TAG_CANON_CUSTOM_FUNCTION_FILL_FLASH_REDUCTION);
     263
     264    @Nullable
     265    public String getFillFlashReductionDescription()
     266    {
     267        Integer value = _directory.getInteger(CanonMakernoteDirectory.TAG_CANON_CUSTOM_FUNCTION_FILL_FLASH_REDUCTION);
     268        if (value==null)
     269            return null;
    218270        switch (value) {
    219271            case 0:     return "Enabled";
     
    222274        }
    223275    }
    224     public String getMenuButtonReturnPositionDescription() throws MetadataException
    225     {
    226         if (!_directory.containsTag(CanonMakernoteDirectory.TAG_CANON_CUSTOM_FUNCTION_MENU_BUTTON_RETURN)) return null;
    227         int value = _directory.getInt(CanonMakernoteDirectory.TAG_CANON_CUSTOM_FUNCTION_MENU_BUTTON_RETURN);
     276
     277    @Nullable
     278    public String getMenuButtonReturnPositionDescription()
     279    {
     280        Integer value = _directory.getInteger(CanonMakernoteDirectory.TAG_CANON_CUSTOM_FUNCTION_MENU_BUTTON_RETURN);
     281        if (value==null)
     282            return null;
    228283        switch (value) {
    229284            case 0:     return "Top";
     
    233288        }
    234289    }
    235     public String getSetButtonFunctionWhenShootingDescription() throws MetadataException
    236     {
    237         if (!_directory.containsTag(CanonMakernoteDirectory.TAG_CANON_CUSTOM_FUNCTION_SET_BUTTON_FUNCTION)) return null;
    238         int value = _directory.getInt(CanonMakernoteDirectory.TAG_CANON_CUSTOM_FUNCTION_SET_BUTTON_FUNCTION);
     290
     291    @Nullable
     292    public String getSetButtonFunctionWhenShootingDescription()
     293    {
     294        Integer value = _directory.getInteger(CanonMakernoteDirectory.TAG_CANON_CUSTOM_FUNCTION_SET_BUTTON_FUNCTION);
     295        if (value==null)
     296            return null;
    239297        switch (value) {
    240298            case 0:     return "Not Assigned";
     
    245303        }
    246304    }
    247     public String getSensorCleaningDescription() throws MetadataException
    248     {
    249         if (!_directory.containsTag(CanonMakernoteDirectory.TAG_CANON_CUSTOM_FUNCTION_SENSOR_CLEANING)) return null;
    250         int value = _directory.getInt(CanonMakernoteDirectory.TAG_CANON_CUSTOM_FUNCTION_SENSOR_CLEANING);
     305
     306    @Nullable
     307    public String getSensorCleaningDescription()
     308    {
     309        Integer value = _directory.getInteger(CanonMakernoteDirectory.TAG_CANON_CUSTOM_FUNCTION_SENSOR_CLEANING);
     310        if (value==null)
     311            return null;
    251312        switch (value) {
    252313            case 0:     return "Disabled";
     
    255316        }
    256317    }
    257 
    258     public String getFlashBiasDescription() throws MetadataException
    259     {
    260         if (!_directory.containsTag(CanonMakernoteDirectory.TAG_CANON_STATE2_FLASH_BIAS)) return null;
    261 
    262         int value = _directory.getInt(CanonMakernoteDirectory.TAG_CANON_STATE2_FLASH_BIAS);
     318*/
     319
     320    @Nullable
     321    public String getFlashBiasDescription()
     322    {
     323        Integer value = _directory.getInteger(CanonMakernoteDirectory.FocalLength.TAG_FLASH_BIAS);
     324
     325        if (value==null)
     326            return null;
    263327
    264328        boolean isNegative = false;
     
    278342    }
    279343
    280     public String getAfPointUsedDescription() throws MetadataException
    281     {
    282         if (!_directory.containsTag(CanonMakernoteDirectory.TAG_CANON_STATE2_AF_POINT_USED)) return null;
    283         int value = _directory.getInt(CanonMakernoteDirectory.TAG_CANON_STATE2_AF_POINT_USED);
     344    @Nullable
     345    public String getAfPointUsedDescription()
     346    {
     347        Integer value = _directory.getInteger(CanonMakernoteDirectory.FocalLength.TAG_AF_POINT_USED);
     348        if (value==null)
     349            return null;
    284350        if ((value & 0x7) == 0) {
    285351            return "Right";
     
    293359    }
    294360
    295     public String getWhiteBalanceDescription() throws MetadataException
    296     {
    297         if (!_directory.containsTag(CanonMakernoteDirectory.TAG_CANON_STATE2_WHITE_BALANCE)) return null;
    298         int value = _directory.getInt(CanonMakernoteDirectory.TAG_CANON_STATE2_WHITE_BALANCE);
     361    @Nullable
     362    public String getWhiteBalanceDescription()
     363    {
     364        Integer value = _directory.getInteger(CanonMakernoteDirectory.FocalLength.TAG_WHITE_BALANCE);
     365        if (value==null)
     366            return null;
    299367        switch (value) {
    300368            case 0:
     
    307375                return "Tungsten";
    308376            case 4:
    309                 return "Flourescent";
     377                return "Florescent";
    310378            case 5:
    311379                return "Flash";
     
    317385    }
    318386
    319     public String getFocusMode2Description() throws MetadataException
    320     {
    321         if (!_directory.containsTag(CanonMakernoteDirectory.TAG_CANON_STATE1_FOCUS_MODE_2)) return null;
    322         int value = _directory.getInt(CanonMakernoteDirectory.TAG_CANON_STATE1_FOCUS_MODE_2);
     387    @Nullable
     388    public String getFocusMode2Description()
     389    {
     390        Integer value = _directory.getInteger(CanonMakernoteDirectory.CameraSettings.TAG_FOCUS_MODE_2);
     391        if (value==null)
     392            return null;
    323393        switch (value) {
    324394            case 0:
     
    331401    }
    332402
    333     public String getFlashDetailsDescription() throws MetadataException
    334     {
    335         if (!_directory.containsTag(CanonMakernoteDirectory.TAG_CANON_STATE1_FLASH_DETAILS)) return null;
    336         int value = _directory.getInt(CanonMakernoteDirectory.TAG_CANON_STATE1_FLASH_DETAILS);
    337         if (((value << 14) & 1) > 0) {
     403    @Nullable
     404    public String getFlashDetailsDescription()
     405    {
     406        Integer value = _directory.getInteger(CanonMakernoteDirectory.CameraSettings.TAG_FLASH_DETAILS);
     407        if (value==null)
     408            return null;
     409        if (((value >> 14) & 1) > 0) {
    338410            return "External E-TTL";
    339411        }
    340         if (((value << 13) & 1) > 0) {
     412        if (((value >> 13) & 1) > 0) {
    341413            return "Internal flash";
    342414        }
    343         if (((value << 11) & 1) > 0) {
     415        if (((value >> 11) & 1) > 0) {
    344416            return "FP sync used";
    345417        }
    346         if (((value << 4) & 1) > 0) {
     418        if (((value >> 4) & 1) > 0) {
    347419            return "FP sync enabled";
    348420        }
     
    350422    }
    351423
    352     public String getFocalUnitsPerMillimetreDescription() throws MetadataException
    353     {
    354         if (!_directory.containsTag(CanonMakernoteDirectory.TAG_CANON_STATE1_FOCAL_UNITS_PER_MM)) return "";
    355         int value = _directory.getInt(CanonMakernoteDirectory.TAG_CANON_STATE1_FOCAL_UNITS_PER_MM);
     424    @Nullable
     425    public String getFocalUnitsPerMillimetreDescription()
     426    {
     427        Integer value = _directory.getInteger(CanonMakernoteDirectory.CameraSettings.TAG_FOCAL_UNITS_PER_MM);
     428        if (value==null)
     429            return null;
    356430        if (value != 0) {
    357431            return Integer.toString(value);
     
    361435    }
    362436
    363     public String getShortFocalLengthDescription() throws MetadataException
    364     {
    365         if (!_directory.containsTag(CanonMakernoteDirectory.TAG_CANON_STATE1_SHORT_FOCAL_LENGTH)) return null;
    366         int value = _directory.getInt(CanonMakernoteDirectory.TAG_CANON_STATE1_SHORT_FOCAL_LENGTH);
     437    @Nullable
     438    public String getShortFocalLengthDescription()
     439    {
     440        Integer value = _directory.getInteger(CanonMakernoteDirectory.CameraSettings.TAG_SHORT_FOCAL_LENGTH);
     441        if (value==null)
     442            return null;
    367443        String units = getFocalUnitsPerMillimetreDescription();
    368444        return Integer.toString(value) + " " + units;
    369445    }
    370446
    371     public String getLongFocalLengthDescription() throws MetadataException
    372     {
    373         if (!_directory.containsTag(CanonMakernoteDirectory.TAG_CANON_STATE1_LONG_FOCAL_LENGTH)) return null;
    374         int value = _directory.getInt(CanonMakernoteDirectory.TAG_CANON_STATE1_LONG_FOCAL_LENGTH);
     447    @Nullable
     448    public String getLongFocalLengthDescription()
     449    {
     450        Integer value = _directory.getInteger(CanonMakernoteDirectory.CameraSettings.TAG_LONG_FOCAL_LENGTH);
     451        if (value==null)
     452            return null;
    375453        String units = getFocalUnitsPerMillimetreDescription();
    376454        return Integer.toString(value) + " " + units;
    377455    }
    378456
    379     public String getExposureModeDescription() throws MetadataException
    380     {
    381         if (!_directory.containsTag(CanonMakernoteDirectory.TAG_CANON_STATE1_EXPOSURE_MODE)) return null;
    382         int value = _directory.getInt(CanonMakernoteDirectory.TAG_CANON_STATE1_EXPOSURE_MODE);
     457    @Nullable
     458    public String getExposureModeDescription()
     459    {
     460        Integer value = _directory.getInteger(CanonMakernoteDirectory.CameraSettings.TAG_EXPOSURE_MODE);
     461        if (value==null)
     462            return null;
    383463        switch (value) {
    384464            case 0:
     
    399479    }
    400480
    401     public String getAfPointSelectedDescription() throws MetadataException
    402     {
    403         if (!_directory.containsTag(CanonMakernoteDirectory.TAG_CANON_STATE1_AF_POINT_SELECTED)) return null;
    404         int value = _directory.getInt(CanonMakernoteDirectory.TAG_CANON_STATE1_AF_POINT_SELECTED);
     481    @Nullable
     482    public String getAfPointSelectedDescription()
     483    {
     484        Integer value = _directory.getInteger(CanonMakernoteDirectory.CameraSettings.TAG_AF_POINT_SELECTED);
     485        if (value==null)
     486            return null;
    405487        switch (value) {
    406488            case 0x3000:
     
    419501    }
    420502
    421     public String getMeteringModeDescription() throws MetadataException
    422     {
    423         if (!_directory.containsTag(CanonMakernoteDirectory.TAG_CANON_STATE1_METERING_MODE)) return null;
    424         int value = _directory.getInt(CanonMakernoteDirectory.TAG_CANON_STATE1_METERING_MODE);
     503    @Nullable
     504    public String getMeteringModeDescription()
     505    {
     506        Integer value = _directory.getInteger(CanonMakernoteDirectory.CameraSettings.TAG_METERING_MODE);
     507        if (value==null)
     508            return null;
    425509        switch (value) {
    426510            case 3:
     
    435519    }
    436520
    437     public String getIsoDescription() throws MetadataException
    438     {
    439         if (!_directory.containsTag(CanonMakernoteDirectory.TAG_CANON_STATE1_ISO)) return null;
    440         int value = _directory.getInt(CanonMakernoteDirectory.TAG_CANON_STATE1_ISO);
     521    @Nullable
     522    public String getIsoDescription()
     523    {
     524        Integer value = _directory.getInteger(CanonMakernoteDirectory.CameraSettings.TAG_ISO);
     525        if (value==null)
     526            return null;
     527
     528        // Canon PowerShot S3 is special
     529        int canonMask = 0x4000;
     530        if ((value & canonMask) > 0)
     531            return "" + (value & ~canonMask);
     532
    441533        switch (value) {
    442534            case 0:
     
    457549    }
    458550
    459     public String getSharpnessDescription() throws MetadataException
    460     {
    461         if (!_directory.containsTag(CanonMakernoteDirectory.TAG_CANON_STATE1_SHARPNESS)) return null;
    462         int value = _directory.getInt(CanonMakernoteDirectory.TAG_CANON_STATE1_SHARPNESS);
     551    @Nullable
     552    public String getSharpnessDescription()
     553    {
     554        Integer value = _directory.getInteger(CanonMakernoteDirectory.CameraSettings.TAG_SHARPNESS);
     555        if (value==null)
     556            return null;
    463557        switch (value) {
    464558            case 0xFFFF:
     
    473567    }
    474568
    475     public String getSaturationDescription() throws MetadataException
    476     {
    477         if (!_directory.containsTag(CanonMakernoteDirectory.TAG_CANON_STATE1_SATURATION)) return null;
    478         int value = _directory.getInt(CanonMakernoteDirectory.TAG_CANON_STATE1_SATURATION);
     569    @Nullable
     570    public String getSaturationDescription()
     571    {
     572        Integer value = _directory.getInteger(CanonMakernoteDirectory.CameraSettings.TAG_SATURATION);
     573        if (value==null)
     574            return null;
    479575        switch (value) {
    480576            case 0xFFFF:
     
    489585    }
    490586
    491     public String getContrastDescription() throws MetadataException
    492     {
    493         if (!_directory.containsTag(CanonMakernoteDirectory.TAG_CANON_STATE1_CONTRAST)) return null;
    494         int value = _directory.getInt(CanonMakernoteDirectory.TAG_CANON_STATE1_CONTRAST);
     587    @Nullable
     588    public String getContrastDescription()
     589    {
     590        Integer value = _directory.getInteger(CanonMakernoteDirectory.CameraSettings.TAG_CONTRAST);
     591        if (value==null)
     592            return null;
    495593        switch (value) {
    496594            case 0xFFFF:
     
    505603    }
    506604
    507     public String getEasyShootingModeDescription() throws MetadataException
    508     {
    509         if (!_directory.containsTag(CanonMakernoteDirectory.TAG_CANON_STATE1_EASY_SHOOTING_MODE)) return null;
    510         int value = _directory.getInt(CanonMakernoteDirectory.TAG_CANON_STATE1_EASY_SHOOTING_MODE);
     605    @Nullable
     606    public String getEasyShootingModeDescription()
     607    {
     608        Integer value = _directory.getInteger(CanonMakernoteDirectory.CameraSettings.TAG_EASY_SHOOTING_MODE);
     609        if (value==null)
     610            return null;
    511611        switch (value) {
    512612            case 0:
     
    539639    }
    540640
    541     public String getImageSizeDescription() throws MetadataException
    542     {
    543         if (!_directory.containsTag(CanonMakernoteDirectory.TAG_CANON_STATE1_IMAGE_SIZE)) return null;
    544         int value = _directory.getInt(CanonMakernoteDirectory.TAG_CANON_STATE1_IMAGE_SIZE);
     641    @Nullable
     642    public String getImageSizeDescription()
     643    {
     644        Integer value = _directory.getInteger(CanonMakernoteDirectory.CameraSettings.TAG_IMAGE_SIZE);
     645        if (value==null)
     646            return null;
    545647        switch (value) {
    546648            case 0:
     
    555657    }
    556658
    557     public String getFocusMode1Description() throws MetadataException
    558     {
    559         if (!_directory.containsTag(CanonMakernoteDirectory.TAG_CANON_STATE1_FOCUS_MODE_1)) return null;
    560         int value = _directory.getInt(CanonMakernoteDirectory.TAG_CANON_STATE1_FOCUS_MODE_1);
     659    @Nullable
     660    public String getFocusMode1Description()
     661    {
     662        Integer value = _directory.getInteger(CanonMakernoteDirectory.CameraSettings.TAG_FOCUS_MODE_1);
     663        if (value==null)
     664            return null;
    561665        switch (value) {
    562666            case 0:
     
    580684    }
    581685
    582     public String getContinuousDriveModeDescription() throws MetadataException
    583     {
    584         if (!_directory.containsTag(CanonMakernoteDirectory.TAG_CANON_STATE1_CONTINUOUS_DRIVE_MODE)) return null;
    585         int value = _directory.getInt(CanonMakernoteDirectory.TAG_CANON_STATE1_CONTINUOUS_DRIVE_MODE);
    586         switch (value) {
    587             case 0:
    588                 if (_directory.getInt(CanonMakernoteDirectory.TAG_CANON_STATE1_SELF_TIMER_DELAY) == 0) {
    589                     return "Single shot";
    590                 } else {
    591                     return "Single shot with self-timer";
    592                 }
     686    @Nullable
     687    public String getContinuousDriveModeDescription()
     688    {
     689        Integer value = _directory.getInteger(CanonMakernoteDirectory.CameraSettings.TAG_CONTINUOUS_DRIVE_MODE);
     690        if (value==null)
     691            return null;
     692        switch (value) {
     693            case 0:
     694                final Integer delay = _directory.getInteger(CanonMakernoteDirectory.CameraSettings.TAG_SELF_TIMER_DELAY);
     695                if (delay!=null)
     696                    return delay == 0 ? "Single shot" : "Single shot with self-timer";
    593697            case 1:
    594698                return "Continuous";
    595             default:
    596                 return "Unknown (" + value + ")";
    597         }
    598     }
    599 
    600     public String getFlashModeDescription() throws MetadataException
    601     {
    602         if (!_directory.containsTag(CanonMakernoteDirectory.TAG_CANON_STATE1_FLASH_MODE)) return null;
    603         int value = _directory.getInt(CanonMakernoteDirectory.TAG_CANON_STATE1_FLASH_MODE);
     699        }
     700        return "Unknown (" + value + ")";
     701    }
     702
     703    @Nullable
     704    public String getFlashModeDescription()
     705    {
     706        Integer value = _directory.getInteger(CanonMakernoteDirectory.CameraSettings.TAG_FLASH_MODE);
     707        if (value==null)
     708            return null;
    604709        switch (value) {
    605710            case 0:
     
    619724            case 16:
    620725                // note: this value not set on Canon D30
    621                 return "Extenal flash";
    622             default:
    623                 return "Unknown (" + value + ")";
    624         }
    625     }
    626 
    627     public String getSelfTimerDelayDescription() throws MetadataException
    628     {
    629         if (!_directory.containsTag(CanonMakernoteDirectory.TAG_CANON_STATE1_SELF_TIMER_DELAY)) return null;
    630         int value = _directory.getInt(CanonMakernoteDirectory.TAG_CANON_STATE1_SELF_TIMER_DELAY);
     726                return "External flash";
     727            default:
     728                return "Unknown (" + value + ")";
     729        }
     730    }
     731
     732    @Nullable
     733    public String getSelfTimerDelayDescription()
     734    {
     735        Integer value = _directory.getInteger(CanonMakernoteDirectory.CameraSettings.TAG_SELF_TIMER_DELAY);
     736        if (value==null)
     737            return null;
    631738        if (value == 0) {
    632739            return "Self timer not used";
     
    637744    }
    638745
    639     public String getMacroModeDescription() throws MetadataException
    640     {
    641         if (!_directory.containsTag(CanonMakernoteDirectory.TAG_CANON_STATE1_MACRO_MODE)) return null;
    642         int value = _directory.getInt(CanonMakernoteDirectory.TAG_CANON_STATE1_MACRO_MODE);
     746    @Nullable
     747    public String getMacroModeDescription()
     748    {
     749        Integer value = _directory.getInteger(CanonMakernoteDirectory.CameraSettings.TAG_MACRO_MODE);
     750        if (value==null)
     751            return null;
    643752        switch (value) {
    644753            case 1:
     
    651760    }
    652761
    653     public String getQualityDescription() throws MetadataException
    654     {
    655         if (!_directory.containsTag(CanonMakernoteDirectory.TAG_CANON_STATE1_QUALITY)) return null;
    656         int value = _directory.getInt(CanonMakernoteDirectory.TAG_CANON_STATE1_QUALITY);
     762    @Nullable
     763    public String getQualityDescription()
     764    {
     765        Integer value = _directory.getInteger(CanonMakernoteDirectory.CameraSettings.TAG_QUALITY);
     766        if (value==null)
     767            return null;
    657768        switch (value) {
    658769            case 2:
     
    667778    }
    668779
    669     public String getDigitalZoomDescription() throws MetadataException
    670     {
    671         if (!_directory.containsTag(CanonMakernoteDirectory.TAG_CANON_STATE1_DIGITAL_ZOOM)) return null;
    672         int value = _directory.getInt(CanonMakernoteDirectory.TAG_CANON_STATE1_DIGITAL_ZOOM);
     780    @Nullable
     781    public String getDigitalZoomDescription()
     782    {
     783        Integer value = _directory.getInteger(CanonMakernoteDirectory.CameraSettings.TAG_DIGITAL_ZOOM);
     784        if (value==null)
     785            return null;
    673786        switch (value) {
    674787            case 0:
     
    683796    }
    684797
    685     public String getFocusTypeDescription() throws MetadataException
    686     {
    687         if (!_directory.containsTag(CanonMakernoteDirectory.TAG_CANON_STATE1_FOCUS_TYPE)) return null;
    688         int value = _directory.getInt(CanonMakernoteDirectory.TAG_CANON_STATE1_FOCUS_TYPE);
     798    @Nullable
     799    public String getFocusTypeDescription()
     800    {
     801        Integer value = _directory.getInteger(CanonMakernoteDirectory.CameraSettings.TAG_FOCUS_TYPE);
     802        if (value==null)
     803            return null;
    689804        switch (value) {
    690805            case 0:
     
    701816    }
    702817
    703     public String getFlashActivityDescription() throws MetadataException
    704     {
    705         if (!_directory.containsTag(CanonMakernoteDirectory.TAG_CANON_STATE1_FLASH_ACTIVITY)) return null;
    706         int value = _directory.getInt(CanonMakernoteDirectory.TAG_CANON_STATE1_FLASH_ACTIVITY);
     818    @Nullable
     819    public String getFlashActivityDescription()
     820    {
     821        Integer value = _directory.getInteger(CanonMakernoteDirectory.CameraSettings.TAG_FLASH_ACTIVITY);
     822        if (value==null)
     823            return null;
    707824        switch (value) {
    708825            case 0:
  • trunk/src/com/drew/metadata/exif/CanonMakernoteDirectory.java

    r4231 r6127  
    11/*
    2  * This is public domain software - that is, you can do whatever you want
    3  * with it, and include it software that is licensed under the GNU or the
    4  * BSD license, or whatever other licence you choose, including proprietary
    5  * closed source licenses.  I do ask that you leave this header in tact.
     2 * Copyright 2002-2012 Drew Noakes
    63 *
    7  * If you make modifications to this code that you think would benefit the
    8  * wider community, please send me a copy and I'll post it on my site.
     4 *    Licensed under the Apache License, Version 2.0 (the "License");
     5 *    you may not use this file except in compliance with the License.
     6 *    You may obtain a copy of the License at
    97 *
    10  * If you make use of this code, I'd appreciate hearing about it.
    11  *   drew@drewnoakes.com
    12  * Latest version of this software kept at
    13  *   http://drewnoakes.com/
     8 *        http://www.apache.org/licenses/LICENSE-2.0
    149 *
    15  * Created by dnoakes on 27-Nov-2002 10:10:47 using IntelliJ IDEA.
     10 *    Unless required by applicable law or agreed to in writing, software
     11 *    distributed under the License is distributed on an "AS IS" BASIS,
     12 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13 *    See the License for the specific language governing permissions and
     14 *    limitations under the License.
     15 *
     16 * More information about this project is available at:
     17 *
     18 *    http://drewnoakes.com/code/exif/
     19 *    http://code.google.com/p/metadata-extractor/
    1620 */
    1721package com.drew.metadata.exif;
    1822
     23import com.drew.lang.annotations.NotNull;
    1924import com.drew.metadata.Directory;
    2025
     
    2732 *
    2833 * Many tag definitions explained here: http://www.ozhiker.com/electronics/pjmt/jpeg_info/canon_mn.html
     34 *
     35 * @author Drew Noakes http://drewnoakes.com
    2936 */
    3037public class CanonMakernoteDirectory extends Directory
    3138{
    32     // CANON cameras have some funny bespoke fields that need further processing...
    33     public static final int TAG_CANON_CAMERA_STATE_1 = 0x0001;
    34     public static final int TAG_CANON_CAMERA_STATE_2 = 0x0004;
    35 
    36     public static final int TAG_CANON_IMAGE_TYPE = 0x0006;
    37     public static final int TAG_CANON_FIRMWARE_VERSION = 0x0007;
    38     public static final int TAG_CANON_IMAGE_NUMBER = 0x0008;
    39     public static final int TAG_CANON_OWNER_NAME = 0x0009;
    40     /**
    41      * To display serial number as on camera use: printf( "%04X%05d", highbyte, lowbyte )
    42      * TODO handle this in CanonMakernoteDescriptor
    43      */
    44     public static final int TAG_CANON_SERIAL_NUMBER = 0x000C;
    45     public static final int TAG_CANON_UNKNOWN_1 = 0x000D;
    46     public static final int TAG_CANON_CUSTOM_FUNCTIONS = 0x000F;
    47 
    48     // These 'sub'-tag values have been created for consistency -- they don't exist within the exif segment
    49     /**
    50      * 1 = Macro
    51      * 2 = Normal
    52      */
    53     public static final int TAG_CANON_STATE1_MACRO_MODE = 0xC101;
    54     public static final int TAG_CANON_STATE1_SELF_TIMER_DELAY = 0xC102;
    55     /**
    56      * 2 = Normal
    57      * 3 = Fine
    58      * 5 = Superfine
    59      */
    60     public static final int TAG_CANON_STATE1_QUALITY = 0xC103;
    61     /**
    62      * 0 = Flash Not Fired
    63      * 1 = Auto
    64      * 2 = On
    65      * 3 = Red Eye Reduction
    66      * 4 = Slow Synchro
    67      * 5 = Auto + Red Eye Reduction
    68      * 6 = On + Red Eye Reduction
    69      * 16 = External Flash
    70      */
    71     public static final int TAG_CANON_STATE1_FLASH_MODE = 0xC104;
    72     /**
    73      * 0 = Single Frame or Timer Mode
    74      * 1 = Continuous
    75      */
    76     public static final int TAG_CANON_STATE1_CONTINUOUS_DRIVE_MODE = 0xC105;
    77     public static final int TAG_CANON_STATE1_UNKNOWN_2 = 0xC106;
    78     /**
    79      * 0 = One-Shot
    80      * 1 = AI Servo
    81      * 2 = AI Focus
    82      * 3 = Manual Focus
    83      * 4 = Single
    84      * 5 = Continuous
    85      * 6 = Manual Focus
    86      */
    87     public static final int TAG_CANON_STATE1_FOCUS_MODE_1 = 0xC107;
    88     public static final int TAG_CANON_STATE1_UNKNOWN_3 = 0xC108;
    89     public static final int TAG_CANON_STATE1_UNKNOWN_4 = 0xC109;
    90     /**
    91      * 0 = Large
    92      * 1 = Medium
    93      * 2 = Small
    94      */
    95     public static final int TAG_CANON_STATE1_IMAGE_SIZE = 0xC10A;
    96     /**
    97      * 0 = Full Auto
    98      * 1 = Manual
    99      * 2 = Landscape
    100      * 3 = Fast Shutter
    101      * 4 = Slow Shutter
    102      * 5 = Night
    103      * 6 = Black & White
    104      * 7 = Sepia
    105      * 8 = Portrait
    106      * 9 = Sports
    107      * 10 = Macro / Close-Up
    108      * 11 = Pan Focus
    109      */
    110     public static final int TAG_CANON_STATE1_EASY_SHOOTING_MODE = 0xC10B;
    111     /**
    112      * 0 = No Digital Zoom
    113      * 1 = 2x
    114      * 2 = 4x
    115      */
    116     public static final int TAG_CANON_STATE1_DIGITAL_ZOOM = 0xC10C;
    117     /**
    118      * 0 = Normal
    119      * 1 = High
    120      * 65535 = Low
    121      */
    122     public static final int TAG_CANON_STATE1_CONTRAST = 0xC10D;
    123     /**
    124      * 0 = Normal
    125      * 1 = High
    126      * 65535 = Low
    127      */
    128     public static final int TAG_CANON_STATE1_SATURATION = 0xC10E;
    129     /**
    130      * 0 = Normal
    131      * 1 = High
    132      * 65535 = Low
    133      */
    134     public static final int TAG_CANON_STATE1_SHARPNESS = 0xC10F;
    135     /**
    136      * 0 = Check ISOSpeedRatings EXIF tag for ISO Speed
    137      * 15 = Auto ISO
    138      * 16 = ISO 50
    139      * 17 = ISO 100
    140      * 18 = ISO 200
    141      * 19 = ISO 400
    142      */
    143     public static final int TAG_CANON_STATE1_ISO = 0xC110;
    144     /**
    145      * 3 = Evaluative
    146      * 4 = Partial
    147      * 5 = Centre Weighted
    148      */
    149     public static final int TAG_CANON_STATE1_METERING_MODE = 0xC111;
    150     /**
    151      * 0 = Manual
    152      * 1 = Auto
    153      * 3 = Close-up (Macro)
    154      * 8 = Locked (Pan Mode)
    155      */
    156     public static final int TAG_CANON_STATE1_FOCUS_TYPE = 0xC112;
    157     /**
    158      * 12288 = None (Manual Focus)
    159      * 12289 = Auto Selected
    160      * 12290 = Right
    161      * 12291 = Centre
    162      * 12292 = Left
    163      */
    164     public static final int TAG_CANON_STATE1_AF_POINT_SELECTED = 0xC113;
    165     /**
    166      * 0 = Easy Shooting (See Easy Shooting Mode)
    167      * 1 = Program
    168      * 2 = Tv-Priority
    169      * 3 = Av-Priority
    170      * 4 = Manual
    171      * 5 = A-DEP
    172      */
    173     public static final int TAG_CANON_STATE1_EXPOSURE_MODE = 0xC114;
    174     public static final int TAG_CANON_STATE1_UNKNOWN_7 = 0xC115;
    175     public static final int TAG_CANON_STATE1_UNKNOWN_8 = 0xC116;
    176     public static final int TAG_CANON_STATE1_LONG_FOCAL_LENGTH = 0xC117;
    177     public static final int TAG_CANON_STATE1_SHORT_FOCAL_LENGTH = 0xC118;
    178     public static final int TAG_CANON_STATE1_FOCAL_UNITS_PER_MM = 0xC119;
    179     public static final int TAG_CANON_STATE1_UNKNOWN_9 = 0xC11A;
    180     public static final int TAG_CANON_STATE1_UNKNOWN_10 = 0xC11B;
    181     /**
    182      * 0 = Flash Did Not Fire
    183      * 1 = Flash Fired
    184      */
    185     public static final int TAG_CANON_STATE1_FLASH_ACTIVITY = 0xC11C;
    186     public static final int TAG_CANON_STATE1_FLASH_DETAILS = 0xC11D;
    187     public static final int TAG_CANON_STATE1_UNKNOWN_12 = 0xC11E;
    188     public static final int TAG_CANON_STATE1_UNKNOWN_13 = 0xC11F;
    189     /**
    190      * 0 = Focus Mode: Single
    191      * 1 = Focus Mode: Continuous
    192      */
    193     public static final int TAG_CANON_STATE1_FOCUS_MODE_2 = 0xC120;
    194 
    195     /**
    196      * 0 = Auto
    197      * 1 = Sunny
    198      * 2 = Cloudy
    199      * 3 = Tungsten
    200      * 4 = Flourescent
    201      * 5 = Flash
    202      * 6 = Custom
    203      */
    204     public static final int TAG_CANON_STATE2_WHITE_BALANCE = 0xC207;
    205     public static final int TAG_CANON_STATE2_SEQUENCE_NUMBER = 0xC209;
    206     public static final int TAG_CANON_STATE2_AF_POINT_USED = 0xC20E;
    207     /**
    208      * The value of this tag may be translated into a flash bias value, in EV.
    209      *
    210      * 0xffc0 = -2 EV
    211      * 0xffcc = -1.67 EV
    212      * 0xffd0 = -1.5 EV
    213      * 0xffd4 = -1.33 EV
    214      * 0xffe0 = -1 EV
    215      * 0xffec = -0.67 EV
    216      * 0xfff0 = -0.5 EV
    217      * 0xfff4 = -0.33 EV
    218      * 0x0000 = 0 EV
    219      * 0x000c = 0.33 EV
    220      * 0x0010 = 0.5 EV
    221      * 0x0014 = 0.67 EV
    222      * 0x0020 = 1 EV
    223      * 0x002c = 1.33 EV
    224      * 0x0030 = 1.5 EV
    225      * 0x0034 = 1.67 EV
    226      * 0x0040 = 2 EV
    227      */
    228     public static final int TAG_CANON_STATE2_FLASH_BIAS = 0xC20F;
    229     public static final int TAG_CANON_STATE2_AUTO_EXPOSURE_BRACKETING = 0xC210;
    230     public static final int TAG_CANON_STATE2_AEB_BRACKET_VALUE = 0xC211;
    231     public static final int TAG_CANON_STATE2_SUBJECT_DISTANCE = 0xC213;
    232 
    233     /**
    234      * Long Exposure Noise Reduction
    235      * 0 = Off
    236      * 1 = On
    237      */
    238     public static final int TAG_CANON_CUSTOM_FUNCTION_LONG_EXPOSURE_NOISE_REDUCTION = 0xC301;
    239 
    240     /**
    241      * Shutter/Auto Exposure-lock buttons
    242      * 0 = AF/AE lock
    243      * 1 = AE lock/AF
    244      * 2 = AF/AF lock
    245      * 3 = AE+release/AE+AF
    246      */
    247     public static final int TAG_CANON_CUSTOM_FUNCTION_SHUTTER_AUTO_EXPOSURE_LOCK_BUTTONS = 0xC302;
    248 
    249     /**
    250      * Mirror lockup
    251      * 0 = Disable
    252      * 1 = Enable
    253      */
    254     public static final int TAG_CANON_CUSTOM_FUNCTION_MIRROR_LOCKUP = 0xC303;
    255 
    256     /**
    257      * Tv/Av and exposure level
    258      * 0 = 1/2 stop
    259      * 1 = 1/3 stop
    260      */
    261     public static final int TAG_CANON_CUSTOM_FUNCTION_TV_AV_AND_EXPOSURE_LEVEL = 0xC304;
    262 
    263     /**
    264      * AF-assist light
    265      * 0 = On (Auto)
    266      * 1 = Off
    267      */
    268     public static final int TAG_CANON_CUSTOM_FUNCTION_AF_ASSIST_LIGHT = 0xC305;
    269 
    270     /**
    271      * Shutter speed in Av mode
    272      * 0 = Automatic
    273      * 1 = 1/200 (fixed)
    274      */
    275     public static final int TAG_CANON_CUSTOM_FUNCTION_SHUTTER_SPEED_IN_AV_MODE = 0xC306;
    276 
    277     /**
    278      * Auto-Exposure Bracketting sequence/auto cancellation
    279      * 0 = 0,-,+ / Enabled
    280      * 1 = 0,-,+ / Disabled
    281      * 2 = -,0,+ / Enabled
    282      * 3 = -,0,+ / Disabled
    283      */
    284     public static final int TAG_CANON_CUSTOM_FUNCTION_BRACKETTING = 0xC307;
    285 
    286     /**
    287      * Shutter Curtain Sync
    288      * 0 = 1st Curtain Sync
    289      * 1 = 2nd Curtain Sync
    290      */
    291     public static final int TAG_CANON_CUSTOM_FUNCTION_SHUTTER_CURTAIN_SYNC = 0xC308;
    292 
    293     /**
    294      * Lens Auto-Focus stop button Function Switch
    295      * 0 = AF stop
    296      * 1 = Operate AF
    297      * 2 = Lock AE and start timer
    298      */
    299     public static final int TAG_CANON_CUSTOM_FUNCTION_AF_STOP = 0xC309;
    300 
    301     /**
    302      * Auto reduction of fill flash
    303      * 0 = Enable
    304      * 1 = Disable
    305      */
    306     public static final int TAG_CANON_CUSTOM_FUNCTION_FILL_FLASH_REDUCTION = 0xC30A;
    307 
    308     /**
    309      * Menu button return position
    310      * 0 = Top
    311      * 1 = Previous (volatile)
    312      * 2 = Previous
    313      */
    314     public static final int TAG_CANON_CUSTOM_FUNCTION_MENU_BUTTON_RETURN = 0xC30B;
    315 
    316     /**
    317      * SET button function when shooting
    318      * 0 = Not Assigned
    319      * 1 = Change Quality
    320      * 2 = Change ISO Speed
    321      * 3 = Select Parameters
    322      */
    323     public static final int TAG_CANON_CUSTOM_FUNCTION_SET_BUTTON_FUNCTION = 0xC30C;
    324 
    325     /**
    326      * Sensor cleaning
    327      * 0 = Disable
    328      * 1 = Enable
    329      */
    330     public static final int TAG_CANON_CUSTOM_FUNCTION_SENSOR_CLEANING = 0xC30D;
     39    // These TAG_*_ARRAY Exif tags map to arrays of int16 values which are split out into separate 'fake' tags.
     40    // When an attempt is made to set one of these on the directory, it is split and the corresponding offset added to the tagType.
     41    // So the resulting tag is the offset + the index into the array.
     42
     43    private static final int TAG_CAMERA_SETTINGS_ARRAY  = 0x0001;
     44    private static final int TAG_FOCAL_LENGTH_ARRAY = 0x0002;
     45    private static final int TAG_SHOT_INFO_ARRAY = 0x0004;
     46    private static final int TAG_PANORAMA_ARRAY = 0x0005;
     47
     48    public static final int TAG_CANON_IMAGE_TYPE            = 0x0006;
     49    public static final int TAG_CANON_FIRMWARE_VERSION      = 0x0007;
     50    public static final int TAG_CANON_IMAGE_NUMBER          = 0x0008;
     51    public static final int TAG_CANON_OWNER_NAME            = 0x0009;
     52    public static final int TAG_CANON_SERIAL_NUMBER         = 0x000C;
     53    public static final int TAG_CAMERA_INFO_ARRAY           = 0x000D; // depends upon model, so leave for now
     54    public static final int TAG_CANON_FILE_LENGTH           = 0x000E;
     55    public static final int TAG_CANON_CUSTOM_FUNCTIONS_ARRAY = 0x000F; // depends upon model, so leave for now
     56    public static final int TAG_MODEL_ID                    = 0x0010;
     57    public static final int TAG_MOVIE_INFO_ARRAY            = 0x0011; // not currently decoded as not sure we see it in still images
     58    private static final int TAG_AF_INFO_ARRAY              = 0x0012; // not currently decoded
     59    public static final int TAG_THUMBNAIL_IMAGE_VALID_AREA  = 0x0013;
     60    public static final int TAG_SERIAL_NUMBER_FORMAT        = 0x0015;
     61    public static final int TAG_SUPER_MACRO                 = 0x001A;
     62    public static final int TAG_DATE_STAMP_MODE             = 0x001C;
     63    public static final int TAG_MY_COLORS                   = 0x001D;
     64    public static final int TAG_FIRMWARE_REVISION           = 0x001E;
     65    public static final int TAG_CATEGORIES                  = 0x0023;
     66    public static final int TAG_FACE_DETECT_ARRAY_1         = 0x0024;
     67    public static final int TAG_FACE_DETECT_ARRAY_2         = 0x0025;
     68    public static final int TAG_AF_INFO_ARRAY_2             = 0x0026;
     69    public static final int TAG_IMAGE_UNIQUE_ID             = 0x0028;
     70    public static final int TAG_RAW_DATA_OFFSET             = 0x0081;
     71    public static final int TAG_ORIGINAL_DECISION_DATA_OFFSET = 0x0083;
     72    public static final int TAG_CUSTOM_FUNCTIONS_1D_ARRAY   = 0x0090; // not currently decoded
     73    public static final int TAG_PERSONAL_FUNCTIONS_ARRAY    = 0x0091; // not currently decoded
     74    public static final int TAG_PERSONAL_FUNCTION_VALUES_ARRAY = 0x0092; // not currently decoded
     75    public static final int TAG_FILE_INFO_ARRAY             = 0x0093; // not currently decoded
     76    public static final int TAG_AF_POINTS_IN_FOCUS_1D       = 0x0094;
     77    public static final int TAG_LENS_MODEL                  = 0x0095;
     78    public static final int TAG_SERIAL_INFO_ARRAY           = 0x0096; // not currently decoded
     79    public static final int TAG_DUST_REMOVAL_DATA           = 0x0097;
     80    public static final int TAG_CROP_INFO                   = 0x0098; // not currently decoded
     81    public static final int TAG_CUSTOM_FUNCTIONS_ARRAY_2    = 0x0099; // not currently decoded
     82    public static final int TAG_ASPECT_INFO_ARRAY           = 0x009A; // not currently decoded
     83    public static final int TAG_PROCESSING_INFO_ARRAY       = 0x00A0; // not currently decoded
     84    public static final int TAG_TONE_CURVE_TABLE            = 0x00A1;
     85    public static final int TAG_SHARPNESS_TABLE             = 0x00A2;
     86    public static final int TAG_SHARPNESS_FREQ_TABLE        = 0x00A3;
     87    public static final int TAG_WHITE_BALANCE_TABLE         = 0x00A4;
     88    public static final int TAG_COLOR_BALANCE_ARRAY         = 0x00A9; // not currently decoded
     89    public static final int TAG_MEASURED_COLOR_ARRAY        = 0x00AA; // not currently decoded
     90    public static final int TAG_COLOR_TEMPERATURE           = 0x00AE;
     91    public static final int TAG_CANON_FLAGS_ARRAY           = 0x00B0; // not currently decoded
     92    public static final int TAG_MODIFIED_INFO_ARRAY         = 0x00B1; // not currently decoded
     93    public static final int TAG_TONE_CURVE_MATCHING         = 0x00B2;
     94    public static final int TAG_WHITE_BALANCE_MATCHING      = 0x00B3;
     95    public static final int TAG_COLOR_SPACE                 = 0x00B4;
     96    public static final int TAG_PREVIEW_IMAGE_INFO_ARRAY    = 0x00B6; // not currently decoded
     97    public static final int TAG_VRD_OFFSET                  = 0x00D0;
     98    public static final int TAG_SENSOR_INFO_ARRAY           = 0x00E0; // not currently decoded
     99    public static final int TAG_COLOR_DATA_ARRAY_2 = 0x4001; // depends upon camera model, not currently decoded
     100    public static final int TAG_COLOR_INFO_ARRAY_2          = 0x4003; // not currently decoded
     101    public static final int TAG_CUSTOM_PICTURE_STYLE_FILE_NAME = 0x4010;
     102    public static final int TAG_COLOR_INFO_ARRAY            = 0x4013; // not currently decoded
     103    public static final int TAG_VIGNETTING_CORRECTION_ARRAY_1 = 0x4015; // not currently decoded
     104    public static final int TAG_VIGNETTING_CORRECTION_ARRAY_2 = 0x4016; // not currently decoded
     105    public static final int TAG_LIGHTING_OPTIMIZER_ARRAY = 0x4018; // not currently decoded
     106    public static final int TAG_LENS_INFO_ARRAY             = 0x4019; // not currently decoded
     107    public static final int TAG_AMBIANCE_INFO_ARRAY         = 0x4020; // not currently decoded
     108    public static final int TAG_FILTER_INFO_ARRAY           = 0x4024; // not currently decoded
     109
     110    public final static class CameraSettings
     111    {
     112        // These 'sub'-tag values have been created for consistency -- they don't exist within the exif segment
     113        private static final int OFFSET = 0xC100;
     114
     115        /**
     116         * 1 = Macro
     117         * 2 = Normal
     118         */
     119        public static final int TAG_MACRO_MODE = OFFSET + 0x01;
     120        public static final int TAG_SELF_TIMER_DELAY = OFFSET + 0x02;
     121        /**
     122         * 2 = Normal
     123         * 3 = Fine
     124         * 5 = Superfine
     125         */
     126        public static final int TAG_QUALITY = OFFSET + 0x03;
     127        /**
     128         * 0 = Flash Not Fired
     129         * 1 = Auto
     130         * 2 = On
     131         * 3 = Red Eye Reduction
     132         * 4 = Slow Synchro
     133         * 5 = Auto + Red Eye Reduction
     134         * 6 = On + Red Eye Reduction
     135         * 16 = External Flash
     136         */
     137        public static final int TAG_FLASH_MODE = OFFSET + 0x04;
     138        /**
     139         * 0 = Single Frame or Timer Mode
     140         * 1 = Continuous
     141         */
     142        public static final int TAG_CONTINUOUS_DRIVE_MODE = OFFSET + 0x05;
     143        public static final int TAG_UNKNOWN_2 = OFFSET + 0x06;
     144        /**
     145         * 0 = One-Shot
     146         * 1 = AI Servo
     147         * 2 = AI Focus
     148         * 3 = Manual Focus
     149         * 4 = Single
     150         * 5 = Continuous
     151         * 6 = Manual Focus
     152         */
     153        public static final int TAG_FOCUS_MODE_1 = OFFSET + 0x07;
     154        public static final int TAG_UNKNOWN_3 = OFFSET + 0x08;
     155        public static final int TAG_UNKNOWN_4 = OFFSET + 0x09;
     156        /**
     157         * 0 = Large
     158         * 1 = Medium
     159         * 2 = Small
     160         */
     161        public static final int TAG_IMAGE_SIZE = OFFSET + 0x0A;
     162        /**
     163         * 0 = Full Auto
     164         * 1 = Manual
     165         * 2 = Landscape
     166         * 3 = Fast Shutter
     167         * 4 = Slow Shutter
     168         * 5 = Night
     169         * 6 = Black & White
     170         * 7 = Sepia
     171         * 8 = Portrait
     172         * 9 = Sports
     173         * 10 = Macro / Close-Up
     174         * 11 = Pan Focus
     175         */
     176        public static final int TAG_EASY_SHOOTING_MODE = OFFSET + 0x0B;
     177        /**
     178         * 0 = No Digital Zoom
     179         * 1 = 2x
     180         * 2 = 4x
     181         */
     182        public static final int TAG_DIGITAL_ZOOM = OFFSET + 0x0C;
     183        /**
     184         * 0 = Normal
     185         * 1 = High
     186         * 65535 = Low
     187         */
     188        public static final int TAG_CONTRAST = OFFSET + 0x0D;
     189        /**
     190         * 0 = Normal
     191         * 1 = High
     192         * 65535 = Low
     193         */
     194        public static final int TAG_SATURATION = OFFSET + 0x0E;
     195        /**
     196         * 0 = Normal
     197         * 1 = High
     198         * 65535 = Low
     199         */
     200        public static final int TAG_SHARPNESS = OFFSET + 0x0F;
     201        /**
     202         * 0 = Check ISOSpeedRatings EXIF tag for ISO Speed
     203         * 15 = Auto ISO
     204         * 16 = ISO 50
     205         * 17 = ISO 100
     206         * 18 = ISO 200
     207         * 19 = ISO 400
     208         */
     209        public static final int TAG_ISO = OFFSET + 0x10;
     210        /**
     211         * 3 = Evaluative
     212         * 4 = Partial
     213         * 5 = Centre Weighted
     214         */
     215        public static final int TAG_METERING_MODE = OFFSET + 0x11;
     216        /**
     217         * 0 = Manual
     218         * 1 = Auto
     219         * 3 = Close-up (Macro)
     220         * 8 = Locked (Pan Mode)
     221         */
     222        public static final int TAG_FOCUS_TYPE = OFFSET + 0x12;
     223        /**
     224         * 12288 = None (Manual Focus)
     225         * 12289 = Auto Selected
     226         * 12290 = Right
     227         * 12291 = Centre
     228         * 12292 = Left
     229         */
     230        public static final int TAG_AF_POINT_SELECTED = OFFSET + 0x13;
     231        /**
     232         * 0 = Easy Shooting (See Easy Shooting Mode)
     233         * 1 = Program
     234         * 2 = Tv-Priority
     235         * 3 = Av-Priority
     236         * 4 = Manual
     237         * 5 = A-DEP
     238         */
     239        public static final int TAG_EXPOSURE_MODE = OFFSET + 0x14;
     240        public static final int TAG_UNKNOWN_7 = OFFSET + 0x15;
     241        public static final int TAG_UNKNOWN_8 = OFFSET + 0x16;
     242        public static final int TAG_LONG_FOCAL_LENGTH = OFFSET + 0x17;
     243        public static final int TAG_SHORT_FOCAL_LENGTH = OFFSET + 0x18;
     244        public static final int TAG_FOCAL_UNITS_PER_MM = OFFSET + 0x19;
     245        public static final int TAG_UNKNOWN_9 = OFFSET + 0x1A;
     246        public static final int TAG_UNKNOWN_10 = OFFSET + 0x1B;
     247        /**
     248         * 0 = Flash Did Not Fire
     249         * 1 = Flash Fired
     250         */
     251        public static final int TAG_FLASH_ACTIVITY = OFFSET + 0x1C;
     252        public static final int TAG_FLASH_DETAILS = OFFSET + 0x1D;
     253        public static final int TAG_UNKNOWN_12 = OFFSET + 0x1E;
     254        public static final int TAG_UNKNOWN_13 = OFFSET + 0x1F;
     255        /**
     256         * 0 = Focus Mode: Single
     257         * 1 = Focus Mode: Continuous
     258         */
     259        public static final int TAG_FOCUS_MODE_2 = OFFSET + 0x20;
     260    }
     261   
     262    public final static class FocalLength
     263    {
     264        // These 'sub'-tag values have been created for consistency -- they don't exist within the exif segment
     265
     266        private static final int OFFSET = 0xC200;
     267       
     268        /**
     269         * 0 = Auto
     270         * 1 = Sunny
     271         * 2 = Cloudy
     272         * 3 = Tungsten
     273         * 4 = Florescent
     274         * 5 = Flash
     275         * 6 = Custom
     276         */
     277        public static final int TAG_WHITE_BALANCE = OFFSET + 0x07;
     278        public static final int TAG_SEQUENCE_NUMBER = OFFSET + 0x09;
     279        public static final int TAG_AF_POINT_USED = OFFSET + 0x0E;
     280        /**
     281         * The value of this tag may be translated into a flash bias value, in EV.
     282         *
     283         * 0xffc0 = -2 EV
     284         * 0xffcc = -1.67 EV
     285         * 0xffd0 = -1.5 EV
     286         * 0xffd4 = -1.33 EV
     287         * 0xffe0 = -1 EV
     288         * 0xffec = -0.67 EV
     289         * 0xfff0 = -0.5 EV
     290         * 0xfff4 = -0.33 EV
     291         * 0x0000 = 0 EV
     292         * 0x000c = 0.33 EV
     293         * 0x0010 = 0.5 EV
     294         * 0x0014 = 0.67 EV
     295         * 0x0020 = 1 EV
     296         * 0x002c = 1.33 EV
     297         * 0x0030 = 1.5 EV
     298         * 0x0034 = 1.67 EV
     299         * 0x0040 = 2 EV
     300         */
     301        public static final int TAG_FLASH_BIAS = OFFSET + 0x0F;
     302        public static final int TAG_AUTO_EXPOSURE_BRACKETING = OFFSET + 0x10;
     303        public static final int TAG_AEB_BRACKET_VALUE = OFFSET + 0x11;
     304        public static final int TAG_SUBJECT_DISTANCE = OFFSET + 0x13;
     305    }
     306   
     307    public final static class ShotInfo
     308    {
     309        // These 'sub'-tag values have been created for consistency -- they don't exist within the exif segment
     310
     311        private static final int OFFSET = 0xC400;
     312
     313        public static final int TAG_AUTO_ISO = OFFSET + 1;
     314        public static final int TAG_BASE_ISO = OFFSET + 2;
     315        public static final int TAG_MEASURED_EV = OFFSET + 3;
     316        public static final int TAG_TARGET_APERTURE = OFFSET + 4;
     317        public static final int TAG_TARGET_EXPOSURE_TIME = OFFSET + 5;
     318        public static final int TAG_EXPOSURE_COMPENSATION = OFFSET + 6;
     319        public static final int TAG_WHITE_BALANCE = OFFSET + 7;
     320        public static final int TAG_SLOW_SHUTTER = OFFSET + 8;
     321        public static final int TAG_SEQUENCE_NUMBER = OFFSET + 9;
     322        public static final int TAG_OPTICAL_ZOOM_CODE = OFFSET + 10;
     323        public static final int TAG_CAMERA_TEMPERATURE = OFFSET + 12;
     324        public static final int TAG_FLASH_GUIDE_NUMBER = OFFSET + 13;
     325        public static final int TAG_AF_POINTS_IN_FOCUS = OFFSET + 14;
     326        public static final int TAG_FLASH_EXPOSURE_BRACKETING = OFFSET + 15;
     327        public static final int TAG_AUTO_EXPOSURE_BRACKETING = OFFSET + 16;
     328        public static final int TAG_AEB_BRACKET_VALUE = OFFSET + 17;
     329        public static final int TAG_CONTROL_MODE = OFFSET + 18;
     330        public static final int TAG_FOCUS_DISTANCE_UPPER = OFFSET + 19;
     331        public static final int TAG_FOCUS_DISTANCE_LOWER = OFFSET + 20;
     332        public static final int TAG_F_NUMBER = OFFSET + 21;
     333        public static final int TAG_EXPOSURE_TIME = OFFSET + 22;
     334        public static final int TAG_MEASURED_EV_2 = OFFSET + 23;
     335        public static final int TAG_BULB_DURATION = OFFSET + 24;
     336        public static final int TAG_CAMERA_TYPE = OFFSET + 26;
     337        public static final int TAG_AUTO_ROTATE = OFFSET + 27;
     338        public static final int TAG_ND_FILTER = OFFSET + 28;
     339        public static final int TAG_SELF_TIMER_2 = OFFSET + 29;
     340        public static final int TAG_FLASH_OUTPUT = OFFSET + 33;
     341    }
     342
     343    public final static class Panorama
     344    {
     345        // These 'sub'-tag values have been created for consistency -- they don't exist within the exif segment
     346
     347        private static final int OFFSET = 0xC500;
     348
     349        public static final int TAG_PANORAMA_FRAME_NUMBER = OFFSET + 2;
     350        public static final int TAG_PANORAMA_DIRECTION = OFFSET + 5;
     351    }
     352
     353    public final static class AFInfo
     354    {
     355        // These 'sub'-tag values have been created for consistency -- they don't exist within the exif segment
     356
     357        private static final int OFFSET = 0xD200;
     358
     359        public static final int TAG_NUM_AF_POINTS = OFFSET;
     360        public static final int TAG_VALID_AF_POINTS = OFFSET + 1;
     361        public static final int TAG_IMAGE_WIDTH = OFFSET + 2;
     362        public static final int TAG_IMAGE_HEIGHT = OFFSET + 3;
     363        public static final int TAG_AF_IMAGE_WIDTH = OFFSET + 4;
     364        public static final int TAG_AF_IMAGE_HEIGHT = OFFSET + 5;
     365        public static final int TAG_AF_AREA_WIDTH = OFFSET + 6;
     366        public static final int TAG_AF_AREA_HEIGHT = OFFSET + 7;
     367        public static final int TAG_AF_AREA_X_POSITIONS = OFFSET + 8;
     368        public static final int TAG_AF_AREA_Y_POSITIONS = OFFSET + 9;
     369        public static final int TAG_AF_POINTS_IN_FOCUS = OFFSET + 10;
     370        public static final int TAG_PRIMARY_AF_POINT_1 = OFFSET + 11;
     371        public static final int TAG_PRIMARY_AF_POINT_2 = OFFSET + 12; // not sure why there are two of these
     372    }
     373
     374//    /**
     375//     * Long Exposure Noise Reduction
     376//     * 0 = Off
     377//     * 1 = On
     378//     */
     379//    public static final int TAG_CANON_CUSTOM_FUNCTION_LONG_EXPOSURE_NOISE_REDUCTION = 0xC301;
     380//
     381//    /**
     382//     * Shutter/Auto Exposure-lock buttons
     383//     * 0 = AF/AE lock
     384//     * 1 = AE lock/AF
     385//     * 2 = AF/AF lock
     386//     * 3 = AE+release/AE+AF
     387//     */
     388//    public static final int TAG_CANON_CUSTOM_FUNCTION_SHUTTER_AUTO_EXPOSURE_LOCK_BUTTONS = 0xC302;
     389//
     390//    /**
     391//     * Mirror lockup
     392//     * 0 = Disable
     393//     * 1 = Enable
     394//     */
     395//    public static final int TAG_CANON_CUSTOM_FUNCTION_MIRROR_LOCKUP = 0xC303;
     396//
     397//    /**
     398//     * Tv/Av and exposure level
     399//     * 0 = 1/2 stop
     400//     * 1 = 1/3 stop
     401//     */
     402//    public static final int TAG_CANON_CUSTOM_FUNCTION_TV_AV_AND_EXPOSURE_LEVEL = 0xC304;
     403//
     404//    /**
     405//     * AF-assist light
     406//     * 0 = On (Auto)
     407//     * 1 = Off
     408//     */
     409//    public static final int TAG_CANON_CUSTOM_FUNCTION_AF_ASSIST_LIGHT = 0xC305;
     410//
     411//    /**
     412//     * Shutter speed in Av mode
     413//     * 0 = Automatic
     414//     * 1 = 1/200 (fixed)
     415//     */
     416//    public static final int TAG_CANON_CUSTOM_FUNCTION_SHUTTER_SPEED_IN_AV_MODE = 0xC306;
     417//
     418//    /**
     419//     * Auto-Exposure Bracketting sequence/auto cancellation
     420//     * 0 = 0,-,+ / Enabled
     421//     * 1 = 0,-,+ / Disabled
     422//     * 2 = -,0,+ / Enabled
     423//     * 3 = -,0,+ / Disabled
     424//     */
     425//    public static final int TAG_CANON_CUSTOM_FUNCTION_BRACKETTING = 0xC307;
     426//
     427//    /**
     428//     * Shutter Curtain Sync
     429//     * 0 = 1st Curtain Sync
     430//     * 1 = 2nd Curtain Sync
     431//     */
     432//    public static final int TAG_CANON_CUSTOM_FUNCTION_SHUTTER_CURTAIN_SYNC = 0xC308;
     433//
     434//    /**
     435//     * Lens Auto-Focus stop button Function Switch
     436//     * 0 = AF stop
     437//     * 1 = Operate AF
     438//     * 2 = Lock AE and start timer
     439//     */
     440//    public static final int TAG_CANON_CUSTOM_FUNCTION_AF_STOP = 0xC309;
     441//
     442//    /**
     443//     * Auto reduction of fill flash
     444//     * 0 = Enable
     445//     * 1 = Disable
     446//     */
     447//    public static final int TAG_CANON_CUSTOM_FUNCTION_FILL_FLASH_REDUCTION = 0xC30A;
     448//
     449//    /**
     450//     * Menu button return position
     451//     * 0 = Top
     452//     * 1 = Previous (volatile)
     453//     * 2 = Previous
     454//     */
     455//    public static final int TAG_CANON_CUSTOM_FUNCTION_MENU_BUTTON_RETURN = 0xC30B;
     456//
     457//    /**
     458//     * SET button function when shooting
     459//     * 0 = Not Assigned
     460//     * 1 = Change Quality
     461//     * 2 = Change ISO Speed
     462//     * 3 = Select Parameters
     463//     */
     464//    public static final int TAG_CANON_CUSTOM_FUNCTION_SET_BUTTON_FUNCTION = 0xC30C;
     465//
     466//    /**
     467//     * Sensor cleaning
     468//     * 0 = Disable
     469//     * 1 = Enable
     470//     */
     471//    public static final int TAG_CANON_CUSTOM_FUNCTION_SENSOR_CLEANING = 0xC30D;
    331472
    332473    // 9  A  B  C  D  E  F  10 11 12 13
    333474    // 9  10 11 12 13 14 15 16 17 18 19
    334     protected static final HashMap _tagNameMap = new HashMap();
     475    @NotNull
     476    protected static final HashMap<Integer, String> _tagNameMap = new HashMap<Integer, String>();
    335477
    336478    static
    337479    {
    338         _tagNameMap.put(new Integer(TAG_CANON_FIRMWARE_VERSION), "Firmware Version");
    339         _tagNameMap.put(new Integer(TAG_CANON_IMAGE_NUMBER), "Image Number");
    340         _tagNameMap.put(new Integer(TAG_CANON_IMAGE_TYPE), "Image Type");
    341         _tagNameMap.put(new Integer(TAG_CANON_OWNER_NAME), "Owner Name");
    342         _tagNameMap.put(new Integer(TAG_CANON_UNKNOWN_1), "Makernote Unknown 1");
    343         _tagNameMap.put(new Integer(TAG_CANON_CUSTOM_FUNCTIONS), "Custom Functions");
    344         _tagNameMap.put(new Integer(TAG_CANON_SERIAL_NUMBER), "Camera Serial Number");
    345         _tagNameMap.put(new Integer(TAG_CANON_STATE1_AF_POINT_SELECTED), "AF Point Selected");
    346         _tagNameMap.put(new Integer(TAG_CANON_STATE1_CONTINUOUS_DRIVE_MODE), "Continuous Drive Mode");
    347         _tagNameMap.put(new Integer(TAG_CANON_STATE1_CONTRAST), "Contrast");
    348         _tagNameMap.put(new Integer(TAG_CANON_STATE1_EASY_SHOOTING_MODE), "Easy Shooting Mode");
    349         _tagNameMap.put(new Integer(TAG_CANON_STATE1_EXPOSURE_MODE), "Exposure Mode");
    350         _tagNameMap.put(new Integer(TAG_CANON_STATE1_FLASH_DETAILS), "Flash Details");
    351         _tagNameMap.put(new Integer(TAG_CANON_STATE1_FLASH_MODE), "Flash Mode");
    352         _tagNameMap.put(new Integer(TAG_CANON_STATE1_FOCAL_UNITS_PER_MM), "Focal Units per mm");
    353         _tagNameMap.put(new Integer(TAG_CANON_STATE1_FOCUS_MODE_1), "Focus Mode");
    354         _tagNameMap.put(new Integer(TAG_CANON_STATE1_FOCUS_MODE_2), "Focus Mode");
    355         _tagNameMap.put(new Integer(TAG_CANON_STATE1_IMAGE_SIZE), "Image Size");
    356         _tagNameMap.put(new Integer(TAG_CANON_STATE1_ISO), "Iso");
    357         _tagNameMap.put(new Integer(TAG_CANON_STATE1_LONG_FOCAL_LENGTH), "Long Focal Length");
    358         _tagNameMap.put(new Integer(TAG_CANON_STATE1_MACRO_MODE), "Macro Mode");
    359         _tagNameMap.put(new Integer(TAG_CANON_STATE1_METERING_MODE), "Metering Mode");
    360         _tagNameMap.put(new Integer(TAG_CANON_STATE1_SATURATION), "Saturation");
    361         _tagNameMap.put(new Integer(TAG_CANON_STATE1_SELF_TIMER_DELAY), "Self Timer Delay");
    362         _tagNameMap.put(new Integer(TAG_CANON_STATE1_SHARPNESS), "Sharpness");
    363         _tagNameMap.put(new Integer(TAG_CANON_STATE1_SHORT_FOCAL_LENGTH), "Short Focal Length");
    364         _tagNameMap.put(new Integer(TAG_CANON_STATE1_QUALITY), "Quality");
    365         _tagNameMap.put(new Integer(TAG_CANON_STATE1_UNKNOWN_2), "Unknown Camera State 2");
    366         _tagNameMap.put(new Integer(TAG_CANON_STATE1_UNKNOWN_3), "Unknown Camera State 3");
    367         _tagNameMap.put(new Integer(TAG_CANON_STATE1_UNKNOWN_4), "Unknown Camera State 4");
    368         _tagNameMap.put(new Integer(TAG_CANON_STATE1_DIGITAL_ZOOM), "Digital Zoom");
    369         _tagNameMap.put(new Integer(TAG_CANON_STATE1_FOCUS_TYPE), "Focus Type");
    370         _tagNameMap.put(new Integer(TAG_CANON_STATE1_UNKNOWN_7), "Unknown Camera State 7");
    371         _tagNameMap.put(new Integer(TAG_CANON_STATE1_UNKNOWN_8), "Unknown Camera State 8");
    372         _tagNameMap.put(new Integer(TAG_CANON_STATE1_UNKNOWN_9), "Unknown Camera State 9");
    373         _tagNameMap.put(new Integer(TAG_CANON_STATE1_UNKNOWN_10), "Unknown Camera State 10");
    374         _tagNameMap.put(new Integer(TAG_CANON_STATE1_FLASH_ACTIVITY), "Flash Activity");
    375         _tagNameMap.put(new Integer(TAG_CANON_STATE1_UNKNOWN_12), "Unknown Camera State 12");
    376         _tagNameMap.put(new Integer(TAG_CANON_STATE1_UNKNOWN_13), "Unknown Camera State 13");
    377         _tagNameMap.put(new Integer(TAG_CANON_STATE2_WHITE_BALANCE), "White Balance");
    378         _tagNameMap.put(new Integer(TAG_CANON_STATE2_SEQUENCE_NUMBER), "Sequence Number");
    379         _tagNameMap.put(new Integer(TAG_CANON_STATE2_AF_POINT_USED), "AF Point Used");
    380         _tagNameMap.put(new Integer(TAG_CANON_STATE2_FLASH_BIAS), "Flash Bias");
    381         _tagNameMap.put(new Integer(TAG_CANON_STATE2_AUTO_EXPOSURE_BRACKETING), "Auto Exposure Bracketing");
    382         _tagNameMap.put(new Integer(TAG_CANON_STATE2_AEB_BRACKET_VALUE), "AEB Bracket Value");
    383         _tagNameMap.put(new Integer(TAG_CANON_STATE2_SUBJECT_DISTANCE), "Subject Distance");
    384 
    385         _tagNameMap.put(new Integer(TAG_CANON_CUSTOM_FUNCTION_LONG_EXPOSURE_NOISE_REDUCTION), "Long Exposure Noise Reduction");
    386         _tagNameMap.put(new Integer(TAG_CANON_CUSTOM_FUNCTION_SHUTTER_AUTO_EXPOSURE_LOCK_BUTTONS), "Shutter/Auto Exposure-lock Buttons");
    387         _tagNameMap.put(new Integer(TAG_CANON_CUSTOM_FUNCTION_MIRROR_LOCKUP), "Mirror Lockup");
    388         _tagNameMap.put(new Integer(TAG_CANON_CUSTOM_FUNCTION_TV_AV_AND_EXPOSURE_LEVEL), "Tv/Av And Exposure Level");
    389         _tagNameMap.put(new Integer(TAG_CANON_CUSTOM_FUNCTION_AF_ASSIST_LIGHT), "AF-Assist Light");
    390         _tagNameMap.put(new Integer(TAG_CANON_CUSTOM_FUNCTION_SHUTTER_SPEED_IN_AV_MODE), "Shutter Speed in Av Mode");
    391         _tagNameMap.put(new Integer(TAG_CANON_CUSTOM_FUNCTION_BRACKETTING), "Auto-Exposure Bracketting Sequence/Auto Cancellation");
    392         _tagNameMap.put(new Integer(TAG_CANON_CUSTOM_FUNCTION_SHUTTER_CURTAIN_SYNC), "Shutter Curtain Sync");
    393         _tagNameMap.put(new Integer(TAG_CANON_CUSTOM_FUNCTION_AF_STOP), "Lens Auto-Focus Stop Button Function Switch");
    394         _tagNameMap.put(new Integer(TAG_CANON_CUSTOM_FUNCTION_FILL_FLASH_REDUCTION), "Auto Reduction of Fill Flash");
    395         _tagNameMap.put(new Integer(TAG_CANON_CUSTOM_FUNCTION_MENU_BUTTON_RETURN), "Menu Button Return Position");
    396         _tagNameMap.put(new Integer(TAG_CANON_CUSTOM_FUNCTION_SET_BUTTON_FUNCTION), "SET Button Function When Shooting");
    397         _tagNameMap.put(new Integer(TAG_CANON_CUSTOM_FUNCTION_SENSOR_CLEANING), "Sensor Cleaning");
     480        _tagNameMap.put(TAG_CANON_FIRMWARE_VERSION, "Firmware Version");
     481        _tagNameMap.put(TAG_CANON_IMAGE_NUMBER, "Image Number");
     482        _tagNameMap.put(TAG_CANON_IMAGE_TYPE, "Image Type");
     483        _tagNameMap.put(TAG_CANON_OWNER_NAME, "Owner Name");
     484        _tagNameMap.put(TAG_CANON_SERIAL_NUMBER, "Camera Serial Number");
     485        _tagNameMap.put(TAG_CAMERA_INFO_ARRAY, "Camera Info Array");
     486        _tagNameMap.put(TAG_CANON_FILE_LENGTH, "File Length");
     487        _tagNameMap.put(TAG_CANON_CUSTOM_FUNCTIONS_ARRAY, "Custom Functions");
     488        _tagNameMap.put(TAG_MODEL_ID, "Canon Model ID");
     489        _tagNameMap.put(TAG_MOVIE_INFO_ARRAY, "Movie Info Array");
     490
     491        _tagNameMap.put(CameraSettings.TAG_AF_POINT_SELECTED, "AF Point Selected");
     492        _tagNameMap.put(CameraSettings.TAG_CONTINUOUS_DRIVE_MODE, "Continuous Drive Mode");
     493        _tagNameMap.put(CameraSettings.TAG_CONTRAST, "Contrast");
     494        _tagNameMap.put(CameraSettings.TAG_EASY_SHOOTING_MODE, "Easy Shooting Mode");
     495        _tagNameMap.put(CameraSettings.TAG_EXPOSURE_MODE, "Exposure Mode");
     496        _tagNameMap.put(CameraSettings.TAG_FLASH_DETAILS, "Flash Details");
     497        _tagNameMap.put(CameraSettings.TAG_FLASH_MODE, "Flash Mode");
     498        _tagNameMap.put(CameraSettings.TAG_FOCAL_UNITS_PER_MM, "Focal Units per mm");
     499        _tagNameMap.put(CameraSettings.TAG_FOCUS_MODE_1, "Focus Mode");
     500        _tagNameMap.put(CameraSettings.TAG_FOCUS_MODE_2, "Focus Mode");
     501        _tagNameMap.put(CameraSettings.TAG_IMAGE_SIZE, "Image Size");
     502        _tagNameMap.put(CameraSettings.TAG_ISO, "Iso");
     503        _tagNameMap.put(CameraSettings.TAG_LONG_FOCAL_LENGTH, "Long Focal Length");
     504        _tagNameMap.put(CameraSettings.TAG_MACRO_MODE, "Macro Mode");
     505        _tagNameMap.put(CameraSettings.TAG_METERING_MODE, "Metering Mode");
     506        _tagNameMap.put(CameraSettings.TAG_SATURATION, "Saturation");
     507        _tagNameMap.put(CameraSettings.TAG_SELF_TIMER_DELAY, "Self Timer Delay");
     508        _tagNameMap.put(CameraSettings.TAG_SHARPNESS, "Sharpness");
     509        _tagNameMap.put(CameraSettings.TAG_SHORT_FOCAL_LENGTH, "Short Focal Length");
     510        _tagNameMap.put(CameraSettings.TAG_QUALITY, "Quality");
     511        _tagNameMap.put(CameraSettings.TAG_UNKNOWN_2, "Unknown Camera Setting 2");
     512        _tagNameMap.put(CameraSettings.TAG_UNKNOWN_3, "Unknown Camera Setting 3");
     513        _tagNameMap.put(CameraSettings.TAG_UNKNOWN_4, "Unknown Camera Setting 4");
     514        _tagNameMap.put(CameraSettings.TAG_DIGITAL_ZOOM, "Digital Zoom");
     515        _tagNameMap.put(CameraSettings.TAG_FOCUS_TYPE, "Focus Type");
     516        _tagNameMap.put(CameraSettings.TAG_UNKNOWN_7, "Unknown Camera Setting 7");
     517        _tagNameMap.put(CameraSettings.TAG_UNKNOWN_8, "Unknown Camera Setting 8");
     518        _tagNameMap.put(CameraSettings.TAG_UNKNOWN_9, "Unknown Camera Setting 9");
     519        _tagNameMap.put(CameraSettings.TAG_UNKNOWN_10, "Unknown Camera Setting 10");
     520        _tagNameMap.put(CameraSettings.TAG_FLASH_ACTIVITY, "Flash Activity");
     521        _tagNameMap.put(CameraSettings.TAG_UNKNOWN_12, "Unknown Camera Setting 12");
     522        _tagNameMap.put(CameraSettings.TAG_UNKNOWN_13, "Unknown Camera Setting 13");
     523
     524        _tagNameMap.put(FocalLength.TAG_WHITE_BALANCE, "White Balance");
     525        _tagNameMap.put(FocalLength.TAG_SEQUENCE_NUMBER, "Sequence Number");
     526        _tagNameMap.put(FocalLength.TAG_AF_POINT_USED, "AF Point Used");
     527        _tagNameMap.put(FocalLength.TAG_FLASH_BIAS, "Flash Bias");
     528        _tagNameMap.put(FocalLength.TAG_AUTO_EXPOSURE_BRACKETING, "Auto Exposure Bracketing");
     529        _tagNameMap.put(FocalLength.TAG_AEB_BRACKET_VALUE, "AEB Bracket Value");
     530        _tagNameMap.put(FocalLength.TAG_SUBJECT_DISTANCE, "Subject Distance");
     531
     532        _tagNameMap.put(ShotInfo.TAG_AUTO_ISO, "Auto ISO");
     533        _tagNameMap.put(ShotInfo.TAG_BASE_ISO, "Base ISO");
     534        _tagNameMap.put(ShotInfo.TAG_MEASURED_EV, "Measured EV");
     535        _tagNameMap.put(ShotInfo.TAG_TARGET_APERTURE, "Target Aperture");
     536        _tagNameMap.put(ShotInfo.TAG_TARGET_EXPOSURE_TIME, "Target Exposure Time");
     537        _tagNameMap.put(ShotInfo.TAG_EXPOSURE_COMPENSATION, "Exposure Compensation");
     538        _tagNameMap.put(ShotInfo.TAG_WHITE_BALANCE, "White Balance");
     539        _tagNameMap.put(ShotInfo.TAG_SLOW_SHUTTER, "Slow Shutter");
     540        _tagNameMap.put(ShotInfo.TAG_SEQUENCE_NUMBER, "Sequence Number");
     541        _tagNameMap.put(ShotInfo.TAG_OPTICAL_ZOOM_CODE, "Optical Zoom Code");
     542        _tagNameMap.put(ShotInfo.TAG_CAMERA_TEMPERATURE, "Camera Temperature");
     543        _tagNameMap.put(ShotInfo.TAG_FLASH_GUIDE_NUMBER, "Flash Guide Number");
     544        _tagNameMap.put(ShotInfo.TAG_AF_POINTS_IN_FOCUS, "AF Points in Focus");
     545        _tagNameMap.put(Shot