content: package tim.prune.jpeg.drew;
  
  import java.io.BufferedInputStream;
  import java.io.File;
  import java.io.FileInputStream;
  import java.io.IOException;
  
  import tim.prune.jpeg.JpegData;
  
  
  /**
   * Extracts Exif data from a JPEG header segment
   * Based on Drew Noakes' Metadata extractor at https://drewnoakes.com
   * which in turn is based on code from Jhead http://www.sentex.net/~mwandel/jhead/
   */
  public class ExifReader
  {
  	/** Magic numbers to mark the beginning of all Jpegs */
  	private static final int MAGIC_JPEG_BYTE_1 = 0xFF;
  	private static final int MAGIC_JPEG_BYTE_2 = 0xD8;
  
  	/** 6-byte preamble before starting the TIFF data. */
  	private static final String JPEG_EXIF_SEGMENT_PREAMBLE = "Exif\0\0";
  
  	/** Start of segment marker */
  	private static final byte SEGMENT_SOS = (byte) 0xDA;
  
  	/** End of segment marker */
  	private static final byte MARKER_EOI = (byte) 0xD9;
  
  	/**
  	 * Processes the provided JPEG data, and extracts the specified JPEG segments into a JpegData object.
  	 * @param inFile a {@link File} from which the JPEG data will be read.
  	 */
  	public static JpegData readMetadata(File inFile) throws ExifException
  	{
  		JpegData jpegData = new JpegData();
  		BufferedInputStream bStream = null;
  
  		try
  		{
  			bStream = new BufferedInputStream(new FileInputStream(inFile));
  			byte[] segmentBytes = readSegments(bStream);
  			if (segmentBytes != null)
  			{
  				// Got the bytes for the required segment, now extract the data
  				extract(segmentBytes, jpegData);
  			}
  		}
  		catch (IOException ioe) {
  			throw new ExifException("IO Exception: " + ioe.getMessage());
  		}
  		finally
  		{
  			if (bStream != null) {
  				try {
  					bStream.close();
  				} catch (IOException ioe) {}
  			}
  		}
  		return jpegData;
  	}
  
  	/**
  	 * Reads the relevant segment and returns the bytes.
  	 */
  	private static byte[] readSegments(final BufferedInputStream bStream)
  		throws ExifException, IOException
  	{
  		// first two bytes should be JPEG magic number
  		final int magic1 = bStream.read() & 0xFF;
  		final int magic2 = bStream.read() & 0xFF;
  		if (magic1 != MAGIC_JPEG_BYTE_1 || magic2 != MAGIC_JPEG_BYTE_2) {
  			throw new ExifException("Jpeg file failed Magic check");
  		}
  
  		final byte segmentTypeByte = (byte)0xE1; // JpegSegmentType.APP1.byteValue;
  
  		do {
  			// Find the segment marker. Markers are zero or more 0xFF bytes, followed
  			// by a 0xFF and then a byte not equal to 0x00 or 0xFF.
  
  			final short segmentIdentifier = (short) bStream.read();
  
  			// We must have at least one 0xFF byte
  			if (segmentIdentifier != 0xFF)
  				throw new ExifException("Expected JPEG segment start identifier 0xFF, not 0x" + Integer.toHexString(segmentIdentifier).toUpperCase());
  
  			// Read until we have a non-0xFF byte. This identifies the segment type.
  			byte currSegmentType = (byte) bStream.read();
  			while (currSegmentType == (byte)0xFF) {
  				currSegmentType = (byte) bStream.read();
  			}
  
  			if (currSegmentType == 0)
  				throw new ExifException("Expected non-zero byte as part of JPEG marker identifier");
  
  			if (currSegmentType == SEGMENT_SOS) {
  				// The 'Start-Of-Scan' segment's length doesn't include the image data, instead would
  				// have to search for the two bytes: 0xFF 0xD9 (EOI).
  				// It comes last so simply return at this point
  				return null;
  			}
  
  			if (currSegmentType == MARKER_EOI) {
  				// the 'End-Of-Image' segment -- this should never be found in this fashion
  				return null;
  			}
  
  			// next 2-bytes are <segment-size>: [high-byte] [low-byte]
  			int segmentLength = (bStream.read() << 8) + bStream.read();
  			// segment length includes size bytes, so subtract two
  			segmentLength -= 2;
  
  			if (segmentLength < 0)
  				throw new ExifException("JPEG segment size would be less than zero");
  
  			byte[] segmentBytes = new byte[segmentLength];
  			int bytesRead = bStream.read(segmentBytes, 0, segmentLength);
  			// Bail if not all bytes read in one go - otherwise following sections will be out of step
  			if (bytesRead != segmentLength) {
  				throw new ExifException("Tried to read " + segmentLength + " bytes but only got " + bytesRead);
  			}
  			// Check whether we are interested in this segment
  			if (segmentTypeByte == currSegmentType)
  			{
  				// Pass the appropriate byte arrays to reader.
  				if (canProcess(segmentBytes)) {
  					return segmentBytes;
  				}
  			}
  
  		} while (true);
  	}
  
  	private static boolean canProcess(final byte[] segmentBytes)
  	{
  		return segmentBytes.length >= JPEG_EXIF_SEGMENT_PREAMBLE.length() && new String(segmentBytes, 0, JPEG_EXIF_SEGMENT_PREAMBLE.length()).equalsIgnoreCase(JPEG_EXIF_SEGMENT_PREAMBLE);
  	}
  
  	/**
  	 * Given the bytes, parse them recursively to fill the JpegData
  	 * @param segmentBytes bytes out of the file
  	 * @param jdata jpeg data to be populated
  	 */
  	private static void extract(final byte[] segmentBytes, final JpegData jdata)
  	{
  		if (segmentBytes == null)
  			throw new NullPointerException("segmentBytes cannot be null");
  
  		try
  		{
  			ByteArrayReader reader = new ByteArrayReader(segmentBytes);
  
  			// Check for the header preamble
  			try {
  				if (!reader.getString(0, JPEG_EXIF_SEGMENT_PREAMBLE.length()).equals(JPEG_EXIF_SEGMENT_PREAMBLE)) {
  					// TODO what to do with this error state?
  					System.err.println("Invalid JPEG Exif segment preamble");
  					return;
  				}
  			} catch (ExifException e) {
  				// TODO what to do with this error state?
  				e.printStackTrace(System.err);
  				return;
  			}
  
  			// Read the TIFF-formatted Exif data
  			TiffProcessor.processTiff(
  				reader,
  				jdata,
  				JPEG_EXIF_SEGMENT_PREAMBLE.length()
  			);
  
  		} catch (ExifException e) {
  			// TODO what to do with this error state?
  			e.printStackTrace(System.err);
  		}
  	}
  }
  