content: package com.almalence.plugins.export.ExifDriver;
  
  import java.io.EOFException;
  import java.io.FileInputStream;
  import java.io.FileNotFoundException;
  import java.io.FileOutputStream;
  import java.io.IOException;
  import java.nio.channels.FileChannel;
  import java.util.Arrays;
  import java.util.HashMap;
  import java.util.logging.Level;
  import java.util.logging.Logger;
  
  import android.util.Log;
  
  import com.almalence.plugins.export.ExifDriver.Values.ExifValue;
  import com.almalence.plugins.export.ExifDriver.Values.UndefinedValueAccessException;
  import com.almalence.plugins.export.ExifDriver.Values.ValueByteArray;
  import com.almalence.plugins.export.ExifDriver.Values.ValueNumber;
  import com.almalence.plugins.export.ExifDriver.Values.ValueRationals;
  
  /**
   * Driver for reading/writting EXIF meta data to JPEG images. It tries to
   * conform Exif Version 2.2. For more info see http://exif.org/ JPEG data from
   * the (very brief) point of view of the driver looks like this: 2B SOI - Start
   * Of Image FF D8 2B APP1 marker FF E1 2B APP1 size (includes itself) 6B EXIF
   * header 45 78 69 66 00 00 6B TIFF header 49 49 2A 00 08 00 00 00 (Intel
   * endian) or 4D 4D 00 2A 00 00 00 08 (Motorola endian) Following EXIF data are
   * encoded with endian declared above
   * 
   * IFD0 - main IFD directory. It contains tags, which holds offsets of IFDExif
   * and IFDGPS subdirectories. It's last 4B holds offset of IFD1 IFD0-related
   * data
   * 
   * IFDExif - Exif subdirectory IFDExif-related data
   * 
   * IFDInteroperability - Interoperability subdirectory
   * IFDInteroperability-related data
   * 
   * IFDGPS - GPS subdirectory IFDGPS-related data
   * 
   * IFD1 - Thumbnail information directory - it's offset is in the last 4B of
   * IFD0 IFD1-related data * * End of area this driver modifies 2B SOI - Start Of
   * Image (Thumbnail actually) FF D8 Thumbnail data 2B EOI - End Of Image FF D9 *
   * * End of area this driver works with ... "Not-interesting" stuff follows
   * 
   * IFD (sub)directory structure is always the following: 2B count of entries n
   * of entries - 12B per entry (2B tag, 2B format, 4B number of values, 4B values
   * or offset to them). 4B "Next" IFD - is used only in IFD0 for reference to
   * IFD1 in other IFD's 0.
   * 
   * @author kocian
   */
  public class ExifDriver
  {
  	// Private constants
  
  	private final String				LOGTAG								= getClass().getName();
  	// Datatypes
  	public static final int				FORMAT_UNSIGNED_BYTE				= 0x01;
  	public static final int				FORMAT_ASCII_STRINGS				= 0x02;
  	public static final int				FORMAT_UNSIGNED_SHORT				= 0x03;
  	public static final int				FORMAT_UNSIGNED_LONG				= 0x04;
  	public static final int				FORMAT_UNSIGNED_RATIONAL			= 0x05;
  	public static final int				FORMAT_SIGNED_BYTE					= 0x06;
  	public static final int				FORMAT_UNDEFINED					= 0x07;
  	public static final int				FORMAT_SIGNED_SHORT					= 0x08;
  	public static final int				FORMAT_SIGNED_LONG					= 0x09;
  	public static final int				FORMAT_SIGNED_RATIONAL				= 0x0a;
  	// Convenience 'hash' for finding component bit width (see above formats)
  	public static final int[]			COMP_WIDTHS							= new int[] { 0, 1, 1, 2, 4, 8, 1, 1, 2, 4,
  			8																};
  	// Pointer values
  	private final int					TAG_EXIF_POINTER					= 0x8769;
  	private final int					TAG_GPS_POINTER						= 0x8825;
  	private final int					TAG_INTEROPERABILITY_POINTER		= 0xa005;
  	// Public constants
  	// IFD0-related tags, some of them are presented in IFD1 too
  	public static final int				TAG_IMAGE_WIDTH						= 0x0100;
  	public static final int				TAG_IMAGE_HEIGHT					= 0x0101;
  	public static final int				TAG_BITS_PER_SAMPLE					= 0x0102;
  	public static final int				TAG_COMPRESSION						= 0x0103;
  	public static final int				TAG_PHOTOMETRIC_INTERPRETATION		= 0x0106;
  	public static final int				TAG_ORIENTATION						= 0x0112;
  	public static final int				TAG_SAMPLES_PER_PIXEL				= 0x0115;
  	public static final int				TAG_PLANAR_CONFIGURATION			= 0x011c;
  	public static final int				TAG_YCBCR_SUBSAMPLING				= 0x0212;
  	public static final int				TAG_YCBCRPOSITIONING				= 0x0213;
  	public static final int				TAG_XRESOLUTION						= 0x011a;
  	public static final int				TAG_YRESOLUTION						= 0x011b;
  	public static final int				TAG_RESOLUTION_UNIT					= 0x0128;
  	public static final int				TAG_STRIP_OFFSETS					= 0x0111;
  	public static final int				TAG_ROWS_PER_STRIP					= 0x0116;
  	public static final int				TAG_STRIP_BYTECOUNTS				= 0x0117;
  	public static final int				TAG_JPEG_INTERCHANGE_FORMAT			= 0x0201;
  	public static final int				TAG_JPEG_INTERCHANGE_FORMAT_LENGTH	= 0x0202;
  	public static final int				TAG_TRANSFER_FUNCTION				= 0x012d;
  	public static final int				TAG_WHITE_POINT						= 0x013e;
  	public static final int				TAG_PRIMARY_CHROMATICITIES			= 0x013f;
  	public static final int				TAG_YCBCR_COEFICIENTS				= 0x0211;
  	public static final int				TAG_REFERENCE_BLACK_WHITE			= 0x0214;
  	public static final int				TAG_DATETIME						= 0x0132;
  	public static final int				TAG_IMAGE_DESCRIPTION				= 0x010e;
  	public static final int				TAG_MAKE							= 0x010f;
  	public static final int				TAG_MODEL							= 0x0110;
  	public static final int				TAG_SOFTWARE						= 0x0131;
  	public static final int				TAG_ARTIST							= 0x013b;
  	public static final int				TAG_COPYRIGHT						= 0x8298;
  	// IFD Exif tags
  	public static final int				TAG_EXIF_VERSION					= 0x9000;
  	public static final int				TAG_FLASHPIX_VERSION				= 0xa000;
  	public static final int				TAG_COLOR_SPACE						= 0xa001;
  	public static final int				TAG_COMPONENT_CONFIGURATION			= 0x9101;
  	public static final int				TAG_COMPRESSED_BITS_PER_PIXEL		= 0x9102;
  	public static final int				TAG_PIXEL_X_DIMENSION				= 0xa002;
  	public static final int				TAG_PIXEL_Y_DIMENSION				= 0xa003;
  	public static final int				TAG_MARKER_NOTE						= 0x927c;
  	public static final int				TAG_USER_COMMENT					= 0x9286;
  	public static final int				TAG_RELATED_SOUND_FILE				= 0xa004;
  	public static final int				TAG_DATETIME_ORIGINAL				= 0x9003;
  	public static final int				TAG_DATETIME_DIGITIZED				= 0x9004;
  	public static final int				TAG_SUB_SEC_TIME					= 0x9290;
  	public static final int				TAG_SUB_SEC_TIME_ORIGINAL			= 0x9291;
  	public static final int				TAG_SUB_SEC_TIME_DIGITIZED			= 0x9292;
  	public static final int				TAG_IMAGE_UNIQUE_ID					= 0xa420;
  	public static final int				TAG_EXPOSURE_TIME					= 0x829a;
  	public static final int				TAG_FNUMBER							= 0x829d;
  	public static final int				TAG_EXPOSURE_PROGRAM				= 0x8822;
  	public static final int				TAG_SPECTRAL_SENSITIVITY			= 0x8824;
  	public static final int				TAG_ISO_SPEED_RATINGS				= 0x8827;
  	public static final int				TAG_OECF							= 0x8828;
  	public static final int				TAG_SHUTTER_SPEED_VALUE				= 0x9201;
  	public static final int				TAG_APERTURE_VALUE					= 0x9202;
  	public static final int				TAG_BRIGHTNESS_VALUE				= 0x9203;
  	public static final int				TAG_EXPOSURE_BIAS_VALUE				= 0x9204;
  	public static final int				TAG_MAX_APERTURE_VALUE				= 0x9205;
  	public static final int				TAG_SUBJECT_DISTANCE				= 0x9206;
  	public static final int				TAG_METERING_MODE					= 0x9207;
  	public static final int				TAG_LIGHT_SOURCE					= 0x9208;
  	public static final int				TAG_FLASH							= 0x9209;
  	public static final int				TAG_FOCAL_LENGTH					= 0x920a;
  	public static final int				TAG_SUBJECT_AREA					= 0x9214;
  	public static final int				TAG_FLASH_ENERGY					= 0xa20b;
  	public static final int				TAG_SPATIAL_FREQUENCY_RESPONSE		= 0xa20c;
  	public static final int				TAG_FOCAL_PLANE_X_RESOLUTION		= 0xa20e;
  	public static final int				TAG_FOCAL_PLANE_Y_RESOLUTION		= 0xa20f;
  	public static final int				TAG_FOCAL_PLANE_RESOLUTION_UNIT		= 0xa210;
  	public static final int				TAG_SUBJECT_LOCATION				= 0xA214;
  	public static final int				TAG_EXPOSURE_INDEX					= 0xA215;
  	public static final int				TAG_SENSING_METHOD					= 0xA217;
  	public static final int				TAG_FILE_SOURCE						= 0xA300;
  	public static final int				TAG_SCENE_TYPE						= 0xA301;
  	public static final int				TAG_CFA_PATTERN						= 0xA302;
  	public static final int				TAG_CUSTOM_RENDERED					= 0xA401;
  	public static final int				TAG_EXPOSURE_MODE					= 0xA402;
  	public static final int				TAG_WHITE_BALANCE					= 0xA403;
  	public static final int				TAG_DIGITAL_ZOOM_RATIO				= 0xA404;
  	public static final int				TAG_FOCAL_LENGTH_35MM_FILM			= 0xA405;
  	public static final int				TAG_SCENE_CAPTURE_TYPE				= 0xA406;
  	public static final int				TAG_GAIN_CONTROL					= 0xA407;
  	public static final int				TAG_CONTRAST						= 0xA408;
  	public static final int				TAG_SATURATION						= 0xA409;
  	public static final int				TAG_SHARPNESS						= 0xA40A;
  	public static final int				TAG_DEVICE_SETTING_DESCRIPTION		= 0xA40B;
  	public static final int				TAG_SUBJECT_DISTANCE_RANGE			= 0xA40C;
  	// IFD GPS tags
  	public static final int				TAG_GPS_VERSION_ID					= 0x0;
  	public static final int				TAG_GPS_LATITUDE_REF				= 0x1;
  	public static final int				TAG_GPS_LATITUDE					= 0x2;
  	public static final int				TAG_GPS_LONGITUDE_REF				= 0x3;
  	public static final int				TAG_GPS_LONGITUDE					= 0x4;
  	public static final int				TAG_GPS_ALTITUDE_REF				= 0x5;
  	public static final int				TAG_GPS_ALTITUDE					= 0x6;
  	public static final int				TAG_GPS_TIME_STAMP					= 0x7;
  	public static final int				TAG_GPS_SATELITES					= 0x8;
  	public static final int				TAG_GPS_STATUS						= 0x9;
  	public static final int				TAG_GPS_MEASURE_MODE				= 0xa;
  	public static final int				TAG_GPS_DOP							= 0xb;
  	public static final int				TAG_GPS_SPEED_REF					= 0xc;
  	public static final int				TAG_GPS_SPEED						= 0xd;
  	public static final int				TAG_GPS_TRACK_REF					= 0xe;
  	public static final int				TAG_GPS_TRACK						= 0xf;
  	public static final int				TAG_GPS_SLMG_DIRECTION_REF			= 0x10;
  	public static final int				TAG_GPS_SLMG_DIRECTION				= 0x11;
  	public static final int				TAG_GPS_MAP_DATUM					= 0x12;
  	public static final int				TAG_GPS_DEST_LATITUDE_REF			= 0x13;
  	public static final int				TAG_GPS_DEST_LATITUDE				= 0x14;
  	public static final int				TAG_GPS_DEST_LONGITUDE_REF			= 0x15;
  	public static final int				TAG_GPS_DEST_LONGITUDE				= 0x16;
  	public static final int				TAG_GPS_DEST_BEARING_REF			= 0x17;
  	public static final int				TAG_GPS_DEST_BEARING				= 0x18;
  	public static final int				TAG_GPS_DEST_DISTANCE_REF			= 0x19;
  	public static final int				TAG_GPS_DEST_DISTANCE				= 0x1a;
  	public static final int				TAG_GPS_PROCESSING_METHOD			= 0x1b;
  	public static final int				TAG_GPS_AREA_INFORMATION			= 0x1c;
  	public static final int				TAG_GPS_DATE_STAMP					= 0x1d;
  	public static final int				TAG_GPS_DIFFERENTIAL				= 0x1e;
  	// IFD Interoperability tags
  	public static final int				TAG_INTEROPERABILITY_1				= 0x1;
  	public static final int				TAG_INTEROPERABILITY_2				= 0x2;
  	// Length of Exif data size declaration - 2B
  	private final int					LENGTH_EXIF_SIZE_DECL				= 2;
  	private final int					LENGTH_APP1_EXIF_HEADER				= 10;										// APP1Marker+EXIF
  																														// size
  																														// +
  																														// EXIF
  																														// header
  	private String						sourceFile;
  	private byte[]						origEXIFdata;
  	// JPEG's Start of image
  	private final byte[]				SOI									= new byte[] { (byte) 0xFF, (byte) 0xD8 };
  	private final byte[]				APP1Marker							= new byte[] { (byte) 0xFF, (byte) 0xE1 };
  	private final byte[]				EXIFHeader							= new byte[] { 'E', 'x', 'i', 'f', '\0',
  			'\0'															};
  	// Note it's a "Intel fashion one" we use it's values only during saving
  	private final byte[]				TIFFHeader							= new byte[] { 'I', 'I', (byte) 0x2A, '\0',
  			(byte) 0x08, '\0', '\0', '\0'									};
  	// Specification requires this tag to have this value
  	private int							origAPP1MarkerOffset				= 2;
  	private int							origThumbnailOffset					= -1;
  	private int							origThumbnailLength					= 0;
  	public static final int				ALIGN_II							= 0x4949;									// Intel
  																														// endian
  	public static final int				ALIGN_MM							= 0x4D4D;									// Motorola
  																														// endian
  	private int							originalAlign;																	// endian
  	// IFD directories are represented as simple tag-value hashes
  	private HashMap<Integer, ExifValue>	ifd0								= new HashMap<Integer, ExifValue>();
  	private HashMap<Integer, ExifValue>	ifdExif								= new HashMap<Integer, ExifValue>();
  	private HashMap<Integer, ExifValue>	ifdGps								= new HashMap<Integer, ExifValue>();
  	private HashMap<Integer, ExifValue>	ifd1								= new HashMap<Integer, ExifValue>();
  	private HashMap<Integer, ExifValue>	ifdIOper							= new HashMap<Integer, ExifValue>();
  	private boolean						readyToWork							= false;
  	private boolean						debug								= true;
  
  	/**
  	 * Get instance of driver for given image file. If everything works well
  	 * (file can be read, it is Exif file .. etc.), this method reurns the
  	 * driver object. In opposite case it reurns null.
  	 * 
  	 * @param _file
  	 *            image file
  	 * @return ExifDriver or null in case, that anyhing went wrong
  	 */
  	public static ExifDriver getInstance(String _file)
  	{
  		ExifDriver result = new ExifDriver(_file);
  		if (result.readyToWork())
  		{
  			return result;
  		} else
  		{
  			return null;
  		}
  	}
  
  	public String getSourceFile()
  	{
  		return sourceFile;
  	}
  
  	/**
  	 * Constructor. Do the basics like find Exif data and prepare them to array.
  	 * Then call parser to read information from the array. Variable readyToWork
  	 * is set during consturction. Constructor is private and can be called only
  	 * through getInstance method, which desides, if user can obtain he driver
  	 * object or null.
  	 * 
  	 * @param _file
  	 *            Path of file to work with
  	 */
  	private ExifDriver(String _file)
  	{
  		sourceFile = _file;
  		readyToWork = true; // Hope for the best;
  		byte[] findBuffer = new byte[100];
  		FileInputStream fis = null;
  		FileChannel channel = null;
  		origAPP1MarkerOffset = -1;
  		int exifDataSize = -1;
  		int read;
  		try
  		{
  			fis = new FileInputStream(sourceFile);
  			channel = fis.getChannel();
  			read = fis.read(findBuffer);
  			// Make sure, that image is JPG
  			if (findBuffer[0] == SOI[0] && findBuffer[1] == SOI[1])
  			{
  				// Make sure, that image is the Exif one. Find APP1 marker and
  				// also remember it's offset from start of file
  				int findOffset = 0;
  				while (origAPP1MarkerOffset < SOI.length && read > LENGTH_APP1_EXIF_HEADER)
  				{
  					for (int i = 0; i - 1 < read - LENGTH_APP1_EXIF_HEADER; i++)
  					{
  						if (findBuffer[i] == APP1Marker[0] && findBuffer[i + 1] == APP1Marker[1]
  								&& findBuffer[i + 4] == EXIFHeader[0] && findBuffer[i + 5] == EXIFHeader[1]
  								&& findBuffer[i + 6] == EXIFHeader[2] && findBuffer[i + 7] == EXIFHeader[3]
  								&& findBuffer[i + 8] == EXIFHeader[4] && findBuffer[i + 9] == EXIFHeader[5])
  						{
  							origAPP1MarkerOffset = i + findOffset; // APP1marker
  																	// and EXIF
  																	// header
  																	// found
  							exifDataSize = (findBuffer[i + 2] & 0xFF) << 8;
  							exifDataSize += findBuffer[i + 3] & 0xFF;
  							break;
  						}
  					}
  					// shift the finding window
  					findOffset += read - LENGTH_APP1_EXIF_HEADER;
  					channel.position(findOffset);
  					read = fis.read(findBuffer);
  				}
  				if (origAPP1MarkerOffset >= SOI.length)
  				{
  					origEXIFdata = new byte[exifDataSize - (LENGTH_EXIF_SIZE_DECL + EXIFHeader.length)];
  					System.out.println("APP1 data size: " + Long.toHexString(exifDataSize));
  					// data will start with TIFF header
  					channel.position(origAPP1MarkerOffset + LENGTH_APP1_EXIF_HEADER);
  					fis.read(origEXIFdata);
  					readExifData(origEXIFdata);
  				} else
  				{
  					readyToWork = false;
  				}
  			} else
  			{
  				readyToWork = false;
  			}
  			channel.close();
  			fis.close();
  		} catch (EOFException e)
  		{
  			e.printStackTrace();
  			readyToWork = false;
  		} catch (Exception e)
  		{
  			e.printStackTrace();
  			readyToWork = false;
  		} finally
  		{
  			try
  			{
  				if (channel != null)
  				{
  					channel.close();
  				}
  				if (fis != null)
  				{
  					fis.close();
  				}
  			} catch (IOException ex)
  			{
  				Logger.getLogger(ExifDriver.class.getName()).log(Level.SEVERE, null, ex);
  			}
  		}
  	}
  
  	/**
  	 * Tells the caller if the driver has been initialized corectly and we can
  	 * work with it. It is used by getInstance method. In case, that readyToWork
  	 * returns false, getInstance returns null
  	 * 
  	 * @return true if driver has been initialized correctly
  	 */
  	private boolean readyToWork()
  	{
  		return readyToWork;
  	}
  
  	/**
  	 * Method reads sequentially the given source data and fills the structures
  	 * with information.
  	 * 
  	 * @param _data
  	 * @throws UndefinedValueAccessException
  	 * @throws Exception
  	 */
  	private void readExifData(byte[] _data) throws UndefinedValueAccessException
  	{
  		originalAlign = (_data[0] << 8) + (_data[1] & 0xFF);
  		int ifdStart = readUInt(_data, 4, 4, originalAlign); // See the TIFF
  																// header
  		ifdStart = readIfd(ifd0, _data, ifdStart);
  		// Was there any IFD1 reference?
  		// Check pointer value is less then data.length, because sometimes
  		// it happens to be more. And it's cause exception.
  		// Actually problem is caused by bugs in android's ExifEnterface.
  		if (ifdStart > 0 && ifdStart < _data.length)
  		{
  			readIfd(ifd1, _data, ifdStart);
  			// Remember thumbnail info
  			if (ifd1.containsKey(TAG_JPEG_INTERCHANGE_FORMAT))
  			{
  				origThumbnailOffset = ifd1.get(TAG_JPEG_INTERCHANGE_FORMAT).getIntegers()[0];
  				origThumbnailLength = ifd1.get(TAG_JPEG_INTERCHANGE_FORMAT_LENGTH).getIntegers()[0];
  			}
  		}
  		// Is there a IFDExif reference?
  		if (ifd0.get(TAG_EXIF_POINTER) != null)
  		{
  			ifdStart = ifd0.get(TAG_EXIF_POINTER).getIntegers()[0];
  			readIfd(ifdExif, _data, ifdStart);
  			if (ifdExif.get(TAG_INTEROPERABILITY_POINTER) != null)
  			{
  				ifdStart = ifdExif.get(TAG_INTEROPERABILITY_POINTER).getIntegers()[0];
  				readIfd(ifdIOper, _data, ifdStart);
  			}
  		}
  		// Is there a IFDGPS reference?
  		if (ifd0.get(TAG_GPS_POINTER) != null)
  		{
  			ifdStart = ifd0.get(TAG_GPS_POINTER).getIntegers()[0];
  			readIfd(ifdGps, _data, ifdStart);
  		}
  	}
  
  	/**
  	 * Read information from one IFD directory
  	 * 
  	 * @param _ifd
  	 *            Structure to store data in
  	 * @param _data
  	 *            Source data byte array
  	 * @param _start
  	 *            Offset, where the ifd directory starts
  	 * @return Long value from the last 4 bytes of the directory, which, if
  	 *         non-zero, refers to next ifd. As a matter of fact, the non-zero
  	 *         is expected only in IFD0.
  	 * @throws Exception
  	 */
  	private int readIfd(HashMap<Integer, ExifValue> _ifd, byte[] _data, int _start)
  	{
  		int entriesNumber = readUInt(_data, _start, 2, originalAlign);
  		if (debug)
  		{
  			System.out.println(entriesNumber + " entries found in ifd begining at "
  					+ Integer.toHexString(origAPP1MarkerOffset + LENGTH_APP1_EXIF_HEADER + _start));
  		}
  		for (int i = 0; i < entriesNumber; i++)
  		{
  			int entryStart = _start + 2 + i * 12;// 2B is size, 12B is the
  			// length of each item
  			// Parse item structure (2B-tag, 2B-datatype, 4B number of
  			// components, 4B value(or offset to value))
  			int tag = readUInt(_data, entryStart, 2, originalAlign);
  			int datatype = readUInt(_data, entryStart + 2, 2, originalAlign);
  			int components = readUInt(_data, entryStart + 4, 4, originalAlign);
  			// If the totalLength is >4 it does not fit in directory
  			int totalLength = 0;
  			Object dType = COMP_WIDTHS[0];
  			if(datatype > COMP_WIDTHS.length)
  			{
  				Log.e("EXIF_TAG", "Error in exifDriver. Tag " + tag + ", datatype = " + datatype);
  				if(tag == 274)
  					dType = COMP_WIDTHS[3];
  			}
  			else
  				dType = COMP_WIDTHS[datatype];
  			
  			if (dType != null)
  			{
  				totalLength = components * (Integer) dType;
  			} else
  			{
  				_ifd.clear();
  				return 0;
  			}
  			// Offset right in directory
  			int offset = entryStart + 8;
  			if (totalLength > 4)
  			{
  				// Offset in data area
  				offset = readUInt(_data, offset, 4, originalAlign);
  			}
  			if (debug)
  			{
  				if (tag == TAG_INTEROPERABILITY_POINTER || tag == TAG_EXIF_POINTER || tag == TAG_GPS_POINTER)
  				{
  					System.out.print("Pointer: " + tag);
  					System.out.print(" Datatype: " + Integer.toHexString(datatype));
  					System.out.print(" Components: " + Integer.toHexString(components));
  					System.out.println(" Offset: "
  							+ Integer.toHexString(origAPP1MarkerOffset + LENGTH_APP1_EXIF_HEADER + offset));
  				}
  			}
  			ExifValue value = null;
  			switch (datatype)
  			{
  			case FORMAT_UNSIGNED_BYTE:
  			case FORMAT_UNSIGNED_SHORT:
  			case FORMAT_UNSIGNED_LONG:
  			case FORMAT_SIGNED_BYTE:
  			case FORMAT_SIGNED_SHORT:
  			case FORMAT_SIGNED_LONG:
  				value = new ValueNumber(datatype);
  				break;
  			case FORMAT_ASCII_STRINGS:
  			case FORMAT_UNDEFINED:
  				value = new ValueByteArray(datatype);
  				break;
  			case FORMAT_UNSIGNED_RATIONAL:
  			case FORMAT_SIGNED_RATIONAL:
  				value = new ValueRationals(datatype);
  				break;
  			default:
  				break;
  			}
  			if (value != null)
  			{
  				value.readValueFromData(_data, offset, components, originalAlign);
  				_ifd.put(tag, value);
  			}
  		}
  		
  		// Return the long value represented by 4B after the last entry
  		return readUInt(_data, _start + entriesNumber * 12 + 2, 4, originalAlign);
  	}
  
  	/**
  	 * Read signed int from source byte array. Handles endianes.
  	 * 
  	 * @param _data
  	 *            Data array to read from
  	 * @param _offset
  	 *            offset, where the value starts
  	 * @param _bytesNumber
  	 *            Number of bytes (1,2,4 for byte, short, long)
  	 * @param _align
  	 *            - Endian
  	 * @return Integer value
  	 */
  	public static Integer readSInt(byte[] _data, int _offset, int _bytesNumber, int _align)
  	{
  		int signMask = 1 << (_bytesNumber * 8 - 1);
  		int valueMask = 0xFFFFFFFF >>> (4 - _bytesNumber) * 8 + 1;
  		int value = readUInt(_data, _offset, _bytesNumber, _align) & valueMask;
  		if ((value & signMask) > 0)
  		{
  			value = -value;
  		}
  		return value;
  	}
  
  	/**
  	 * Read unsigned int from source byte array. Handles endianes.
  	 * 
  	 * @param _data
  	 *            Data array to read from
  	 * @param _offset
  	 *            offset, where the value starts
  	 * @param _bytesNumber
  	 *            Number of bytes (1,2,4 for byte, short, long)
  	 * @param _align
  	 *            - Endian
  	 * @return Integer value
  	 */
  	public static Integer readUInt(byte[] _data, int _offset, int _bytesNumber, int _align)
  	{
  		int result = 0;
  		int shift = 0;
  		switch (_align)
  		{
  		case ALIGN_MM:
  			shift = _bytesNumber * 8;
  			break;
  		case ALIGN_II:
  			shift = 0;
  			break;
  		default:
  			break;
  		}
  		for (int i = _offset; i < _bytesNumber + _offset; i++)
  		{
  			switch (_align)
  			{
  			case ALIGN_MM:
  				shift -= 8;
  				result += (_data[i] & 0xFF) << shift;
  				break;
  			case ALIGN_II:
  				result += (_data[i] & 0xFF) << shift;
  				shift += 8;
  				break;
  			default:
  				break;
  			}
  		}
  		return result;
  	}
  
  	/**
  	 * The total space, that the given IFD requires. It is space required by
  	 * directory itself and it's related data too.
  	 * 
  	 * @param _ifd
  	 *            Given IFD
  	 * @return Required space in bytes
  	 */
  	private int requiredSpace(HashMap<Integer, ExifValue> _ifd)
  	{
  		int result = 6;// 2B number of items, 4B the "next" address
  		Object[] oKeys = _ifd.keySet().toArray();
  		for (int i = 0; i < oKeys.length; i++)
  		{
  			result += 12;
  			ExifValue val = _ifd.get((Integer) oKeys[i]);
  			if (val != null)
  			{
  				result += val.getExtraSize();
  			}
  		}
  		return result;
  	}
  
  	/**
  	 * Write one "integer" number to output byte array. The method uses Intel
  	 * endian.
  	 * 
  	 * @param _data
  	 *            Output byte array
  	 * @param _offset
  	 *            offset, where the number will written
  	 * @param _value
  	 *            numeric value of the namber
  	 * @param _width
  	 *            number of bytes, the number covers
  	 */
  	public static void writeNumber(byte[] _data, int _offset, int _value, int _width)
  	{
  		int mask = 0xFF;
  		for (int i = 0; i < _width; i++)
  		{
  			_data[_offset + i] = (byte) ((_value & (mask << i * 8)) >>> (i * 8));
  		}
  	}
  
  	/**
  	 * Write the specified IFD into given output byte array. The method writes
  	 * to array the directory itself, same as all the "extra" values to
  	 * directory-related area.
  	 * 
  	 * @param _data
  	 *            Output byte array
  	 * @param _ifd
  	 *            the directory to write
  	 * @param _offset
  	 *            Offset in the byte array, where the data goes
  	 * @param _nextOffset
  	 *            Value, which will be written to the "next" area of the
  	 *            directory. In fact only in case of IFD0 it will be a nonzero.
  	 */
  	private void writeIfd(byte[] _data, HashMap<Integer, ExifValue> _ifd, int _offset, int _nextOffset)
  	{
  		Object[] oKeys = _ifd.keySet().toArray();
  		int valuesOffset = _offset + 2 + oKeys.length * 12 + 4;
  		writeNumber(_data, _offset, oKeys.length, 2);
  		int itemOffset = _offset + 2;
  		Arrays.sort(oKeys);
  		for (int i = 0; i < oKeys.length; i++)
  		{
  			Integer key = (Integer) oKeys[i];
  			writeNumber(_data, itemOffset, key, 2);
  			valuesOffset = _ifd.get(key).writeToData(_data, itemOffset, valuesOffset);
  			itemOffset += 12;
  		}
  		writeNumber(_data, itemOffset, _nextOffset, 4);
  	}
  
  	/**
  	 * Saves new image file with current Exif information. It is quite expensive
  	 * operation, so it is recomended to call it only at the end of work.
  	 * 
  	 * @param _name
  	 *            name of the new file
  	 */
  	public void save(String _name)
  	{
  		// Write empty directory referencies to calculate size of dirs
  		ValueNumber val = new ValueNumber(FORMAT_UNSIGNED_LONG, 0);
  		ifd0.put(TAG_EXIF_POINTER, val);
  		val = new ValueNumber(FORMAT_UNSIGNED_LONG, 0);
  		ifd0.put(TAG_GPS_POINTER, val);
  		val = new ValueNumber(FORMAT_UNSIGNED_LONG, 0);
  		ifdExif.put(TAG_INTEROPERABILITY_POINTER, val);
  		// Adjust referencies to image data
  		val = new ValueNumber(FORMAT_UNSIGNED_LONG, 0);
  		ifd1.put(TAG_JPEG_INTERCHANGE_FORMAT, val);
  
  		int startOfIfd0 = TIFFHeader.length;
  		int startOfIfdExif = startOfIfd0 + requiredSpace(ifd0);
  		int startOfIfdIOper = startOfIfdExif + requiredSpace(ifdExif);
  		int startOfIfdGps = startOfIfdIOper + requiredSpace(ifdIOper);
  		int startOfIfd1 = startOfIfdGps + requiredSpace(ifdGps);
  		int startOfThumbnail = startOfIfd1 + requiredSpace(ifd1);
  		int reqSize = startOfThumbnail + origThumbnailLength;
  
  		if (origThumbnailOffset == -1)
  		{
  			reqSize = startOfIfd1;
  		}
  
  		// Write directory referencies
  		val = new ValueNumber(FORMAT_UNSIGNED_LONG, startOfIfdExif);
  		ifd0.put(TAG_EXIF_POINTER, val);
  		val = new ValueNumber(FORMAT_UNSIGNED_LONG, startOfIfdGps);
  		ifd0.put(TAG_GPS_POINTER, val);
  		val = new ValueNumber(FORMAT_UNSIGNED_LONG, startOfIfdIOper);
  		ifdExif.put(TAG_INTEROPERABILITY_POINTER, val);
  		// Adjust referencies to image data
  		val = new ValueNumber(FORMAT_UNSIGNED_LONG, startOfThumbnail);
  		ifd1.put(TAG_JPEG_INTERCHANGE_FORMAT, val);
  		// }
  		// Write all headers
  		byte[] resultExif = new byte[reqSize];
  		byte[] exifHeader = new byte[] { (byte) 0xFF, (byte) 0xE1, 0, 0, (byte) 0x45, (byte) 0x78, (byte) 0x69,
  				(byte) 0x66, 0, 0 };
  		exifHeader[2] = (byte) (((reqSize + 8) & 0xFF00) >> 8);
  		exifHeader[3] = (byte) ((reqSize + 8) & 0xFF);
  		// Note, we will always use Intel align
  		byte[] tiffHeader = new byte[] { 0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00 };
  		System.arraycopy(tiffHeader, 0, resultExif, 0, tiffHeader.length);
  		writeIfd(resultExif, ifd0, startOfIfd0, origThumbnailOffset == -1 ? 0 : startOfIfd1);
  		writeIfd(resultExif, ifdExif, startOfIfdExif, 0);
  		writeIfd(resultExif, ifdIOper, startOfIfdIOper, 0);
  		writeIfd(resultExif, ifdGps, startOfIfdGps, 0);
  
  		if (origThumbnailOffset != -1)
  		{
  			writeIfd(resultExif, ifd1, startOfIfd1, 0);
  			System.arraycopy(origEXIFdata, origThumbnailOffset, resultExif, startOfThumbnail, origThumbnailLength);
  		}
  
  		FileOutputStream fos = null;
  		FileInputStream fis = null;
  		try
  		{
  			fos = new FileOutputStream(_name);
  			fis = new FileInputStream(sourceFile);
  			fos.write((byte) 0xFF);
  			fos.write((byte) 0xD8);
  			fos.write(exifHeader);
  			fos.write(resultExif);
  			int skipped = 0;
  			int imageOffset = origAPP1MarkerOffset + APP1Marker.length + LENGTH_EXIF_SIZE_DECL + EXIFHeader.length
  					+ origEXIFdata.length;
  			while (skipped < imageOffset)
  			{
  				int skip = (int) fis.skip(imageOffset - skipped);
  				if (skip < 0)
  				{
  					fos.close();
  					throw (new IOException());
  				} else
  				{
  					skipped += skip;
  				}
  			}
  			byte[] buffer = new byte[10240];
  			int len;
  			while ((len = fis.read(buffer)) > 0)
  			{
  				fos.write(buffer, 0, len);
  			}
  			fis.close();
  			fos.close();
  		} catch (FileNotFoundException ex)
  		{
  			Logger.getLogger(ExifDriver.class.getName()).log(Level.SEVERE, null, ex);
  		} catch (IOException ex)
  		{
  			Logger.getLogger(ExifDriver.class.getName()).log(Level.SEVERE, null, ex);
  		} finally
  		{
  			try
  			{
  				fis.close();
  				fos.close();
  			} catch (IOException ex)
  			{
  				Logger.getLogger(ExifDriver.class.getName()).log(Level.SEVERE, null, ex);
  			}
  
  		}
  	}
  
  	public HashMap<Integer, ExifValue> getIfd0()
  	{
  		return ifd0;
  	}
  
  	public HashMap<Integer, ExifValue> getIfdExif()
  	{
  		return ifdExif;
  	}
  
  	public HashMap<Integer, ExifValue> getIfdGps()
  	{
  		return ifdGps;
  	}
  
  	public HashMap<Integer, ExifValue> getIfd1()
  	{
  		return ifd1;
  	}
  
  	public HashMap<Integer, ExifValue> getIfdIOper()
  	{
  		return ifdIOper;
  	}
  }
  