/* * Copyright (C) 2012 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.gallery3d.exif; 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. 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 { /** * 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. */ public static final short TYPE_ASCII = 2; /** * The SHORT type in the EXIF standard. A 16-bit (2-byte) unsigned integer */ public static final short TYPE_UNSIGNED_SHORT = 3; /** * The LONG type in the EXIF standard. A 32-bit (4-byte) unsigned integer */ 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. */ 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. */ public static final short TYPE_UNDEFINED = 7; /** * The SLONG type in the EXIF standard. A 32-bit (4-byte) signed integer * (2's complement notation). */ 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. */ 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; TYPE_TO_SIZE_MAP[TYPE_UNSIGNED_SHORT] = 2; TYPE_TO_SIZE_MAP[TYPE_UNSIGNED_LONG] = 4; TYPE_TO_SIZE_MAP[TYPE_UNSIGNED_RATIONAL] = 8; TYPE_TO_SIZE_MAP[TYPE_UNDEFINED] = 1; TYPE_TO_SIZE_MAP[TYPE_LONG] = 4; TYPE_TO_SIZE_MAP[TYPE_RATIONAL] = 8; } static final int SIZE_UNDEFINED = 0; // Exif TagId private final short mTagId; // Exif Tag Type private final short mDataType; // 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; private static final SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("yyyy:MM:dd kk:mm:ss"); /** * Returns true if the given IFD is a valid IFD. */ 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; } /** * Returns true if a given type is a valid tag 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; } // Use builtTag in ExifInterface instead of constructor. ExifTag(short tagId, short type, int componentCount, int ifd, boolean hasDefinedComponentCount) { mTagId = tagId; mDataType = type; 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]; } /** * Returns the ID of the IFD this tag belongs to. * * @see IfdId#TYPE_IFD_0 * @see IfdId#TYPE_IFD_1 * @see IfdId#TYPE_IFD_EXIF * @see IfdId#TYPE_IFD_GPS * @see IfdId#TYPE_IFD_INTEROPERABILITY */ public int getIfd() { return mIfd; } protected void setIfd(int ifdId) { mIfd = ifdId; } /** * Gets the TID of this tag. */ public short getTagId() { return mTagId; } /** * Gets the data type of this tag * * @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 short getDataType() { return mDataType; } /** * Gets the total data size in bytes of the value of this tag. */ public int getDataSize() { return getComponentCount() * getElementSize(getDataType()); } /** * Gets the component count of this tag. */ // TODO: fix integer overflows with this public int getComponentCount() { 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. */ protected void forceSetComponentCount(int count) { mComponentCountActual = count; } /** * 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; } /** * Sets integer values into this tag. This method should be used for tags of * type {@link #TYPE_UNSIGNED_SHORT}. This method will fail if: * */ 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 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: * */ public boolean setValue(int value) { return setValue(new int[] { value }); } /** * Sets long values into this tag. This method should be used for tags of * type {@link #TYPE_UNSIGNED_LONG}. This method will fail if: * */ 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; } /** * Sets long values into this tag. This method should be used for tags of * type {@link #TYPE_UNSIGNED_LONG}. This method will fail if: * */ public boolean setValue(long value) { return setValue(new long[] { value }); } /** * 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: * */ public boolean setValue(String value) { if (mDataType != TYPE_ASCII && mDataType != TYPE_UNDEFINED) { return false; } 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 }; } int count = finalBuf.length; if (checkBadComponentCount(count)) { return false; } mComponentCountActual = count; mValue = finalBuf; return true; } /** * 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: * * * @see Rational */ public boolean setValue(Rational[] value) { if (checkBadComponentCount(value.length)) { return false; } 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; } /** * 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: * * * @see Rational */ public boolean setValue(Rational value) { return setValue(new Rational[] { value }); } /** * 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: * */ public boolean setValue(byte[] value, int offset, int length) { if (checkBadComponentCount(length)) { return false; } 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; } /** * Equivalent to setValue(value, 0, value.length). */ public boolean setValue(byte[] value) { return setValue(value, 0, value.length); } /** * 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: * */ public boolean setValue(byte value) { return setValue(new byte[] { value }); } /** * Sets the value for this tag using an appropriate setValue method for the * given object. This method will fail if: * */ 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; } } /** * 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 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))); } } /** * Gets the value as a String. This method should be used for tags of type * {@link #TYPE_ASCII}. * * @return the value as a String, or null if the tag's value does not exist * or cannot be converted to a 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; } /** * 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; } /** * 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; } return null; } /** * 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 byte getValueAsByte(byte defaultValue) { byte[] b = getValueAsBytes(); if (b == null || b.length < 1) { return defaultValue; } return b[0]; } /** * 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; } /** * 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; } return r[0]; } /** * 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 Rational getValueAsRational(long defaultValue) { Rational defaultVal = new Rational(defaultValue, 1); return getValueAsRational(defaultVal); } /** * 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; } /** * 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 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 int getValueAsInt(int defaultValue) { int[] i = getValueAsInts(); if (i == null || i.length < 1) { return defaultValue; } return i[0]; } /** * 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 long[] getValueAsLongs() { if (mValue instanceof long[]) { return (long[]) mValue; } return null; } /** * 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 long getValueAsLong(long defaultValue) { long[] l = getValueAsLongs(); if (l == null || l.length < 1) { return defaultValue; } return l[0]; } /** * Gets the tag's value or null if none exists. */ 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(); } } /** * 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}. */ protected String getString() { if (mDataType != TYPE_ASCII) { throw new IllegalArgumentException("Cannot get ASCII value from " + convertTypeToString(mDataType)); } return new String((byte[]) mValue, US_ASCII); } /* * Get the converted ascii byte. Used by ExifOutputStream. */ 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}. */ 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]; } /** * Equivalent to getBytes(buffer, 0, buffer.length). */ protected void getBytes(byte[] buf) { getBytes(buf, 0, buf.length); } /** * Gets the {@link #TYPE_UNDEFINED} or {@link #TYPE_UNSIGNED_BYTE} data. * * @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}. */ 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 > mComponentCountActual) ? mComponentCountActual : length); } /** * 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; } /** * Sets the offset of this tag. */ 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 false; } 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 ""; } } 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 (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; } return Arrays.equals((Rational[]) mValue, (Rational[]) tag.mValue); } else if (mValue instanceof byte[]) { if (!(tag.mValue instanceof byte[])) { return false; } return Arrays.equals((byte[]) mValue, (byte[]) tag.mValue); } else { return mValue.equals(tag.mValue); } } else { return tag.mValue == null; } } 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"; } }