OSDN Git Service

Fix offset and ASCII type handling in exif.
[android-x86/packages-apps-Gallery2.git] / gallerycommon / src / com / android / gallery3d / exif / ExifTag.java
index 157a8db..b8b3872 100644 (file)
 
 package com.android.gallery3d.exif;
 
-import android.util.SparseArray;
-
 import java.nio.charset.Charset;
 import java.text.SimpleDateFormat;
 import java.util.Arrays;
 import java.util.Date;
 
 /**
- * This class stores information of an EXIF tag.
- * @see ExifParser
- * @see ExifReader
- * @see IfdData
- * @see ExifData
+ * This class stores information of an EXIF tag. For more information about
+ * defined EXIF tags, please read the Jeita EXIF 2.2 standard. Tags should be
+ * instantiated using {@link ExifInterface#buildTag}.
+ *
+ * @see ExifInterface
  */
 public class ExifTag {
-    // Tiff Tags
-    public static final short TAG_IMAGE_WIDTH = 0x100;
-    /*
-     * The height of the image.
-     */
-    public static final short TAG_IMAGE_LENGTH = 0x101;
-    public static final short TAG_BITS_PER_SAMPLE = 0x102;
-    public static final short TAG_COMPRESSION = 0x103;
-    public static final short TAG_PHOTOMETRIC_INTERPRETATION = 0x106;
-    public static final short TAG_IMAGE_DESCRIPTION = 0x10E;
-    public static final short TAG_MAKE = 0x10F;
-    public static final short TAG_MODEL = 0x110;
-    public static final short TAG_STRIP_OFFSETS = 0x111;
-    public static final short TAG_ORIENTATION = 0x112;
-    public static final short TAG_SAMPLES_PER_PIXEL = 0x115;
-    public static final short TAG_ROWS_PER_STRIP = 0x116;
-    public static final short TAG_STRIP_BYTE_COUNTS = 0x117;
-    public static final short TAG_X_RESOLUTION = 0x11A;
-    public static final short TAG_Y_RESOLUTION = 0x11B;
-    public static final short TAG_PLANAR_CONFIGURATION = 0x11C;
-    public static final short TAG_RESOLUTION_UNIT = 0x128;
-    public static final short TAG_TRANSFER_FUNCTION = 0x12D;
-    public static final short TAG_SOFTWARE = 0x131;
-    public static final short TAG_DATE_TIME = 0x132;
-    public static final short TAG_ARTIST = 0x13B;
-    public static final short TAG_WHITE_POINT = 0x13E;
-    public static final short TAG_PRIMARY_CHROMATICITIES = 0x13F;
-    public static final short TAG_JPEG_INTERCHANGE_FORMAT = 0x201;
-    public static final short TAG_JPEG_INTERCHANGE_FORMAT_LENGTH = 0x202;
-    public static final short TAG_Y_CB_CR_COEFFICIENTS = 0x211;
-    public static final short TAG_Y_CB_CR_SUB_SAMPLING = 0x212;
-    public static final short TAG_Y_CB_CR_POSITIONING = 0x213;
-    public static final short TAG_REFERENCE_BLACK_WHITE = 0x214;
-    public static final short TAG_COPYRIGHT = (short) 0x8298;
-    public static final short TAG_EXIF_IFD = (short) 0x8769;
-    public static final short TAG_GPS_IFD = (short) 0x8825;
-
-    // Exif Tags
-    public static final short TAG_EXPOSURE_TIME = (short) 0x829A;
-    public static final short TAG_F_NUMBER = (short) 0x829D;
-    public static final short TAG_EXPOSURE_PROGRAM = (short) 0x8822;
-    public static final short TAG_SPECTRAL_SENSITIVITY = (short) 0x8824;
-    public static final short TAG_ISO_SPEED_RATINGS = (short) 0x8827;
-    public static final short TAG_OECF = (short) 0x8828;
-    public static final short TAG_EXIF_VERSION = (short) 0x9000;
-    public static final short TAG_DATE_TIME_ORIGINAL = (short) 0x9003;
-    public static final short TAG_DATE_TIME_DIGITIZED = (short) 0x9004;
-    public static final short TAG_COMPONENTS_CONFIGURATION = (short) 0x9101;
-    public static final short TAG_COMPRESSED_BITS_PER_PIXEL = (short) 0x9102;
-    public static final short TAG_SHUTTER_SPEED_VALUE = (short) 0x9201;
-    public static final short TAG_APERTURE_VALUE = (short) 0x9202;
-    public static final short TAG_BRIGHTNESS_VALUE = (short) 0x9203;
-    public static final short TAG_EXPOSURE_BIAS_VALUE = (short) 0x9204;
-    public static final short TAG_MAX_APERTURE_VALUE = (short) 0x9205;
-    public static final short TAG_SUBJECT_DISTANCE = (short) 0x9206;
-    public static final short TAG_METERING_MODE = (short) 0x9207;
-    public static final short TAG_LIGHT_SOURCE = (short) 0x9208;
-    public static final short TAG_FLASH = (short) 0x9209;
-    public static final short TAG_FOCAL_LENGTH = (short) 0x920A;
-    public static final short TAG_SUBJECT_AREA = (short) 0x9214;
-    public static final short TAG_MAKER_NOTE = (short) 0x927C;
-    public static final short TAG_USER_COMMENT = (short) 0x9286;
-    public static final short TAG_SUB_SEC_TIME = (short) 0x9290;
-    public static final short TAG_SUB_SEC_TIME_ORIGINAL = (short) 0x9291;
-    public static final short TAG_SUB_SEC_TIME_DIGITIZED = (short) 0x9292;
-    public static final short TAG_FLASHPIX_VERSION = (short) 0xA000;
-    public static final short TAG_COLOR_SPACE = (short) 0xA001;
-    public static final short TAG_PIXEL_X_DIMENSION = (short) 0xA002;
-    public static final short TAG_PIXEL_Y_DIMENSION = (short) 0xA003;
-    public static final short TAG_RELATED_SOUND_FILE = (short) 0xA004;
-    public static final short TAG_INTEROPERABILITY_IFD = (short) 0xA005;
-    public static final short TAG_FLASH_ENERGY = (short) 0xA20B;
-    public static final short TAG_SPATIAL_FREQUENCY_RESPONSE = (short) 0xA20C;
-    public static final short TAG_FOCAL_PLANE_X_RESOLUTION = (short) 0xA20E;
-    public static final short TAG_FOCAL_PLANE_Y_RESOLUTION = (short) 0xA20F;
-    public static final short TAG_FOCAL_PLANE_RESOLUTION_UNIT = (short) 0xA210;
-    public static final short TAG_SUBJECT_LOCATION = (short) 0xA214;
-    public static final short TAG_EXPOSURE_INDEX = (short) 0xA215;
-    public static final short TAG_SENSING_METHOD = (short) 0xA217;
-    public static final short TAG_FILE_SOURCE = (short) 0xA300;
-    public static final short TAG_SCENE_TYPE = (short) 0xA301;
-    public static final short TAG_CFA_PATTERN = (short) 0xA302;
-    public static final short TAG_CUSTOM_RENDERED = (short) 0xA401;
-    public static final short TAG_EXPOSURE_MODE = (short) 0xA402;
-    public static final short TAG_WHITE_BALANCE = (short) 0xA403;
-    public static final short TAG_DIGITAL_ZOOM_RATIO = (short) 0xA404;
-    public static final short TAG_FOCAL_LENGTH_IN_35_MM_FILE = (short) 0xA405;
-    public static final short TAG_SCENE_CAPTURE_TYPE = (short) 0xA406;
-    public static final short TAG_GAIN_CONTROL = (short) 0xA407;
-    public static final short TAG_CONTRAST = (short) 0xA408;
-    public static final short TAG_SATURATION = (short) 0xA409;
-    public static final short TAG_SHARPNESS = (short) 0xA40A;
-    public static final short TAG_DEVICE_SETTING_DESCRIPTION = (short) 0xA40B;
-    public static final short TAG_SUBJECT_DISTANCE_RANGE = (short) 0xA40C;
-    public static final short TAG_IMAGE_UNIQUE_ID = (short) 0xA420;
-
-    // GPS tags
-    public static final short TAG_GPS_VERSION_ID = 0;
-    public static final short TAG_GPS_LATITUDE_REF = 1;
-    public static final short TAG_GPS_LATITUDE = 2;
-    public static final short TAG_GPS_LONGITUDE_REF = 3;
-    public static final short TAG_GPS_LONGITUDE = 4;
-    public static final short TAG_GPS_ALTITUDE_REF = 5;
-    public static final short TAG_GPS_ALTITUDE = 6;
-    public static final short TAG_GPS_TIME_STAMP = 7;
-    public static final short TAG_GPS_SATTELLITES = 8;
-    public static final short TAG_GPS_STATUS = 9;
-    public static final short TAG_GPS_MEASURE_MODE = 10;
-    public static final short TAG_GPS_DOP = 11;
-    public static final short TAG_GPS_SPEED_REF = 12;
-    public static final short TAG_GPS_SPEED = 13;
-    public static final short TAG_GPS_TRACK_REF = 14;
-    public static final short TAG_GPS_TRACK = 15;
-    public static final short TAG_GPS_IMG_DIRECTION_REF = 16;
-    public static final short TAG_GPS_IMG_DIRECTION = 17;
-    public static final short TAG_GPS_MAP_DATUM = 18;
-    public static final short TAG_GPS_DEST_LATITUDE_REF = 19;
-    public static final short TAG_GPS_DEST_LATITUDE = 20;
-    public static final short TAG_GPS_DEST_LONGITUDE_REF = 21;
-    public static final short TAG_GPS_DEST_LONGITUDE = 22;
-    public static final short TAG_GPS_DEST_BEARING_REF = 23;
-    public static final short TAG_GPS_DEST_BEARING = 24;
-    public static final short TAG_GPS_DEST_DISTANCE_REF = 25;
-    public static final short TAG_GPS_DEST_DISTANCE = 26;
-    public static final short TAG_GPS_PROCESSING_METHOD = 27;
-    public static final short TAG_GPS_AREA_INFORMATION = 28;
-    public static final short TAG_GPS_DATA_STAMP = 29;
-    public static final short TAG_GPS_DIFFERENTIAL = 30;
-
-    // Interoperability tag
-    public static final short TAG_INTEROPERABILITY_INDEX = 1;
-
-    /**
-     * Constants for {@link #TAG_ORIENTATION}
-     */
-    public static interface Orientation {
-        public static final short TOP_LEFT = 1;
-        public static final short TOP_RIGHT = 2;
-        public static final short BOTTOM_LEFT = 3;
-        public static final short BOTTOM_RIGHT = 4;
-        public static final short LEFT_TOP = 5;
-        public static final short RIGHT_TOP = 6;
-        public static final short LEFT_BOTTOM = 7;
-        public static final short RIGHT_BOTTOM = 8;
-    }
-
-    /**
-     * Constants for {@link #TAG_Y_CB_CR_POSITIONING}
-     */
-    public static interface YCbCrPositioning {
-        public static final short CENTERED = 1;
-        public static final short CO_SITED = 2;
-    }
-
-    /**
-     * Constants for {@link #TAG_COMPRESSION}
-     */
-    public static interface Compression {
-        public static final short UNCOMPRESSION = 1;
-        public static final short JPEG = 6;
-    }
-
-    /**
-     * Constants for {@link #TAG_RESOLUTION_UNIT}
-     */
-    public static interface ResolutionUnit {
-        public static final short INCHES = 2;
-        public static final short CENTIMETERS = 3;
-    }
-
-    /**
-     * Constants for {@link #TAG_PHOTOMETRIC_INTERPRETATION}
-     */
-    public static interface PhotometricInterpretation {
-        public static final short RGB = 2;
-        public static final short YCBCR = 6;
-    }
-
-    /**
-     * Constants for {@link #TAG_PLANAR_CONFIGURATION}
-     */
-    public static interface PlanarConfiguration {
-        public static final short CHUNKY = 1;
-        public static final short PLANAR = 2;
-    }
-
-    /**
-     * Constants for {@link #TAG_EXPOSURE_PROGRAM}
-     */
-    public static interface ExposureProgram {
-        public static final short NOT_DEFINED = 0;
-        public static final short MANUAL = 1;
-        public static final short NORMAL_PROGRAM = 2;
-        public static final short APERTURE_PRIORITY = 3;
-        public static final short SHUTTER_PRIORITY = 4;
-        public static final short CREATIVE_PROGRAM = 5;
-        public static final short ACTION_PROGRAM = 6;
-        public static final short PROTRAIT_MODE = 7;
-        public static final short LANDSCAPE_MODE = 8;
-    }
-
-    /**
-     * Constants for {@link #TAG_METERING_MODE}
-     */
-    public static interface MeteringMode {
-        public static final short UNKNOWN = 0;
-        public static final short AVERAGE = 1;
-        public static final short CENTER_WEIGHTED_AVERAGE = 2;
-        public static final short SPOT = 3;
-        public static final short MULTISPOT = 4;
-        public static final short PATTERN = 5;
-        public static final short PARTAIL = 6;
-        public static final short OTHER = 255;
-    }
-
-    /**
-     * Constants for {@link #TAG_FLASH} As the definition in Jeita EXIF 2.2 standard, we can
-     * treat this constant as bitwise flag.
-     * <p>
-     * e.g.
-     * <p>
-     * short flash = FIRED | RETURN_STROBE_RETURN_LIGHT_DETECTED | MODE_AUTO_MODE
-     */
-    public static interface Flash {
-        // LSB
-        public static final short DID_NOT_FIRED = 0;
-        public static final short FIRED = 1;
-        // 1st~2nd bits
-        public static final short RETURN_NO_STROBE_RETURN_DETECTION_FUNCTION = 0 << 1;
-        public static final short RETURN_STROBE_RETURN_LIGHT_NOT_DETECTED = 2 << 1;
-        public static final short RETURN_STROBE_RETURN_LIGHT_DETECTED = 3 << 1;
-        // 3rd~4th bits
-        public static final short MODE_UNKNOWN = 0 << 3;
-        public static final short MODE_COMPULSORY_FLASH_FIRING = 1 << 3;
-        public static final short MODE_COMPULSORY_FLASH_SUPPRESSION = 2 << 3;
-        public static final short MODE_AUTO_MODE = 3 << 3;
-        // 5th bit
-        public static final short FUNCTION_PRESENT = 0 << 5;
-        public static final short FUNCTION_NO_FUNCTION = 1 << 5;
-        // 6th bit
-        public static final short RED_EYE_REDUCTION_NO_OR_UNKNOWN = 0 << 6;
-        public static final short RED_EYE_REDUCTION_SUPPORT = 1 << 6;
-    }
-
-    /**
-     * Constants for {@link #TAG_COLOR_SPACE}
-     */
-    public static interface ColorSpace {
-        public static final short SRGB = 1;
-        public static final short UNCALIBRATED = (short) 0xFFFF;
-    }
-
-    /**
-     * Constants for {@link #TAG_EXPOSURE_MODE}
-     */
-    public static interface ExposureMode {
-        public static final short AUTO_EXPOSURE = 0;
-        public static final short MANUAL_EXPOSURE = 1;
-        public static final short AUTO_BRACKET = 2;
-    }
-
-    /**
-     * Constants for {@link #TAG_WHITE_BALANCE}
-     */
-    public static interface WhiteBalance {
-        public static final short AUTO = 0;
-        public static final short MANUAL = 1;
-    }
-
-    /**
-     * Constants for {@link #TAG_SCENE_CAPTURE_TYPE}
-     */
-    public static interface SceneCapture {
-        public static final short STANDARD = 0;
-        public static final short LANDSCAPE = 1;
-        public static final short PROTRAIT = 2;
-        public static final short NIGHT_SCENE = 3;
-    }
-
-    /**
-     * Constants for {@link #TAG_COMPONENTS_CONFIGURATION}
-     */
-    public static interface ComponentsConfiguration {
-        public static final short NOT_EXIST = 0;
-        public static final short Y = 1;
-        public static final short CB = 2;
-        public static final short CR = 3;
-        public static final short R = 4;
-        public static final short G = 5;
-        public static final short B = 6;
-    }
-
-    /**
-     * Constants for {@link #TAG_LIGHT_SOURCE}
-     */
-    public static interface LightSource {
-        public static final short UNKNOWN = 0;
-        public static final short DAYLIGHT = 1;
-        public static final short FLUORESCENT = 2;
-        public static final short TUNGSTEN = 3;
-        public static final short FLASH = 4;
-        public static final short FINE_WEATHER = 9;
-        public static final short CLOUDY_WEATHER = 10;
-        public static final short SHADE = 11;
-        public static final short DAYLIGHT_FLUORESCENT = 12;
-        public static final short DAY_WHITE_FLUORESCENT = 13;
-        public static final short COOL_WHITE_FLUORESCENT = 14;
-        public static final short WHITE_FLUORESCENT = 15;
-        public static final short STANDARD_LIGHT_A = 17;
-        public static final short STANDARD_LIGHT_B = 18;
-        public static final short STANDARD_LIGHT_C = 19;
-        public static final short D55 = 20;
-        public static final short D65 = 21;
-        public static final short D75 = 22;
-        public static final short D50 = 23;
-        public static final short ISO_STUDIO_TUNGSTEN = 24;
-        public static final short OTHER = 255;
-    }
-
-    /**
-     * Constants for {@link #TAG_SENSING_METHOD}
-     */
-    public static interface SensingMethod {
-        public static final short NOT_DEFINED = 1;
-        public static final short ONE_CHIP_COLOR = 2;
-        public static final short TWO_CHIP_COLOR = 3;
-        public static final short THREE_CHIP_COLOR = 4;
-        public static final short COLOR_SEQUENTIAL_AREA = 5;
-        public static final short TRILINEAR = 7;
-        public static final short COLOR_SEQUENTIAL_LINEAR = 8;
-    }
-
-    /**
-     * Constants for {@link #TAG_FILE_SOURCE}
-     */
-    public static interface FileSource {
-        public static final short DSC = 3;
-    }
-
-    /**
-     * Constants for {@link #TAG_SCENE_TYPE}
-     */
-    public static interface SceneType {
-        public static final short DIRECT_PHOTOGRAPHED = 1;
-    }
-
-    /**
-     * Constants for {@link #TAG_GAIN_CONTROL}
-     */
-    public static interface GainControl {
-        public static final short NONE = 0;
-        public static final short LOW_UP = 1;
-        public static final short HIGH_UP = 2;
-        public static final short LOW_DOWN = 3;
-        public static final short HIGH_DOWN = 4;
-    }
-
-    /**
-     * Constants for {@link #TAG_CONTRAST}
-     */
-    public static interface Contrast {
-        public static final short NORMAL = 0;
-        public static final short SOFT = 1;
-        public static final short HARD = 2;
-    }
-
-    /**
-     * Constants for {@link #TAG_SATURATION}
-     */
-    public static interface Saturation {
-        public static final short NORMAL = 0;
-        public static final short LOW = 1;
-        public static final short HIGH = 2;
-    }
-
-    /**
-     * Constants for {@link #TAG_SHARPNESS}
-     */
-    public static interface Sharpness {
-        public static final short NORMAL = 0;
-        public static final short SOFT = 1;
-        public static final short HARD = 2;
-    }
-
-    /**
-     * Constants for {@link #TAG_SUBJECT_DISTANCE}
-     */
-    public static interface SubjectDistance {
-        public static final short UNKNOWN = 0;
-        public static final short MACRO = 1;
-        public static final short CLOSE_VIEW = 2;
-        public static final short DISTANT_VIEW = 3;
-    }
-
-    /**
-     * Constants for {@link #TAG_GPS_LATITUDE_REF}, {@link #TAG_GPS_DEST_LATITUDE_REF}
-     */
-    public static interface GpsLatitudeRef {
-        public static final String NORTH = "N";
-        public static final String SOUTH = "S";
-    }
-
-    /**
-     * Constants for {@link #TAG_GPS_LONGITUDE_REF}, {@link #TAG_GPS_DEST_LONGITUDE_REF}
-     */
-    public static interface GpsLongitudeRef {
-        public static final String EAST = "E";
-        public static final String WEST = "W";
-    }
-
-    /**
-     * Constants for {@link #TAG_GPS_ALTITUDE_REF}
-     */
-    public static interface GpsAltitudeRef {
-        public static final short SEA_LEVEL = 0;
-        public static final short SEA_LEVEL_NEGATIVE = 1;
-    }
-
-    /**
-     * Constants for {@link #TAG_GPS_STATUS}
-     */
-    public static interface GpsStatus {
-        public static final String IN_PROGRESS = "A";
-        public static final String INTEROPERABILITY = "V";
-    }
-
-    /**
-     * Constants for {@link #TAG_GPS_MEASURE_MODE}
-     */
-    public static interface GpsMeasureMode {
-        public static final String MODE_2_DIMENSIONAL = "2";
-        public static final String MODE_3_DIMENSIONAL = "3";
-    }
-
-    /**
-     * Constants for {@link #TAG_GPS_SPEED_REF}, {@link #TAG_GPS_DEST_DISTANCE_REF}
-     */
-    public static interface GpsSpeedRef {
-        public static final String KILOMETERS = "K";
-        public static final String MILES = "M";
-        public static final String KNOTS = "N";
-    }
-
-    /**
-     * Constants for {@link #TAG_GPS_TRACK_REF}, {@link #TAG_GPS_IMG_DIRECTION_REF},
-     * {@link #TAG_GPS_DEST_BEARING_REF}
-     */
-    public static interface GpsTrackRef {
-        public static final String TRUE_DIRECTION = "T";
-        public static final String MAGNETIC_DIRECTION = "M";
-    }
-
-    /**
-     * Constants for {@link #TAG_GPS_DIFFERENTIAL}
-     */
-    public static interface GpsDifferential {
-        public static final short WITHOUT_DIFFERENTIAL_CORRECTION = 0;
-        public static final short DIFFERENTIAL_CORRECTION_APPLIED = 1;
-    }
-
     /**
      * The BYTE type in the EXIF standard. An 8-bit unsigned integer.
      */
     public static final short TYPE_UNSIGNED_BYTE = 1;
     /**
-     * The ASCII type in the EXIF standard. An 8-bit byte containing one 7-bit ASCII code.
-     * The final byte is terminated with NULL.
+     * The ASCII type in the EXIF standard. An 8-bit byte containing one 7-bit
+     * ASCII code. The final byte is terminated with NULL.
      */
     public static final short TYPE_ASCII = 2;
     /**
@@ -509,13 +47,13 @@ public class ExifTag {
      */
     public static final short TYPE_UNSIGNED_LONG = 4;
     /**
-     * The RATIONAL type of EXIF standard. It consists of two LONGs. The first one is the numerator
-     * and the second one expresses the denominator.
+     * The RATIONAL type of EXIF standard. It consists of two LONGs. The first
+     * one is the numerator and the second one expresses the denominator.
      */
     public static final short TYPE_UNSIGNED_RATIONAL = 5;
     /**
-     * The UNDEFINED type in the EXIF standard. An 8-bit byte that can take any value
-     * depending on the field definition.
+     * The UNDEFINED type in the EXIF standard. An 8-bit byte that can take any
+     * value depending on the field definition.
      */
     public static final short TYPE_UNDEFINED = 7;
     /**
@@ -524,12 +62,18 @@ public class ExifTag {
      */
     public static final short TYPE_LONG = 9;
     /**
-     * The SRATIONAL type of EXIF standard. It consists of two SLONGs. The first one is the
-     * numerator and the second one is the denominator.
+     * The SRATIONAL type of EXIF standard. It consists of two SLONGs. The first
+     * one is the numerator and the second one is the denominator.
      */
     public static final short TYPE_RATIONAL = 10;
 
+    private static Charset US_ASCII = Charset.forName("US-ASCII");
     private static final int TYPE_TO_SIZE_MAP[] = new int[11];
+    private static final int UNSIGNED_SHORT_MAX = 65535;
+    private static final long UNSIGNED_LONG_MAX = 4294967295L;
+    private static final long LONG_MAX = Integer.MAX_VALUE;
+    private static final long LONG_MIN = Integer.MIN_VALUE;
+
     static {
         TYPE_TO_SIZE_MAP[TYPE_UNSIGNED_BYTE] = 1;
         TYPE_TO_SIZE_MAP[TYPE_ASCII] = 1;
@@ -541,391 +85,69 @@ public class ExifTag {
         TYPE_TO_SIZE_MAP[TYPE_RATIONAL] = 8;
     }
 
-    /**
-     * Gets the element size of the given data type.
-     *
-     * @see #TYPE_ASCII
-     * @see #TYPE_LONG
-     * @see #TYPE_RATIONAL
-     * @see #TYPE_UNDEFINED
-     * @see #TYPE_UNSIGNED_BYTE
-     * @see #TYPE_UNSIGNED_LONG
-     * @see #TYPE_UNSIGNED_RATIONAL
-     * @see #TYPE_UNSIGNED_SHORT
-     */
-    public static int getElementSize(short type) {
-        return TYPE_TO_SIZE_MAP[type];
-    }
-
-    private static volatile SparseArray<Integer> sTagInfo = null;
-    private static volatile SparseArray<Integer> sInteroperTagInfo = null;
-    private static final int SIZE_UNDEFINED = 0;
+    static final int SIZE_UNDEFINED = 0;
 
-    private static SparseArray<Integer> getTagInfo() {
-        if (sTagInfo == null) {
-            synchronized(ExifTag.class) {
-                if (sTagInfo == null) {
-                    sTagInfo = new SparseArray<Integer>();
-                    initTagInfo();
-                }
-            }
-        }
-        return sTagInfo;
-    }
-
-    private static SparseArray<Integer> getInteroperTagInfo() {
-        if (sInteroperTagInfo == null) {
-            synchronized(ExifTag.class) {
-                if (sInteroperTagInfo == null) {
-                    sInteroperTagInfo = new SparseArray<Integer>();
-                    sInteroperTagInfo.put(TAG_INTEROPERABILITY_INDEX,
-                            (IfdId.TYPE_IFD_INTEROPERABILITY << 24)
-                            | TYPE_ASCII << 16 | SIZE_UNDEFINED);
-                }
-            }
-        }
-        return sInteroperTagInfo;
-    }
-
-    private static void initTagInfo() {
-        /**
-         * We put tag information in a 4-bytes integer. The first byte is the
-         * IFD of the tag, and the second byte is the default data type. The
-         * last two byte are a short value indicating the component count of this
-         * tag.
-         */
-        sTagInfo.put(TAG_MAKE,
-                (IfdId.TYPE_IFD_0 << 24) | TYPE_ASCII << 16 | SIZE_UNDEFINED);
-        sTagInfo.put(TAG_IMAGE_WIDTH,
-                (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_LONG << 16 | 1);
-        sTagInfo.put(TAG_IMAGE_LENGTH,
-                (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_LONG << 16 | 1);
-        sTagInfo.put(TAG_BITS_PER_SAMPLE,
-                (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_SHORT << 16 | 3);
-        sTagInfo.put(TAG_COMPRESSION,
-                (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_SHORT << 16 | 1);
-        sTagInfo.put(TAG_PHOTOMETRIC_INTERPRETATION,
-                (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_SHORT << 16 | 1);
-        sTagInfo.put(TAG_ORIENTATION, (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_SHORT << 16 | 1);
-        sTagInfo.put(TAG_SAMPLES_PER_PIXEL,
-                (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_SHORT << 16 | 1);
-        sTagInfo.put(TAG_PLANAR_CONFIGURATION,
-                (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_SHORT << 16 | 1);
-        sTagInfo.put(TAG_Y_CB_CR_SUB_SAMPLING,
-                (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_SHORT << 16 | 2);
-        sTagInfo.put(TAG_Y_CB_CR_POSITIONING,
-                (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_SHORT << 16 | 1);
-        sTagInfo.put(TAG_X_RESOLUTION,
-                (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1);
-        sTagInfo.put(TAG_Y_RESOLUTION,
-                (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1);
-        sTagInfo.put(TAG_RESOLUTION_UNIT,
-                (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_SHORT << 16 | 1);
-        sTagInfo.put(TAG_STRIP_OFFSETS,
-                (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_LONG << 16 | SIZE_UNDEFINED);
-        sTagInfo.put(TAG_ROWS_PER_STRIP,
-                (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_LONG << 16 | 1);
-        sTagInfo.put(TAG_STRIP_BYTE_COUNTS,
-                (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_LONG << 16 | SIZE_UNDEFINED);
-        sTagInfo.put(TAG_JPEG_INTERCHANGE_FORMAT,
-                (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_LONG << 16 | 1);
-        sTagInfo.put(TAG_JPEG_INTERCHANGE_FORMAT_LENGTH,
-                (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_LONG << 16 | 1);
-        sTagInfo.put(TAG_TRANSFER_FUNCTION,
-                (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_SHORT << 16 | 3 * 256);
-        sTagInfo.put(TAG_WHITE_POINT,
-                (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 2);
-        sTagInfo.put(TAG_PRIMARY_CHROMATICITIES,
-                (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 6);
-        sTagInfo.put(TAG_Y_CB_CR_COEFFICIENTS,
-                (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 3);
-        sTagInfo.put(TAG_REFERENCE_BLACK_WHITE,
-                (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 6);
-        sTagInfo.put(TAG_DATE_TIME,
-                (IfdId.TYPE_IFD_0 << 24) | TYPE_ASCII << 16 | 20);
-        sTagInfo.put(TAG_IMAGE_DESCRIPTION,
-                (IfdId.TYPE_IFD_0 << 24) | TYPE_ASCII << 16 | SIZE_UNDEFINED);
-        sTagInfo.put(TAG_MAKE,
-                (IfdId.TYPE_IFD_0 << 24) | TYPE_ASCII << 16 | SIZE_UNDEFINED);
-        sTagInfo.put(TAG_MODEL,
-                (IfdId.TYPE_IFD_0 << 24) | TYPE_ASCII << 16 | SIZE_UNDEFINED);
-        sTagInfo.put(TAG_SOFTWARE,
-                (IfdId.TYPE_IFD_0 << 24) | TYPE_ASCII << 16 | SIZE_UNDEFINED);
-        sTagInfo.put(TAG_ARTIST,
-                (IfdId.TYPE_IFD_0 << 24) | TYPE_ASCII << 16 | SIZE_UNDEFINED);
-        sTagInfo.put(TAG_COPYRIGHT,
-                (IfdId.TYPE_IFD_0 << 24) | TYPE_ASCII << 16 | SIZE_UNDEFINED);
-        sTagInfo.put(TAG_EXIF_IFD,
-                (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_LONG << 16 | 1);
-        sTagInfo.put(TAG_GPS_IFD,
-                (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_LONG << 16 | 1);
-
-        // EXIF TAG
-        sTagInfo.put(TAG_EXIF_VERSION,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNDEFINED << 16 | 4);
-        sTagInfo.put(TAG_FLASHPIX_VERSION,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNDEFINED << 16 | 4);
-        sTagInfo.put(TAG_COLOR_SPACE,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 1);
-        sTagInfo.put(TAG_COMPONENTS_CONFIGURATION,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNDEFINED << 16 | 4);
-        sTagInfo.put(TAG_COMPRESSED_BITS_PER_PIXEL,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1);
-        sTagInfo.put(TAG_PIXEL_X_DIMENSION,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_LONG << 16 | 1);
-        sTagInfo.put(TAG_PIXEL_Y_DIMENSION,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_LONG << 16 | 1);
-        sTagInfo.put(TAG_MAKER_NOTE,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNDEFINED << 16 | SIZE_UNDEFINED);
-        sTagInfo.put(TAG_USER_COMMENT,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNDEFINED << 16 | SIZE_UNDEFINED);
-        sTagInfo.put(TAG_RELATED_SOUND_FILE,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_ASCII << 16 | 13);
-        sTagInfo.put(TAG_DATE_TIME_ORIGINAL,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_ASCII << 16 | 20);
-        sTagInfo.put(TAG_DATE_TIME_DIGITIZED,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_ASCII << 16 | 20);
-        sTagInfo.put(TAG_SUB_SEC_TIME,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_ASCII << 16 | SIZE_UNDEFINED);
-        sTagInfo.put(TAG_SUB_SEC_TIME_ORIGINAL,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_ASCII << 16 | SIZE_UNDEFINED);
-        sTagInfo.put(TAG_SUB_SEC_TIME_DIGITIZED,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_ASCII << 16 | SIZE_UNDEFINED);
-        sTagInfo.put(TAG_IMAGE_UNIQUE_ID,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_ASCII << 16 | 33);
-        sTagInfo.put(TAG_EXPOSURE_TIME,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1);
-        sTagInfo.put(TAG_F_NUMBER,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1);
-        sTagInfo.put(TAG_EXPOSURE_PROGRAM,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 1);
-        sTagInfo.put(TAG_SPECTRAL_SENSITIVITY,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_ASCII << 16 | SIZE_UNDEFINED);
-        sTagInfo.put(TAG_ISO_SPEED_RATINGS,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | SIZE_UNDEFINED);
-        sTagInfo.put(TAG_OECF,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNDEFINED << 16 | SIZE_UNDEFINED);
-        sTagInfo.put(TAG_SHUTTER_SPEED_VALUE,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_RATIONAL << 16 | 1);
-        sTagInfo.put(TAG_APERTURE_VALUE,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1);
-        sTagInfo.put(TAG_BRIGHTNESS_VALUE,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_RATIONAL << 16 | 1);
-        sTagInfo.put(TAG_EXPOSURE_BIAS_VALUE,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_RATIONAL << 16 | 1);
-        sTagInfo.put(TAG_MAX_APERTURE_VALUE,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1);
-        sTagInfo.put(TAG_SUBJECT_DISTANCE,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1);
-        sTagInfo.put(TAG_METERING_MODE,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 1);
-        sTagInfo.put(TAG_LIGHT_SOURCE,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 1);
-        sTagInfo.put(TAG_FLASH,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 1);
-        sTagInfo.put(TAG_FOCAL_LENGTH,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1);
-        sTagInfo.put(TAG_SUBJECT_AREA,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | SIZE_UNDEFINED);
-        sTagInfo.put(TAG_FLASH_ENERGY,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1);
-        sTagInfo.put(TAG_SPATIAL_FREQUENCY_RESPONSE,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNDEFINED << 16 | SIZE_UNDEFINED);
-        sTagInfo.put(TAG_FOCAL_PLANE_X_RESOLUTION,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1);
-        sTagInfo.put(TAG_FOCAL_PLANE_Y_RESOLUTION,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1);
-        sTagInfo.put(TAG_FOCAL_PLANE_RESOLUTION_UNIT,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 1);
-        sTagInfo.put(TAG_SUBJECT_LOCATION,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 2);
-        sTagInfo.put(TAG_EXPOSURE_INDEX,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1);
-        sTagInfo.put(TAG_SENSING_METHOD,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 1);
-        sTagInfo.put(TAG_FILE_SOURCE,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNDEFINED << 16 | 1);
-        sTagInfo.put(TAG_SCENE_TYPE,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNDEFINED << 16 | 1);
-        sTagInfo.put(TAG_CFA_PATTERN,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNDEFINED << 16 | SIZE_UNDEFINED);
-        sTagInfo.put(TAG_CUSTOM_RENDERED,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 1);
-        sTagInfo.put(TAG_EXPOSURE_MODE,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 1);
-        sTagInfo.put(TAG_WHITE_BALANCE,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 1);
-        sTagInfo.put(TAG_DIGITAL_ZOOM_RATIO,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1);
-        sTagInfo.put(TAG_FOCAL_LENGTH_IN_35_MM_FILE,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 1);
-        sTagInfo.put(TAG_SCENE_CAPTURE_TYPE,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 1);
-        sTagInfo.put(TAG_GAIN_CONTROL,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1);
-        sTagInfo.put(TAG_CONTRAST,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 1);
-        sTagInfo.put(TAG_SATURATION,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 1);
-        sTagInfo.put(TAG_SHARPNESS,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 1);
-        sTagInfo.put(TAG_DEVICE_SETTING_DESCRIPTION,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNDEFINED << 16 | SIZE_UNDEFINED);
-        sTagInfo.put(TAG_SUBJECT_DISTANCE_RANGE,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 1);
-        // GPS tag
-        sTagInfo.put(TAG_GPS_VERSION_ID,
-                (IfdId.TYPE_IFD_GPS << 24) | TYPE_UNSIGNED_BYTE << 16 | 4);
-        sTagInfo.put(TAG_GPS_LATITUDE_REF,
-                (IfdId.TYPE_IFD_GPS << 24) | TYPE_ASCII << 16 | 2);
-        sTagInfo.put(TAG_GPS_LONGITUDE_REF,
-                (IfdId.TYPE_IFD_GPS << 24) | TYPE_ASCII << 16 | 2);
-        sTagInfo.put(TAG_GPS_LATITUDE,
-                (IfdId.TYPE_IFD_GPS << 24) | TYPE_RATIONAL << 16 | 3);
-        sTagInfo.put(TAG_GPS_LONGITUDE,
-                (IfdId.TYPE_IFD_GPS << 24) | TYPE_RATIONAL << 16 | 3);
-        sTagInfo.put(TAG_GPS_ALTITUDE_REF,
-                (IfdId.TYPE_IFD_GPS << 24) | TYPE_UNSIGNED_BYTE << 16 | 1);
-        sTagInfo.put(TAG_GPS_ALTITUDE,
-                (IfdId.TYPE_IFD_GPS << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1);
-        sTagInfo.put(TAG_GPS_TIME_STAMP,
-                (IfdId.TYPE_IFD_GPS << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 3);
-        sTagInfo.put(TAG_GPS_SATTELLITES,
-                (IfdId.TYPE_IFD_GPS << 24) | TYPE_ASCII << 16 | SIZE_UNDEFINED);
-        sTagInfo.put(TAG_GPS_STATUS,
-                (IfdId.TYPE_IFD_GPS << 24) | TYPE_ASCII << 16 | 2);
-        sTagInfo.put(TAG_GPS_MEASURE_MODE,
-                (IfdId.TYPE_IFD_GPS << 24) | TYPE_ASCII << 16 | 2);
-        sTagInfo.put(TAG_GPS_DOP,
-                (IfdId.TYPE_IFD_GPS << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1);
-        sTagInfo.put(TAG_GPS_SPEED_REF,
-                (IfdId.TYPE_IFD_GPS << 24) | TYPE_ASCII << 16 | 2);
-        sTagInfo.put(TAG_GPS_SPEED,
-                (IfdId.TYPE_IFD_GPS << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1);
-        sTagInfo.put(TAG_GPS_TRACK_REF,
-                (IfdId.TYPE_IFD_GPS << 24) | TYPE_ASCII << 16 | 2);
-        sTagInfo.put(TAG_GPS_TRACK,
-                (IfdId.TYPE_IFD_GPS << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1);
-        sTagInfo.put(TAG_GPS_IMG_DIRECTION_REF,
-                (IfdId.TYPE_IFD_GPS << 24) | TYPE_ASCII << 16 | 2);
-        sTagInfo.put(TAG_GPS_IMG_DIRECTION,
-                (IfdId.TYPE_IFD_GPS << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1);
-        sTagInfo.put(TAG_GPS_MAP_DATUM,
-                (IfdId.TYPE_IFD_GPS << 24) | TYPE_ASCII << 16 | SIZE_UNDEFINED);
-        sTagInfo.put(TAG_GPS_DEST_LATITUDE_REF,
-                (IfdId.TYPE_IFD_GPS << 24) | TYPE_ASCII << 16 | 2);
-        sTagInfo.put(TAG_GPS_DEST_LATITUDE,
-                (IfdId.TYPE_IFD_GPS << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1);
-        sTagInfo.put(TAG_GPS_DEST_BEARING_REF,
-                (IfdId.TYPE_IFD_GPS << 24) | TYPE_ASCII << 16 | 2);
-        sTagInfo.put(TAG_GPS_DEST_BEARING,
-                (IfdId.TYPE_IFD_GPS << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1);
-        sTagInfo.put(TAG_GPS_DEST_DISTANCE_REF,
-                (IfdId.TYPE_IFD_GPS << 24) | TYPE_ASCII << 16 | 2);
-        sTagInfo.put(TAG_GPS_DEST_DISTANCE,
-                (IfdId.TYPE_IFD_GPS << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1);
-        sTagInfo.put(TAG_GPS_PROCESSING_METHOD,
-                (IfdId.TYPE_IFD_GPS << 24) | TYPE_UNDEFINED << 16 | SIZE_UNDEFINED);
-        sTagInfo.put(TAG_GPS_AREA_INFORMATION,
-                (IfdId.TYPE_IFD_GPS << 24) | TYPE_UNDEFINED << 16 | SIZE_UNDEFINED);
-        sTagInfo.put(TAG_GPS_DATA_STAMP,
-                (IfdId.TYPE_IFD_GPS << 24) | TYPE_ASCII << 16 | 11);
-        sTagInfo.put(TAG_GPS_DIFFERENTIAL,
-                (IfdId.TYPE_IFD_GPS << 24) | TYPE_UNSIGNED_SHORT << 16 | 11);
-    }
-
-    private static Charset US_ASCII = Charset.forName("US-ASCII");
+    // Exif TagId
     private final short mTagId;
+    // Exif Tag Type
     private final short mDataType;
-    private final int mIfd;
-    private final boolean mComponentCountDefined;
-    private int mComponentCount;
+    // If tag has defined count
+    private boolean mHasDefinedDefaultComponentCount;
+    // Actual data count in tag (should be number of elements in value array)
+    private int mComponentCountActual;
+    // The ifd that this tag should be put in
+    private int mIfd;
+    // The value (array of elements of type Tag Type)
     private Object mValue;
+    // Value offset in exif header.
     private int mOffset;
 
-    static private short getTypeFromInfo(int info) {
-        return (short) ((info >> 16) & 0xff);
-    }
-
-    static private int getComponentCountFromInfo(int info) {
-        return info & 0xffff;
-    }
-
-    static private int getIfdIdFromInfo(int info) {
-        return (info >> 24) & 0xff;
-    }
-
-    static private boolean getComponentCountDefined(short tagId, int ifd) {
-        Integer info = (ifd == IfdId.TYPE_IFD_INTEROPERABILITY) ?
-                getInteroperTagInfo().get(tagId) : getTagInfo().get(tagId);
-        if (info == null) return false;
-        return getComponentCountFromInfo(info) != SIZE_UNDEFINED;
-    }
-
-    static int getIfdIdFromTagId(short tagId) {
-        Integer info = getTagInfo().get(tagId);
-        if (info == null) {
-            throw new IllegalArgumentException("Unknown Tag ID: " + tagId);
-        }
-        return getIfdIdFromInfo(info);
-    }
-
-    /**
-     * Create a tag with given ID. For tags related to interoperability and thumbnail, call
-     * {@link #buildInteroperabilityTag(short)} and {@link #buildThumbnailTag(short)} respectively.
-     * @exception IllegalArgumentException If the ID is invalid.
-     */
-    static public ExifTag buildTag(short tagId) {
-        Integer info = getTagInfo().get(tagId);
-        if (info == null) {
-            throw new IllegalArgumentException("Unknown Tag ID: " + tagId);
-        }
-        return new ExifTag(tagId, getTypeFromInfo(info),
-                getComponentCountFromInfo(info),
-                getIfdIdFromInfo(info));
-    }
+    private static final SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("yyyy:MM:dd kk:mm:ss");
 
     /**
-     * Create a tag related to thumbnail with given ID.
-     * @exception IllegalArgumentException If the ID is invalid.
+     * Returns true if the given IFD is a valid IFD.
      */
-    static public ExifTag buildThumbnailTag(short tagId) {
-        Integer info = getTagInfo().get(tagId);
-        if (info == null || getIfdIdFromInfo(info) != IfdId.TYPE_IFD_0) {
-            throw new IllegalArgumentException("Unknown Thumnail Tag ID: " + tagId);
-        }
-        return new ExifTag(tagId, getTypeFromInfo(info),
-                getComponentCountFromInfo(info),
-                IfdId.TYPE_IFD_1);
+    public static boolean isValidIfd(int ifdId) {
+        return ifdId == IfdId.TYPE_IFD_0 || ifdId == IfdId.TYPE_IFD_1
+                || ifdId == IfdId.TYPE_IFD_EXIF || ifdId == IfdId.TYPE_IFD_INTEROPERABILITY
+                || ifdId == IfdId.TYPE_IFD_GPS;
     }
 
     /**
-     * Create a tag related to interoperability with given ID.
-     * @exception IllegalArgumentException If the ID is invalid.
+     * Returns true if a given type is a valid tag type.
      */
-    static public ExifTag buildInteroperabilityTag(short tagId) {
-        Integer info = getInteroperTagInfo().get(tagId);
-        if (info == null || getIfdIdFromInfo(info) != IfdId.TYPE_IFD_INTEROPERABILITY) {
-            throw new RuntimeException("Unknown Interoperability Tag ID: " + tagId);
-        }
-        return new ExifTag(tagId, getTypeFromInfo(info),
-                getComponentCountFromInfo(info),
-                IfdId.TYPE_IFD_INTEROPERABILITY);
-    }
-
-    static boolean isValidType(short type) {
+    public static boolean isValidType(short type) {
         return type == TYPE_UNSIGNED_BYTE || type == TYPE_ASCII ||
-               type == TYPE_UNSIGNED_SHORT || type == TYPE_UNSIGNED_LONG ||
-               type == TYPE_UNSIGNED_RATIONAL || type == TYPE_UNDEFINED ||
-               type == TYPE_LONG || type == TYPE_RATIONAL;
+                type == TYPE_UNSIGNED_SHORT || type == TYPE_UNSIGNED_LONG ||
+                type == TYPE_UNSIGNED_RATIONAL || type == TYPE_UNDEFINED ||
+                type == TYPE_LONG || type == TYPE_RATIONAL;
     }
 
-    ExifTag(short tagId, short type, int componentCount, int ifd) {
+    // Use builtTag in ExifInterface instead of constructor.
+    ExifTag(short tagId, short type, int componentCount, int ifd,
+            boolean hasDefinedComponentCount) {
         mTagId = tagId;
         mDataType = type;
-        mComponentCount = componentCount;
-        mComponentCountDefined = getComponentCountDefined(tagId, ifd);
+        mComponentCountActual = componentCount;
+        mHasDefinedDefaultComponentCount = hasDefinedComponentCount;
         mIfd = ifd;
+        mValue = null;
+    }
+
+    /**
+     * Gets the element size of the given data type in bytes.
+     *
+     * @see #TYPE_ASCII
+     * @see #TYPE_LONG
+     * @see #TYPE_RATIONAL
+     * @see #TYPE_UNDEFINED
+     * @see #TYPE_UNSIGNED_BYTE
+     * @see #TYPE_UNSIGNED_LONG
+     * @see #TYPE_UNSIGNED_RATIONAL
+     * @see #TYPE_UNSIGNED_SHORT
+     */
+    public static int getElementSize(short type) {
+        return TYPE_TO_SIZE_MAP[type];
     }
 
     /**
@@ -941,8 +163,12 @@ public class ExifTag {
         return mIfd;
     }
 
+    protected void setIfd(int ifdId) {
+        mIfd = ifdId;
+    }
+
     /**
-     * Gets the ID of this tag.
+     * Gets the TID of this tag.
      */
     public short getTagId() {
         return mTagId;
@@ -974,376 +200,608 @@ public class ExifTag {
     /**
      * Gets the component count of this tag.
      */
+
+    // TODO: fix integer overflows with this
     public int getComponentCount() {
-        return mComponentCount;
+        return mComponentCountActual;
     }
 
     /**
-     * Sets the component count of this tag.
-     * Call this function before setValue() if the length of value does not
-     * match the component count.
+     * Sets the component count of this tag. Call this function before
+     * setValue() if the length of value does not match the component count.
      */
-    public void setComponentCount(int count) {
-        mComponentCount = count;
+    protected void forceSetComponentCount(int count) {
+        mComponentCountActual = count;
     }
 
     /**
-     * Returns true if this ExifTag contains value; otherwise, this tag will contain an offset value
-     * that links to the area where the actual value is located.
-     *
-     * @see #getOffset()
+     * Returns true if this ExifTag contains value; otherwise, this tag will
+     * contain an offset value that is determined when the tag is written.
      */
     public boolean hasValue() {
         return mValue != null;
     }
 
     /**
-     * Gets the offset of this tag. This is only valid if this data size > 4 and contains an offset
-     * to the location of the actual value.
+     * Sets integer values into this tag. This method should be used for tags of
+     * type {@link #TYPE_UNSIGNED_SHORT}. This method will fail if:
+     * <ul>
+     * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_SHORT},
+     * {@link #TYPE_UNSIGNED_LONG}, or {@link #TYPE_LONG}.</li>
+     * <li>The value overflows.</li>
+     * <li>The value.length does NOT match the component count in the definition
+     * for this tag.</li>
+     * </ul>
      */
-    public int getOffset() {
-        return mOffset;
+    public boolean setValue(int[] value) {
+        if (checkBadComponentCount(value.length)) {
+            return false;
+        }
+        if (mDataType != TYPE_UNSIGNED_SHORT && mDataType != TYPE_LONG &&
+                mDataType != TYPE_UNSIGNED_LONG) {
+            return false;
+        }
+        if (mDataType == TYPE_UNSIGNED_SHORT && checkOverflowForUnsignedShort(value)) {
+            return false;
+        } else if (mDataType == TYPE_UNSIGNED_LONG && checkOverflowForUnsignedLong(value)) {
+            return false;
+        }
+
+        long[] data = new long[value.length];
+        for (int i = 0; i < value.length; i++) {
+            data[i] = value[i];
+        }
+        mValue = data;
+        mComponentCountActual = value.length;
+        return true;
     }
 
     /**
-     * Sets the offset of this tag.
+     * Sets integer value into this tag. This method should be used for tags of
+     * type {@link #TYPE_UNSIGNED_SHORT}, or {@link #TYPE_LONG}. This method
+     * will fail if:
+     * <ul>
+     * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_SHORT},
+     * {@link #TYPE_UNSIGNED_LONG}, or {@link #TYPE_LONG}.</li>
+     * <li>The value overflows.</li>
+     * <li>The component count in the definition of this tag is not 1.</li>
+     * </ul>
      */
-    void setOffset(int offset) {
-        mOffset = offset;
+    public boolean setValue(int value) {
+        return setValue(new int[] {
+                value
+        });
     }
 
-    private void checkComponentCountOrThrow(int count)
-            throws IllegalArgumentException {
-        if (mComponentCountDefined && (mComponentCount != count)) {
-            throw new IllegalArgumentException("Tag " + mTagId + ": Required "
-                    + mComponentCount + " components but was given " + count
-                    + " component(s)");
+    /**
+     * Sets long values into this tag. This method should be used for tags of
+     * type {@link #TYPE_UNSIGNED_LONG}. This method will fail if:
+     * <ul>
+     * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_LONG}.</li>
+     * <li>The value overflows.</li>
+     * <li>The value.length does NOT match the component count in the definition
+     * for this tag.</li>
+     * </ul>
+     */
+    public boolean setValue(long[] value) {
+        if (checkBadComponentCount(value.length) || mDataType != TYPE_UNSIGNED_LONG) {
+            return false;
+        }
+        if (checkOverflowForUnsignedLong(value)) {
+            return false;
         }
+        mValue = value;
+        mComponentCountActual = value.length;
+        return true;
     }
 
-    private void throwTypeNotMatchedException(String className)
-            throws IllegalArgumentException {
-        throw new IllegalArgumentException("Tag " + mTagId + ": expect type " +
-                convertTypeToString(mDataType) + " but got " + className);
+    /**
+     * Sets long values into this tag. This method should be used for tags of
+     * type {@link #TYPE_UNSIGNED_LONG}. This method will fail if:
+     * <ul>
+     * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_LONG}.</li>
+     * <li>The value overflows.</li>
+     * <li>The component count in the definition for this tag is not 1.</li>
+     * </ul>
+     */
+    public boolean setValue(long value) {
+        return setValue(new long[] {
+                value
+        });
     }
 
-    private static String convertTypeToString(short type) {
-        switch (type) {
-            case TYPE_UNSIGNED_BYTE:
-                return "UNSIGNED_BYTE";
-            case TYPE_ASCII:
-                return "ASCII";
-            case TYPE_UNSIGNED_SHORT:
-                return "UNSIGNED_SHORT";
-            case TYPE_UNSIGNED_LONG:
-                return "UNSIGNED_LONG";
-            case TYPE_UNSIGNED_RATIONAL:
-                return "UNSIGNED_RATIONAL";
-            case TYPE_UNDEFINED:
-                return "UNDEFINED";
-            case TYPE_LONG:
-                return "LONG";
-            case TYPE_RATIONAL:
-                return "RATIONAL";
-            default:
-                return "";
+    /**
+     * Sets a string value into this tag. This method should be used for tags of
+     * type {@link #TYPE_ASCII}. The string is converted to an ASCII string.
+     * Characters that cannot be converted are replaced with '?'. The length of
+     * the string must be equal to either (component count -1) or (component
+     * count). The final byte will be set to the string null terminator '\0',
+     * overwriting the last character in the string if the value.length is equal
+     * to the component count. This method will fail if:
+     * <ul>
+     * <li>The data type is not {@link #TYPE_ASCII} or {@link #TYPE_UNDEFINED}.</li>
+     * <li>The length of the string is not equal to (component count -1) or
+     * (component count) in the definition for this tag.</li>
+     * </ul>
+     */
+    public boolean setValue(String value) {
+        if (mDataType != TYPE_ASCII && mDataType != TYPE_UNDEFINED) {
+            return false;
         }
-    }
 
-    private static final int UNSIGNED_SHORT_MAX = 65535;
-    private static final long UNSIGNED_LONG_MAX = 4294967295L;
-    private static final long LONG_MAX = Integer.MAX_VALUE;
-    private static final long LONG_MIN = Integer.MIN_VALUE;
-
-    private void checkOverflowForUnsignedShort(int[] value) {
-        for (int v : value) {
-            if (v > UNSIGNED_SHORT_MAX || v < 0) {
-                throw new IllegalArgumentException(
-                        "Tag " + mTagId+ ": Value" + v +
-                        " is illegal for type UNSIGNED_SHORT");
-            }
+        byte[] buf = value.getBytes(US_ASCII);
+        byte[] finalBuf = buf;
+        if (buf.length > 0) {
+            finalBuf = (buf[buf.length - 1] == 0 || mDataType == TYPE_UNDEFINED) ? buf : Arrays
+                .copyOf(buf, buf.length + 1);
+        } else if (mDataType == TYPE_ASCII && mComponentCountActual == 1) {
+            finalBuf = new byte[] { 0 };
         }
-    }
-
-    private void checkOverflowForUnsignedLong(long[] value) {
-        for (long v: value) {
-            if (v < 0 || v > UNSIGNED_LONG_MAX) {
-                throw new IllegalArgumentException(
-                        "Tag " + mTagId+ ": Value" + v +
-                        " is illegal for type UNSIGNED_LONG");
-            }
+        int count = finalBuf.length;
+        if (checkBadComponentCount(count)) {
+            return false;
         }
+        mComponentCountActual = count;
+        mValue = finalBuf;
+        return true;
     }
 
-    private void checkOverflowForUnsignedLong(int[] value) {
-        for (int v: value) {
-            if (v < 0) {
-                throw new IllegalArgumentException(
-                        "Tag " + mTagId+ ": Value" + v +
-                        " is illegal for type UNSIGNED_LONG");
-            }
+    /**
+     * Sets Rational values into this tag. This method should be used for tags
+     * of type {@link #TYPE_UNSIGNED_RATIONAL}, or {@link #TYPE_RATIONAL}. This
+     * method will fail if:
+     * <ul>
+     * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_RATIONAL}
+     * or {@link #TYPE_RATIONAL}.</li>
+     * <li>The value overflows.</li>
+     * <li>The value.length does NOT match the component count in the definition
+     * for this tag.</li>
+     * </ul>
+     *
+     * @see Rational
+     */
+    public boolean setValue(Rational[] value) {
+        if (checkBadComponentCount(value.length)) {
+            return false;
         }
-    }
-
-    private void checkOverflowForUnsignedRational(Rational[] value) {
-        for (Rational v: value) {
-            if (v.getNominator() < 0 || v.getDenominator() < 0
-                    || v.getNominator() > UNSIGNED_LONG_MAX
-                    || v.getDenominator() > UNSIGNED_LONG_MAX) {
-                throw new IllegalArgumentException(
-                        "Tag " + mTagId+ ": Value" + v +
-                        " is illegal for type UNSIGNED_RATIONAL");
-            }
+        if (mDataType != TYPE_UNSIGNED_RATIONAL && mDataType != TYPE_RATIONAL) {
+            return false;
         }
+        if (mDataType == TYPE_UNSIGNED_RATIONAL && checkOverflowForUnsignedRational(value)) {
+            return false;
+        } else if (mDataType == TYPE_RATIONAL && checkOverflowForRational(value)) {
+            return false;
+        }
+
+        mValue = value;
+        mComponentCountActual = value.length;
+        return true;
     }
 
-    private void checkOverflowForRational(Rational[] value) {
-        for (Rational v: value) {
-            if (v.getNominator() < LONG_MIN || v.getDenominator() < LONG_MIN
-                    || v.getNominator() > LONG_MAX
-                    || v.getDenominator() > LONG_MAX) {
-                throw new IllegalArgumentException(
-                        "Tag " + mTagId+ ": Value" + v +
-                        " is illegal for type RATIONAL");
-            }
-        }
+    /**
+     * Sets a Rational value into this tag. This method should be used for tags
+     * of type {@link #TYPE_UNSIGNED_RATIONAL}, or {@link #TYPE_RATIONAL}. This
+     * method will fail if:
+     * <ul>
+     * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_RATIONAL}
+     * or {@link #TYPE_RATIONAL}.</li>
+     * <li>The value overflows.</li>
+     * <li>The component count in the definition for this tag is not 1.</li>
+     * </ul>
+     *
+     * @see Rational
+     */
+    public boolean setValue(Rational value) {
+        return setValue(new Rational[] {
+                value
+        });
     }
 
     /**
-     * Sets integer values into this tag.
-     * @exception IllegalArgumentException for the following situation:
+     * Sets byte values into this tag. This method should be used for tags of
+     * type {@link #TYPE_UNSIGNED_BYTE} or {@link #TYPE_UNDEFINED}. This method
+     * will fail if:
      * <ul>
-     *     <li>The component type of this tag is not {@link #TYPE_UNSIGNED_SHORT},
-     *      {@link #TYPE_UNSIGNED_LONG}, or {@link #TYPE_LONG}.</li>
-     *     <li>The value overflows. </li>
-     *     <li>The value.length does NOT match the definition of component count in
-     *      EXIF standard.</li>
+     * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_BYTE} or
+     * {@link #TYPE_UNDEFINED} .</li>
+     * <li>The length does NOT match the component count in the definition for
+     * this tag.</li>
      * </ul>
      */
-    public void setValue(int[] value) {
-        checkComponentCountOrThrow(value.length);
-        if (mDataType != TYPE_UNSIGNED_SHORT && mDataType != TYPE_LONG &&
-                mDataType != TYPE_UNSIGNED_LONG) {
-            throwTypeNotMatchedException("int");
+    public boolean setValue(byte[] value, int offset, int length) {
+        if (checkBadComponentCount(length)) {
+            return false;
         }
-        if (mDataType == TYPE_UNSIGNED_SHORT) {
-            checkOverflowForUnsignedShort(value);
-        } else if (mDataType == TYPE_UNSIGNED_LONG) {
-            checkOverflowForUnsignedLong(value);
+        if (mDataType != TYPE_UNSIGNED_BYTE && mDataType != TYPE_UNDEFINED) {
+            return false;
         }
+        mValue = new byte[length];
+        System.arraycopy(value, offset, mValue, 0, length);
+        mComponentCountActual = length;
+        return true;
+    }
 
-        long[] data = new long[value.length];
-        for (int i = 0; i < value.length; i++) {
-            data[i] = value[i];
-        }
-        mValue = data;
-        mComponentCount = value.length;
+    /**
+     * Equivalent to setValue(value, 0, value.length).
+     */
+    public boolean setValue(byte[] value) {
+        return setValue(value, 0, value.length);
     }
 
     /**
-     * Sets integer values into this tag.
-     * @exception IllegalArgumentException For the following situation:
+     * Sets byte value into this tag. This method should be used for tags of
+     * type {@link #TYPE_UNSIGNED_BYTE} or {@link #TYPE_UNDEFINED}. This method
+     * will fail if:
      * <ul>
-     *     <li>The component type of this tag is not {@link #TYPE_UNSIGNED_SHORT},
-     *      {@link #TYPE_UNSIGNED_LONG}, or {@link #TYPE_LONG}.</li>
-     *     <li>The value overflows.</li>
-     *     <li>The component count in the definition of EXIF standard is not 1.</li>
+     * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_BYTE} or
+     * {@link #TYPE_UNDEFINED} .</li>
+     * <li>The component count in the definition for this tag is not 1.</li>
      * </ul>
      */
-    public void setValue(int value) {
-        checkComponentCountOrThrow(1);
-        setValue(new int[] {value});
+    public boolean setValue(byte value) {
+        return setValue(new byte[] {
+                value
+        });
     }
 
     /**
-     * Sets long values into this tag.
-     * @exception IllegalArgumentException For the following situation:
+     * Sets the value for this tag using an appropriate setValue method for the
+     * given object. This method will fail if:
      * <ul>
-     *      <li>The component type of this tag is not {@link #TYPE_UNSIGNED_LONG}.</li>
-     *      <li>The value overflows. </li>
-     *      <li>The value.length does NOT match the definition of component count in
-     *       EXIF standard.</li>
+     * <li>The corresponding setValue method for the class of the object passed
+     * in would fail.</li>
+     * <li>There is no obvious way to cast the object passed in into an EXIF tag
+     * type.</li>
      * </ul>
      */
-    public void setValue(long[] value) {
-        checkComponentCountOrThrow(value.length);
-        if (mDataType != TYPE_UNSIGNED_LONG) {
-            throwTypeNotMatchedException("long");
+    public boolean setValue(Object obj) {
+        if (obj == null) {
+            return false;
+        } else if (obj instanceof Short) {
+            return setValue(((Short) obj).shortValue() & 0x0ffff);
+        } else if (obj instanceof String) {
+            return setValue((String) obj);
+        } else if (obj instanceof int[]) {
+            return setValue((int[]) obj);
+        } else if (obj instanceof long[]) {
+            return setValue((long[]) obj);
+        } else if (obj instanceof Rational) {
+            return setValue((Rational) obj);
+        } else if (obj instanceof Rational[]) {
+            return setValue((Rational[]) obj);
+        } else if (obj instanceof byte[]) {
+            return setValue((byte[]) obj);
+        } else if (obj instanceof Integer) {
+            return setValue(((Integer) obj).intValue());
+        } else if (obj instanceof Long) {
+            return setValue(((Long) obj).longValue());
+        } else if (obj instanceof Byte) {
+            return setValue(((Byte) obj).byteValue());
+        } else if (obj instanceof Short[]) {
+            // Nulls in this array are treated as zeroes.
+            Short[] arr = (Short[]) obj;
+            int[] fin = new int[arr.length];
+            for (int i = 0; i < arr.length; i++) {
+                fin[i] = (arr[i] == null) ? 0 : arr[i].shortValue() & 0x0ffff;
+            }
+            return setValue(fin);
+        } else if (obj instanceof Integer[]) {
+            // Nulls in this array are treated as zeroes.
+            Integer[] arr = (Integer[]) obj;
+            int[] fin = new int[arr.length];
+            for (int i = 0; i < arr.length; i++) {
+                fin[i] = (arr[i] == null) ? 0 : arr[i].intValue();
+            }
+            return setValue(fin);
+        } else if (obj instanceof Long[]) {
+            // Nulls in this array are treated as zeroes.
+            Long[] arr = (Long[]) obj;
+            long[] fin = new long[arr.length];
+            for (int i = 0; i < arr.length; i++) {
+                fin[i] = (arr[i] == null) ? 0 : arr[i].longValue();
+            }
+            return setValue(fin);
+        } else if (obj instanceof Byte[]) {
+            // Nulls in this array are treated as zeroes.
+            Byte[] arr = (Byte[]) obj;
+            byte[] fin = new byte[arr.length];
+            for (int i = 0; i < arr.length; i++) {
+                fin[i] = (arr[i] == null) ? 0 : arr[i].byteValue();
+            }
+            return setValue(fin);
+        } else {
+            return false;
         }
-        checkOverflowForUnsignedLong(value);
-        mValue = value;
-        mComponentCount = value.length;
     }
 
     /**
-     * Sets long values into this tag.
-     * @exception IllegalArgumentException For the following situation:
-     * <ul>
-     *     <li>The component type of this tag is not {@link #TYPE_UNSIGNED_LONG}.</li>
-     *     <li>The value overflows. </li>
-     *     <li>The component count in the definition of EXIF standard is not 1.</li>
-     * </ul>
+     * Sets a timestamp to this tag. The method converts the timestamp with the
+     * format of "yyyy:MM:dd kk:mm:ss" and calls {@link #setValue(String)}. This
+     * method will fail if the data type is not {@link #TYPE_ASCII} or the
+     * component count of this tag is not 20 or undefined.
+     *
+     * @param time the number of milliseconds since Jan. 1, 1970 GMT
+     * @return true on success
      */
-    public void setValue(long value) {
-        setValue(new long[] {value});
+    public boolean setTimeValue(long time) {
+        // synchronized on TIME_FORMAT as SimpleDateFormat is not thread safe
+        synchronized (TIME_FORMAT) {
+            return setValue(TIME_FORMAT.format(new Date(time)));
+        }
     }
 
     /**
-     * Sets a string value into this tag. The value is treated as an ASCII string where we only
-     * preserve the lower byte of each character. The length of the string should be equal
-     * to either (component count -1) or (component count). A "0" byte will be appeneded while
-     * written to the EXIF file. If the length equals (component count), the final byte will be
-     * replaced by a "0" byte.
+     * Gets the value as a String. This method should be used for tags of type
+     * {@link #TYPE_ASCII}.
      *
-     * @exception IllegalArgumentException If the data type is not {@link #TYPE_ASCII}
-     * or the length of the string is not equal to (component count -1) and (component count)
+     * @return the value as a String, or null if the tag's value does not exist
+     *         or cannot be converted to a String.
      */
-    public void setValue(String value) {
-        if (mDataType != TYPE_ASCII) {
-            throwTypeNotMatchedException("String");
+    public String getValueAsString() {
+        if (mValue == null) {
+            return null;
+        } else if (mValue instanceof String) {
+            return (String) mValue;
+        } else if (mValue instanceof byte[]) {
+            return new String((byte[]) mValue, US_ASCII);
         }
+        return null;
+    }
 
-        byte[] buf = new byte[value.length()];
-        for (int i = 0, n = value.length(); i < n; i++) {
-            buf[i] = (byte) value.charAt(i);
+    /**
+     * Gets the value as a String. This method should be used for tags of type
+     * {@link #TYPE_ASCII}.
+     *
+     * @param defaultValue the String to return if the tag's value does not
+     *            exist or cannot be converted to a String.
+     * @return the tag's value as a String, or the defaultValue.
+     */
+    public String getValueAsString(String defaultValue) {
+        String s = getValueAsString();
+        if (s == null) {
+            return defaultValue;
         }
+        return s;
+    }
 
-        int count = buf.length;
-        if (mComponentCountDefined) {
-            if (mComponentCount != count && mComponentCount != count + 1) {
-                throw new IllegalArgumentException("Tag " + mTagId + ": Required "
-                        + mComponentCount + " or " + (mComponentCount + 1)
-                        + " components but was given " + count
-                        + " component(s)");
-            }
-        } else {
-            mComponentCount = buf[count - 1] == 0 ? count : count + 1;
+    /**
+     * Gets the value as a byte array. This method should be used for tags of
+     * type {@link #TYPE_UNDEFINED} or {@link #TYPE_UNSIGNED_BYTE}.
+     *
+     * @return the value as a byte array, or null if the tag's value does not
+     *         exist or cannot be converted to a byte array.
+     */
+    public byte[] getValueAsBytes() {
+        if (mValue instanceof byte[]) {
+            return (byte[]) mValue;
         }
-        mValue = buf;
+        return null;
     }
 
     /**
-     * Sets Rational values into this tag.
-     * @exception IllegalArgumentException For the following situation:
-     * <ul>
-     *      <li>The component type of this tag is not {@link #TYPE_UNSIGNED_RATIONAL} or
-     *       {@link #TYPE_RATIONAL} .</li>
-     *      <li>The value overflows. </li>
-     *      <li>The value.length does NOT match the definition of component count in
-     *       EXIF standard.</li>
-     * </ul>
+     * Gets the value as a byte. If there are more than 1 bytes in this value,
+     * gets the first byte. This method should be used for tags of type
+     * {@link #TYPE_UNDEFINED} or {@link #TYPE_UNSIGNED_BYTE}.
+     *
+     * @param defaultValue the byte to return if tag's value does not exist or
+     *            cannot be converted to a byte.
+     * @return the tag's value as a byte, or the defaultValue.
      */
-    public void setValue(Rational[] value) {
-        if (mDataType == TYPE_UNSIGNED_RATIONAL) {
-            checkOverflowForUnsignedRational(value);
-        } else if (mDataType == TYPE_RATIONAL) {
-            checkOverflowForRational(value);
-        } else {
-            throwTypeNotMatchedException("Rational");
+    public byte getValueAsByte(byte defaultValue) {
+        byte[] b = getValueAsBytes();
+        if (b == null || b.length < 1) {
+            return defaultValue;
         }
-        checkComponentCountOrThrow(value.length);
-        mValue = value;
-        mComponentCount = value.length;
+        return b[0];
     }
 
     /**
-     * Sets Rational values into this tag.
-     * @exception IllegalArgumentException For the following situation:
-     * <ul>
-     *      <li>The component type of this tag is not {@link #TYPE_UNSIGNED_RATIONAL} or
-     *       {@link #TYPE_RATIONAL} .</li>
-     *      <li>The value overflows. </li>
-     *      <li>The component count in the definition of EXIF standard is not 1.</li>
-     * </ul>
-     * */
-    public void setValue(Rational value) {
-        setValue(new Rational[] {value});
+     * Gets the value as an array of Rationals. This method should be used for
+     * tags of type {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}.
+     *
+     * @return the value as as an array of Rationals, or null if the tag's value
+     *         does not exist or cannot be converted to an array of Rationals.
+     */
+    public Rational[] getValueAsRationals() {
+        if (mValue instanceof Rational[]) {
+            return (Rational[]) mValue;
+        }
+        return null;
     }
 
     /**
-     * Sets byte values into this tag.
-     * @exception IllegalArgumentException For the following situation:
-     * <ul>
-     *      <li>The component type of this tag is not {@link #TYPE_UNSIGNED_BYTE} or
-     *       {@link #TYPE_UNDEFINED} .</li>
-     *      <li>The length does NOT match the definition of component count in EXIF standard.</li>
-     * </ul>
-     * */
-    public void setValue(byte[] value, int offset, int length) {
-        checkComponentCountOrThrow(length);
-        if (mDataType != TYPE_UNSIGNED_BYTE && mDataType != TYPE_UNDEFINED) {
-            throwTypeNotMatchedException("byte");
+     * Gets the value as a Rational. If there are more than 1 Rationals in this
+     * value, gets the first one. This method should be used for tags of type
+     * {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}.
+     *
+     * @param defaultValue the Rational to return if tag's value does not exist
+     *            or cannot be converted to a Rational.
+     * @return the tag's value as a Rational, or the defaultValue.
+     */
+    public Rational getValueAsRational(Rational defaultValue) {
+        Rational[] r = getValueAsRationals();
+        if (r == null || r.length < 1) {
+            return defaultValue;
         }
-        mValue = new byte[length];
-        System.arraycopy(value, offset, mValue, 0, length);
-        mComponentCount = length;
+        return r[0];
     }
 
     /**
-     * Equivalent to setValue(value, 0, value.length).
+     * Gets the value as a Rational. If there are more than 1 Rationals in this
+     * value, gets the first one. This method should be used for tags of type
+     * {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}.
+     *
+     * @param defaultValue the numerator of the Rational to return if tag's
+     *            value does not exist or cannot be converted to a Rational (the
+     *            denominator will be 1).
+     * @return the tag's value as a Rational, or the defaultValue.
      */
-    public void setValue(byte[] value) {
-        setValue(value, 0, value.length);
+    public Rational getValueAsRational(long defaultValue) {
+        Rational defaultVal = new Rational(defaultValue, 1);
+        return getValueAsRational(defaultVal);
     }
 
-    private static final SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("yyyy:MM:dd kk:mm:ss");
+    /**
+     * Gets the value as an array of ints. This method should be used for tags
+     * of type {@link #TYPE_UNSIGNED_SHORT}, {@link #TYPE_UNSIGNED_LONG}.
+     *
+     * @return the value as as an array of ints, or null if the tag's value does
+     *         not exist or cannot be converted to an array of ints.
+     */
+    public int[] getValueAsInts() {
+        if (mValue == null) {
+            return null;
+        } else if (mValue instanceof long[]) {
+            long[] val = (long[]) mValue;
+            int[] arr = new int[val.length];
+            for (int i = 0; i < val.length; i++) {
+                arr[i] = (int) val[i]; // Truncates
+            }
+            return arr;
+        }
+        return null;
+    }
 
     /**
-     * Sets a timestamp to this tag. The method converts the timestamp with the format of
-     * "yyyy:MM:dd kk:mm:ss" and calls {@link #setValue(String)}.
+     * Gets the value as an int. If there are more than 1 ints in this value,
+     * gets the first one. This method should be used for tags of type
+     * {@link #TYPE_UNSIGNED_SHORT}, {@link #TYPE_UNSIGNED_LONG}.
      *
-     * @param time the number of milliseconds since Jan. 1, 1970 GMT
-     * @exception IllegalArgumentException If the data type is not {@link #TYPE_ASCII}
-     * or the component count of this tag is not 20 or undefined
+     * @param defaultValue the int to return if tag's value does not exist or
+     *            cannot be converted to an int.
+     * @return the tag's value as a int, or the defaultValue.
      */
-    public void setTimeValue(long time) {
-        // synchronized on TIME_FORMAT as SimpleDateFormat is not thread safe
-        synchronized (TIME_FORMAT) {
-            setValue(TIME_FORMAT.format(new Date(time)));
+    public int getValueAsInt(int defaultValue) {
+        int[] i = getValueAsInts();
+        if (i == null || i.length < 1) {
+            return defaultValue;
         }
+        return i[0];
     }
 
     /**
-     * Gets the {@link #TYPE_UNSIGNED_SHORT} data.
-     * @exception IllegalArgumentException If the type is NOT {@link #TYPE_UNSIGNED_SHORT}.
+     * Gets the value as an array of longs. This method should be used for tags
+     * of type {@link #TYPE_UNSIGNED_LONG}.
+     *
+     * @return the value as as an array of longs, or null if the tag's value
+     *         does not exist or cannot be converted to an array of longs.
      */
-    public int getUnsignedShort(int index) {
-        if (mDataType != TYPE_UNSIGNED_SHORT) {
-            throw new IllegalArgumentException("Cannot get UNSIGNED_SHORT value from "
-                    + convertTypeToString(mDataType));
+    public long[] getValueAsLongs() {
+        if (mValue instanceof long[]) {
+            return (long[]) mValue;
         }
-        return (int) (((long[]) mValue) [index]);
+        return null;
     }
 
     /**
-     * Gets the {@link #TYPE_LONG} data.
-     * @exception IllegalArgumentException If the type is NOT {@link #TYPE_LONG}.
+     * Gets the value or null if none exists. If there are more than 1 longs in
+     * this value, gets the first one. This method should be used for tags of
+     * type {@link #TYPE_UNSIGNED_LONG}.
+     *
+     * @param defaultValue the long to return if tag's value does not exist or
+     *            cannot be converted to a long.
+     * @return the tag's value as a long, or the defaultValue.
      */
-    public int getLong(int index) {
-        if (mDataType != TYPE_LONG) {
-            throw new IllegalArgumentException("Cannot get LONG value from "
-                    + convertTypeToString(mDataType));
+    public long getValueAsLong(long defaultValue) {
+        long[] l = getValueAsLongs();
+        if (l == null || l.length < 1) {
+            return defaultValue;
         }
-        return (int) (((long[]) mValue) [index]);
+        return l[0];
     }
 
     /**
-     * Gets the {@link #TYPE_UNSIGNED_LONG} data.
-     * @exception IllegalArgumentException If the type is NOT {@link #TYPE_UNSIGNED_LONG}.
+     * Gets the tag's value or null if none exists.
      */
-    public long getUnsignedLong(int index) {
-        if (mDataType != TYPE_UNSIGNED_LONG) {
-            throw new IllegalArgumentException("Cannot get UNSIGNED LONG value from "
-                    + convertTypeToString(mDataType));
+    public Object getValue() {
+        return mValue;
+    }
+
+    /**
+     * Gets a long representation of the value.
+     *
+     * @param defaultValue value to return if there is no value or value is a
+     *            rational with a denominator of 0.
+     * @return the tag's value as a long, or defaultValue if no representation
+     *         exists.
+     */
+    public long forceGetValueAsLong(long defaultValue) {
+        long[] l = getValueAsLongs();
+        if (l != null && l.length >= 1) {
+            return l[0];
+        }
+        byte[] b = getValueAsBytes();
+        if (b != null && b.length >= 1) {
+            return b[0];
+        }
+        Rational[] r = getValueAsRationals();
+        if (r != null && r.length >= 1 && r[0].getDenominator() != 0) {
+            return (long) r[0].toDouble();
+        }
+        return defaultValue;
+    }
+
+    /**
+     * Gets a string representation of the value.
+     */
+    public String forceGetValueAsString() {
+        if (mValue == null) {
+            return "";
+        } else if (mValue instanceof byte[]) {
+            if (mDataType == TYPE_ASCII) {
+                return new String((byte[]) mValue, US_ASCII);
+            } else {
+                return Arrays.toString((byte[]) mValue);
+            }
+        } else if (mValue instanceof long[]) {
+            if (((long[]) mValue).length == 1) {
+                return String.valueOf(((long[]) mValue)[0]);
+            } else {
+                return Arrays.toString((long[]) mValue);
+            }
+        } else if (mValue instanceof Object[]) {
+            if (((Object[]) mValue).length == 1) {
+                Object val = ((Object[]) mValue)[0];
+                if (val == null) {
+                    return "";
+                } else {
+                    return val.toString();
+                }
+            } else {
+                return Arrays.toString((Object[]) mValue);
+            }
+        } else {
+            return mValue.toString();
         }
-        return ((long[]) mValue) [index];
+    }
+
+    /**
+     * Gets the value for type {@link #TYPE_ASCII}, {@link #TYPE_LONG},
+     * {@link #TYPE_UNDEFINED}, {@link #TYPE_UNSIGNED_BYTE},
+     * {@link #TYPE_UNSIGNED_LONG}, or {@link #TYPE_UNSIGNED_SHORT}. For
+     * {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}, call
+     * {@link #getRational(int)} instead.
+     *
+     * @exception IllegalArgumentException if the data type is
+     *                {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}.
+     */
+    protected long getValueAt(int index) {
+        if (mValue instanceof long[]) {
+            return ((long[]) mValue)[index];
+        } else if (mValue instanceof byte[]) {
+            return ((byte[]) mValue)[index];
+        }
+        throw new IllegalArgumentException("Cannot get integer value from "
+                + convertTypeToString(mDataType));
     }
 
     /**
      * Gets the {@link #TYPE_ASCII} data.
-     * @exception IllegalArgumentException If the type is NOT {@link #TYPE_ASCII}.
+     *
+     * @exception IllegalArgumentException If the type is NOT
+     *                {@link #TYPE_ASCII}.
      */
-    public String getString() {
+    protected String getString() {
         if (mDataType != TYPE_ASCII) {
             throw new IllegalArgumentException("Cannot get ASCII value from "
                     + convertTypeToString(mDataType));
@@ -1354,27 +812,28 @@ public class ExifTag {
     /*
      * Get the converted ascii byte. Used by ExifOutputStream.
      */
-    byte[] getStringByte() {
+    protected byte[] getStringByte() {
         return (byte[]) mValue;
     }
 
     /**
      * Gets the {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL} data.
-     * @exception IllegalArgumentException If the type is NOT {@link #TYPE_RATIONAL} or
-     * {@link #TYPE_UNSIGNED_RATIONAL}.
+     *
+     * @exception IllegalArgumentException If the type is NOT
+     *                {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}.
      */
-    public Rational getRational(int index) {
+    protected Rational getRational(int index) {
         if ((mDataType != TYPE_RATIONAL) && (mDataType != TYPE_UNSIGNED_RATIONAL)) {
             throw new IllegalArgumentException("Cannot get RATIONAL value from "
                     + convertTypeToString(mDataType));
         }
-        return ((Rational[]) mValue) [index];
+        return ((Rational[]) mValue)[index];
     }
 
     /**
      * Equivalent to getBytes(buffer, 0, buffer.length).
      */
-    public void getBytes(byte[] buf) {
+    protected void getBytes(byte[] buf) {
         getBytes(buf, 0, buf.length);
     }
 
@@ -1383,142 +842,151 @@ public class ExifTag {
      *
      * @param buf the byte array in which to store the bytes read.
      * @param offset the initial position in buffer to store the bytes.
-     * @param length the maximum number of bytes to store in buffer. If length > component count,
-     * only the valid bytes will be stored.
-     *
-     * @exception IllegalArgumentException If the type is NOT {@link #TYPE_UNDEFINED} or
-     * {@link #TYPE_UNSIGNED_BYTE}.
+     * @param length the maximum number of bytes to store in buffer. If length >
+     *            component count, only the valid bytes will be stored.
+     * @exception IllegalArgumentException If the type is NOT
+     *                {@link #TYPE_UNDEFINED} or {@link #TYPE_UNSIGNED_BYTE}.
      */
-    public void getBytes(byte[] buf, int offset, int length) {
+    protected void getBytes(byte[] buf, int offset, int length) {
         if ((mDataType != TYPE_UNDEFINED) && (mDataType != TYPE_UNSIGNED_BYTE)) {
             throw new IllegalArgumentException("Cannot get BYTE value from "
                     + convertTypeToString(mDataType));
         }
         System.arraycopy(mValue, 0, buf, offset,
-                (length > mComponentCount) ? mComponentCount : length);
+                (length > mComponentCountActual) ? mComponentCountActual : length);
     }
 
-    private String undefinedTypeValueToString() {
-        StringBuilder sbuilder = new StringBuilder();
-        byte buf[] = (byte[]) mValue;
-        switch (mTagId) {
-            case TAG_COMPONENTS_CONFIGURATION:
-                for(int i = 0, n = getComponentCount(); i < n; i++) {
-                    if(i != 0) sbuilder.append(" ");
-                    sbuilder.append(buf[i]);
-                }
-                break;
-            default:
-                if (buf.length == 1) {
-                    sbuilder.append(buf[0]);
-                } else {
-                    for (int i = 0, n = buf.length; i < n; i++) {
-                        byte code = buf[i];
-                        if (code == 0) continue;
-                        if (code > 31 && code < 127) {
-                            sbuilder.append((char) code);
-                        } else {
-                            sbuilder.append('.');
-                        }
-                    }
-                }
-        }
-        return sbuilder.toString();
+    /**
+     * Gets the offset of this tag. This is only valid if this data size > 4 and
+     * contains an offset to the location of the actual value.
+     */
+    protected int getOffset() {
+        return mOffset;
     }
 
     /**
-     * Returns a string representation of the value of this tag.
+     * Sets the offset of this tag.
      */
-    String valueToString() {
-        StringBuilder sbuilder = new StringBuilder();
-        switch (getDataType()) {
-            case ExifTag.TYPE_UNDEFINED:
-                sbuilder.append(undefinedTypeValueToString());
-                break;
-            case ExifTag.TYPE_UNSIGNED_BYTE:
-                byte buf[] = (byte[]) mValue;
-                for(int i = 0, n = getComponentCount(); i < n; i++) {
-                    if(i != 0) sbuilder.append(" ");
-                    sbuilder.append(buf[i]);
-                }
-                break;
-            case ExifTag.TYPE_ASCII:
-                buf = (byte[]) mValue;
-                for (int i = 0, n = buf.length; i < n; i++) {
-                    byte code = buf[i];
-                    if (code == 0) break;
-                    if (code > 31 && code < 127) {
-                        sbuilder.append((char) code);
-                    } else {
-                        sbuilder.append('.');
-                    }
-                }
-                break;
-            case ExifTag.TYPE_UNSIGNED_LONG:
-                for(int i = 0, n = getComponentCount(); i < n; i++) {
-                    if(i != 0) sbuilder.append(" ");
-                    sbuilder.append(getUnsignedLong(i));
-                }
-                break;
-            case ExifTag.TYPE_RATIONAL:
-            case ExifTag.TYPE_UNSIGNED_RATIONAL:
-                for(int i = 0, n = getComponentCount(); i < n; i++) {
-                    Rational r = getRational(i);
-                    if(i != 0) sbuilder.append(" ");
-                    sbuilder.append(r.getNominator()).append("/").append(r.getDenominator());
-                }
-                break;
-            case ExifTag.TYPE_UNSIGNED_SHORT:
-                for(int i = 0, n = getComponentCount(); i < n; i++) {
-                    if(i != 0) sbuilder.append(" ");
-                    sbuilder.append(getUnsignedShort(i));
-                }
-                break;
-            case ExifTag.TYPE_LONG:
-                for(int i = 0, n = getComponentCount(); i < n; i++) {
-                    if(i != 0) sbuilder.append(" ");
-                    sbuilder.append(getLong(i));
-                }
-                break;
+    protected void setOffset(int offset) {
+        mOffset = offset;
+    }
+
+    protected void setHasDefinedCount(boolean d) {
+        mHasDefinedDefaultComponentCount = d;
+    }
+
+    protected boolean hasDefinedCount() {
+        return mHasDefinedDefaultComponentCount;
+    }
+
+    private boolean checkBadComponentCount(int count) {
+        if (mHasDefinedDefaultComponentCount && (mComponentCountActual != count)) {
+            return true;
         }
-        return sbuilder.toString();
+        return false;
     }
 
-    /**
-     * Returns true if the ID is one of the following: {@link #TAG_EXIF_IFD},
-     * {@link #TAG_GPS_IFD}, {@link #TAG_JPEG_INTERCHANGE_FORMAT},
-     * {@link #TAG_STRIP_OFFSETS}, {@link #TAG_INTEROPERABILITY_IFD}
-     */
-    static boolean isOffsetTag(short tagId) {
-        return tagId == TAG_EXIF_IFD
-                || tagId == TAG_GPS_IFD
-                || tagId == TAG_JPEG_INTERCHANGE_FORMAT
-                || tagId == TAG_STRIP_OFFSETS
-                || tagId == TAG_INTEROPERABILITY_IFD;
+    private static String convertTypeToString(short type) {
+        switch (type) {
+            case TYPE_UNSIGNED_BYTE:
+                return "UNSIGNED_BYTE";
+            case TYPE_ASCII:
+                return "ASCII";
+            case TYPE_UNSIGNED_SHORT:
+                return "UNSIGNED_SHORT";
+            case TYPE_UNSIGNED_LONG:
+                return "UNSIGNED_LONG";
+            case TYPE_UNSIGNED_RATIONAL:
+                return "UNSIGNED_RATIONAL";
+            case TYPE_UNDEFINED:
+                return "UNDEFINED";
+            case TYPE_LONG:
+                return "LONG";
+            case TYPE_RATIONAL:
+                return "RATIONAL";
+            default:
+                return "";
+        }
     }
 
-    /**
-     * Returns true if the ID is one of the following: {@link #TAG_EXIF_IFD},
-     * {@link #TAG_GPS_IFD}, {@link #TAG_INTEROPERABILITY_IFD}
-     */
-    static boolean isSubIfdOffsetTag(short tagId) {
-        return tagId == TAG_EXIF_IFD
-                || tagId == TAG_GPS_IFD
-                || tagId == TAG_INTEROPERABILITY_IFD;
+    private boolean checkOverflowForUnsignedShort(int[] value) {
+        for (int v : value) {
+            if (v > UNSIGNED_SHORT_MAX || v < 0) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean checkOverflowForUnsignedLong(long[] value) {
+        for (long v : value) {
+            if (v < 0 || v > UNSIGNED_LONG_MAX) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean checkOverflowForUnsignedLong(int[] value) {
+        for (int v : value) {
+            if (v < 0) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean checkOverflowForUnsignedRational(Rational[] value) {
+        for (Rational v : value) {
+            if (v.getNumerator() < 0 || v.getDenominator() < 0
+                    || v.getNumerator() > UNSIGNED_LONG_MAX
+                    || v.getDenominator() > UNSIGNED_LONG_MAX) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean checkOverflowForRational(Rational[] value) {
+        for (Rational v : value) {
+            if (v.getNumerator() < LONG_MIN || v.getDenominator() < LONG_MIN
+                    || v.getNumerator() > LONG_MAX
+                    || v.getDenominator() > LONG_MAX) {
+                return true;
+            }
+        }
+        return false;
     }
+
     @Override
     public boolean equals(Object obj) {
+        if (obj == null) {
+            return false;
+        }
         if (obj instanceof ExifTag) {
             ExifTag tag = (ExifTag) obj;
+            if (tag.mTagId != this.mTagId
+                    || tag.mComponentCountActual != this.mComponentCountActual
+                    || tag.mDataType != this.mDataType) {
+                return false;
+            }
             if (mValue != null) {
-                if (mValue instanceof long[]) {
-                    if (!(tag.mValue instanceof long[])) return false;
+                if (tag.mValue == null) {
+                    return false;
+                } else if (mValue instanceof long[]) {
+                    if (!(tag.mValue instanceof long[])) {
+                        return false;
+                    }
                     return Arrays.equals((long[]) mValue, (long[]) tag.mValue);
                 } else if (mValue instanceof Rational[]) {
-                    if (!(tag.mValue instanceof Rational[])) return false;
+                    if (!(tag.mValue instanceof Rational[])) {
+                        return false;
+                    }
                     return Arrays.equals((Rational[]) mValue, (Rational[]) tag.mValue);
                 } else if (mValue instanceof byte[]) {
-                    if (!(tag.mValue instanceof byte[])) return false;
+                    if (!(tag.mValue instanceof byte[])) {
+                        return false;
+                    }
                     return Arrays.equals((byte[]) mValue, (byte[]) tag.mValue);
                 } else {
                     return mValue.equals(tag.mValue);
@@ -1529,4 +997,12 @@ public class ExifTag {
         }
         return false;
     }
+
+    @Override
+    public String toString() {
+        return String.format("tag id: %04X\n", mTagId) + "ifd id: " + mIfd + "\ntype: "
+                + convertTypeToString(mDataType) + "\ncount: " + mComponentCountActual
+                + "\noffset: " + mOffset + "\nvalue: " + forceGetValueAsString() + "\n";
+    }
+
 }