gt:         ArrayList<EXIFGPSTagSet> tags = new ArrayList<EXIFGPSTagSet>();
context: /*
   * JGrass - Free Open Source Java GIS http://www.jgrass.org 
   * (C) HydroloGIS - www.hydrologis.com 
   * 
   * This library is free software; you can redistribute it and/or modify it under
   * the terms of the GNU Library General Public License as published by the Free
   * Software Foundation; either version 2 of the License, or (at your option) any
   * later version.
   * 
   * This library is distributed in the hope that it will be useful, but WITHOUT
   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
   * FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more
   * details.
   * 
   * You should have received a copy of the GNU Library General Public License
   * along with this library; if not, write to the Free Foundation, Inc., 59
   * Temple Place, Suite 330, Boston, MA 02111-1307 USA
   */
  package org.hortonmachine.gears.io.exif;
  
  import static org.hortonmachine.gears.i18n.GearsMessages.EXIFGPSWRITER_AUTHORCONTACTS;
  import static org.hortonmachine.gears.i18n.GearsMessages.EXIFGPSWRITER_AUTHORNAMES;
  import static org.hortonmachine.gears.i18n.GearsMessages.EXIFGPSWRITER_DESCRIPTION;
  import static org.hortonmachine.gears.i18n.GearsMessages.EXIFGPSWRITER_DO_EAST_DESCRIPTION;
  import static org.hortonmachine.gears.i18n.GearsMessages.EXIFGPSWRITER_DO_NORTH_DESCRIPTION;
  import static org.hortonmachine.gears.i18n.GearsMessages.EXIFGPSWRITER_FILE_DESCRIPTION;
  import static org.hortonmachine.gears.i18n.GearsMessages.EXIFGPSWRITER_KEYWORDS;
  import static org.hortonmachine.gears.i18n.GearsMessages.EXIFGPSWRITER_LABEL;
  import static org.hortonmachine.gears.i18n.GearsMessages.EXIFGPSWRITER_LICENSE;
  import static org.hortonmachine.gears.i18n.GearsMessages.EXIFGPSWRITER_NAME;
  import static org.hortonmachine.gears.i18n.GearsMessages.EXIFGPSWRITER_P_ALTITUDE_DESCRIPTION;
  import static org.hortonmachine.gears.i18n.GearsMessages.EXIFGPSWRITER_P_LAT_DESCRIPTION;
  import static org.hortonmachine.gears.i18n.GearsMessages.EXIFGPSWRITER_P_LON_DESCRIPTION;
  import static org.hortonmachine.gears.i18n.GearsMessages.EXIFGPSWRITER_STATUS;
  import static org.hortonmachine.gears.i18n.GearsMessages.EXIFGPSWRITER_T_TIMESTAMP_DESCRIPTION;
  
  import java.awt.image.BufferedImage;
  import java.io.ByteArrayInputStream;
  import java.io.ByteArrayOutputStream;
  import java.io.File;
  import java.io.IOException;
  import java.text.DecimalFormat;
  import java.util.ArrayList;
  import java.util.Iterator;
  
  import javax.imageio.IIOImage;
  import javax.imageio.ImageIO;
  import javax.imageio.ImageReader;
  import javax.imageio.ImageTypeSpecifier;
  import javax.imageio.ImageWriteParam;
  import javax.imageio.ImageWriter;
  import javax.imageio.metadata.IIOInvalidTreeException;
  import javax.imageio.metadata.IIOMetadata;
  import javax.imageio.metadata.IIOMetadataNode;
  import javax.imageio.stream.FileImageOutputStream;
  import javax.imageio.stream.ImageInputStream;
  import javax.imageio.stream.MemoryCacheImageInputStream;
  import javax.imageio.stream.MemoryCacheImageOutputStream;
  
  import oms3.annotations.Author;
  import oms3.annotations.Description;
  import oms3.annotations.Execute;
  import oms3.annotations.In;
  import oms3.annotations.Keywords;
  import oms3.annotations.Label;
  import oms3.annotations.License;
  import oms3.annotations.Name;
  import oms3.annotations.Out;
  import oms3.annotations.Status;
  import oms3.annotations.UI;
  
  import org.hortonmachine.gears.libs.exceptions.ModelsIOException;
  import org.hortonmachine.gears.libs.modules.HMConstants;
  import org.hortonmachine.gears.libs.modules.HMModel;
  import org.w3c.dom.NodeList;
  
  import com.sun.media.imageio.plugins.tiff.EXIFGPSTagSet;
  import com.sun.media.imageio.plugins.tiff.EXIFParentTIFFTagSet;
  import com.sun.media.imageio.plugins.tiff.TIFFDirectory;
  import com.sun.media.imageio.plugins.tiff.TIFFField;
  import com.sun.media.imageio.plugins.tiff.TIFFImageReadParam;
  import com.sun.media.imageio.plugins.tiff.TIFFTag;
  import com.sun.media.imageioimpl.plugins.tiff.TIFFIFD;
  
  @Description(EXIFGPSWRITER_DESCRIPTION)
  @Author(name = EXIFGPSWRITER_AUTHORNAMES, contact = EXIFGPSWRITER_AUTHORCONTACTS)
  @Keywords(EXIFGPSWRITER_KEYWORDS)
  @Label(EXIFGPSWRITER_LABEL)
  @Name(EXIFGPSWRITER_NAME)
  @Status(EXIFGPSWRITER_STATUS)
  @License(EXIFGPSWRITER_LICENSE)
  @UI(HMConstants.HIDE_UI_HINT)
  public class ExifGpsWriter extends HMModel {
  
      @Description(EXIFGPSWRITER_FILE_DESCRIPTION)
      @In
      public String file = null;
  
      @Description(EXIFGPSWRITER_P_LAT_DESCRIPTION)
      @Out
      public Double pLat = null;
  
      @Description(EXIFGPSWRITER_P_LON_DESCRIPTION)
      @Out
      public Double pLon = null;
  
      @Description(EXIFGPSWRITER_T_TIMESTAMP_DESCRIPTION)
      @Out
      public String tTimestamp = null;
  
      @Description(EXIFGPSWRITER_P_ALTITUDE_DESCRIPTION)
      @Out
      public Double pAltitude = null;
  
      @Description(EXIFGPSWRITER_DO_NORTH_DESCRIPTION)
      @Out
      public boolean doNorth = true;
  
      @Description(EXIFGPSWRITER_DO_EAST_DESCRIPTION)
      @Out
      public boolean doEast = true;
  
      private ImageReader jpegReader;
  
      private ImageWriter jpegWriter;
  
      private BufferedImage image;
  
      private File imageFile;
  
      private String[] latRef = {"", ""};
      private String[] longRef = {"", ""};
      private byte[] altRef = new byte[1];
      private long[][] latitude;
      private long[][] longitude;
      private long[][] altitude;
      private String[] imgDirectionRef = {"", ""};
      private long[][] imgDirection;
      private String[] datum = {"W", "G", "S", "-", "8", "4", ""};
      private String[] status = {"", ""};
      private long[][] timeStamp;
      private String[] dateStamp = new String[11];
  
      private DecimalFormat latFormatter = new DecimalFormat("0000.0000");
      private DecimalFormat lonFormatter = new DecimalFormat("00000.0000");
  
      @Execute
      public void writeGpsExif() throws IOException {
  
          checkNull(pLat, pLon, tTimestamp);
  
          String latStr = latFormatter.format(pLat * 100);
          latitude = getLatitude(latStr);
          String lonStr = lonFormatter.format(pLon * 100);
          longitude = getLongitude(lonStr);
  
          latRef[0] = doNorth ? EXIFGPSTagSet.LATITUDE_REF_NORTH : EXIFGPSTagSet.LATITUDE_REF_SOUTH;
          longRef[0] = doEast ? EXIFGPSTagSet.LONGITUDE_REF_EAST : EXIFGPSTagSet.LONGITUDE_REF_WEST;
  
          if (pAltitude != null) {
              double alt = pAltitude * 10;
              altitude = new long[][]{{(long) alt, 10}};
              altRef[0] = EXIFGPSTagSet.ALTITUDE_REF_SEA_LEVEL;
          }
  
          String[] timeStampSplit = tTimestamp.trim().split("\\s+"); // yyyy-MM-dd HH:mm:ss
          String date = timeStampSplit[0].replaceAll("-", ":");
          dateStamp = getDate(date);
          String time = timeStampSplit[1].replaceAll(":", "");
          timeStamp = getTime(time);
  
          imageFile = new File(file);
          ImageInputStream is = ImageIO.createImageInputStream(imageFile);
  
          // Get core JPEG reader.
          jpegReader = ExifUtil.findReader();
          if (jpegReader == null) {
              throw new ModelsIOException("Cannot find JPEG reader.", this);
          }
  
          // Get core JPEG writer.
          jpegWriter = ExifUtil.findWriter();
          if (jpegWriter == null) {
              throw new ModelsIOException("Cannot find JPEG writer.", this);
          }
  
          jpegReader.setInput(is);
          image = jpegReader.read(0);
  
          writeExif();
  
      }
  
      /**
       * Main method to write the gps data to the exif 
       * @param gps - gps position to be added
       * @throws IOException 
       */
      private void writeExif() throws IOException {
  
          IIOMetadata metadata = jpegReader.getImageMetadata(0);
  
          // names says which exif tree to get - 0 for jpeg 1 for the default
          String[] names = metadata.getMetadataFormatNames();
          IIOMetadataNode root = (IIOMetadataNode) metadata.getAsTree(names[0]);
  
          // exif is on the app1 node called unknown
          NodeList nList = root.getElementsByTagName("unknown");
          IIOMetadataNode app1EXIFNode = (IIOMetadataNode) nList.item(0);
          ArrayList<IIOMetadata> md = readExif(app1EXIFNode);
          IIOMetadata exifMetadata = md.get(0);
  
          // insert the gps data into the exif
          exifMetadata = insertGPSCoords(exifMetadata);
  
          // create a new exif node
          IIOMetadataNode app1NodeNew = createNewExifNode(exifMetadata, null, null);
  
          // copy the user data accross
          app1EXIFNode.setUserObject(app1NodeNew.getUserObject());
  
          // write to a new image file
          FileImageOutputStream out1 = new FileImageOutputStream(new File("GPS_" + imageFile.getName()));
          jpegWriter.setOutput(out1);
          metadata.setFromTree(names[0], root);
  
          IIOImage image = new IIOImage(jpegReader.readAsRenderedImage(0, jpegReader.getDefaultReadParam()), null, metadata);
  
          // write out the new image
          jpegWriter.write(jpegReader.getStreamMetadata(), image, jpegWriter.getDefaultWriteParam());
  
      }
  
      /**
       * Private method - Reads the exif metadata for an image
       * @param app1EXIFNode app1 Node of the image (where the exif data is stored)
       * @return the exif metadata
       */
      private ArrayList<IIOMetadata> readExif( IIOMetadataNode app1EXIFNode ) {
          // Set up input skipping EXIF ID 6-byte sequence.
          byte[] app1Params = (byte[]) app1EXIFNode.getUserObject();
  
          MemoryCacheImageInputStream app1EXIFInput = new MemoryCacheImageInputStream(new ByteArrayInputStream(app1Params, 6,
                  app1Params.length - 6));
  
          // only the tiff reader knows how to interpret the exif metadata
          ImageReader tiffReader = null;
          Iterator<ImageReader> readers = ImageIO.getImageReadersByFormatName("tiff");
  
          while( readers.hasNext() ) {
              tiffReader = (ImageReader) readers.next();
              if (tiffReader.getClass().getName().startsWith("com.sun.media")) {
                  // Break on finding the core provider.
                  break;
              }
          }
          if (tiffReader == null) {
              throw new RuntimeException("Cannot find core TIFF reader!");
          }
  
          ArrayList<IIOMetadata> out = new ArrayList<IIOMetadata>(1);
  
          tiffReader.setInput(app1EXIFInput);
  
          IIOMetadata tiffMetadata = null;
  
          try {
              tiffMetadata = tiffReader.getImageMetadata(0);
              // IIOMetadata meta = tiffReader.getImageMetadata(0);
              TIFFImageReadParam rParam = (TIFFImageReadParam) tiffReader.getDefaultReadParam();
              rParam.setTIFFDecompressor(null);
          } catch (IOException e) {
              e.printStackTrace();
          };
  
          tiffReader.dispose();
  
          out.add(0, tiffMetadata);
  
          return out;
      }
  
      /**
       * Private method - creates a copy of the metadata that can be written to
       * @param tiffMetadata - in metadata
       * @return new metadata node that can be written to
       */
      private IIOMetadataNode createNewExifNode( IIOMetadata tiffMetadata, IIOMetadata thumbMeta, BufferedImage thumbnail ) {
  
          IIOMetadataNode app1Node = null;
          ImageWriter tiffWriter = null;
          try {
              Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName("tiff");
              while( writers.hasNext() ) {
                  tiffWriter = writers.next();
                  if (tiffWriter.getClass().getName().startsWith("com.sun.media")) {
                      // Break on finding the core provider.
                      break;
                  }
              }
              if (tiffWriter == null) {
                  System.out.println("Cannot find core TIFF writer!");
                  System.exit(0);
              }
  
              ImageWriteParam writeParam = tiffWriter.getDefaultWriteParam();
              writeParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
              writeParam.setCompressionType("EXIF JPEG");
  
              ByteArrayOutputStream baos = new ByteArrayOutputStream();
              MemoryCacheImageOutputStream app1EXIFOutput = new MemoryCacheImageOutputStream(baos);
              tiffWriter.setOutput(app1EXIFOutput);
  
              // escribir
              tiffWriter.prepareWriteEmpty(jpegReader.getStreamMetadata(), new ImageTypeSpecifier(image), image.getWidth(),
                      image.getHeight(), tiffMetadata, null, writeParam);
  
              tiffWriter.endWriteEmpty();
  
              // Flush data into byte stream.
              app1EXIFOutput.flush();
  
              // Create APP1 parameter array.
              byte[] app1Parameters = new byte[6 + baos.size()];
  
              // Add EXIF APP1 ID bytes.
              app1Parameters[0] = (byte) 'E';
              app1Parameters[1] = (byte) 'x';
              app1Parameters[2] = (byte) 'i';
              app1Parameters[3] = (byte) 'f';
              app1Parameters[4] = app1Parameters[5] = (byte) 0;
  
              // Append TIFF stream to APP1 parameters.
              System.arraycopy(baos.toByteArray(), 0, app1Parameters, 6, baos.size());
  
              // Create the APP1 EXIF node to be added to native JPEG image metadata.
              app1Node = new IIOMetadataNode("unknown");
              app1Node.setAttribute("MarkerTag", (new Integer(0xE1)).toString());
              app1Node.setUserObject(app1Parameters);
          } catch (IOException e) {
              e.printStackTrace();
          } finally {
              if (tiffWriter != null)
                  tiffWriter.dispose();
          }
  
          return app1Node;
  
      }
  
      /**
       * Private method - adds gps information to the exif data
       * @param pos a GPSPosition object containing the information to encode
       * @param exif the exif metadata to add the position to
       * 
       */
      private IIOMetadata insertGPSCoords( IIOMetadata exif ) {
  
          IIOMetadata outExif = null;
          try {
              TIFFDirectory ifd = TIFFDirectory.createFromMetadata(exif);
              TIFFField gpsInfoPointer = null;
  
              // first get the pointer from the directory if it's not there create a new one
              if (ifd.containsTIFFField(EXIFParentTIFFTagSet.TAG_GPS_INFO_IFD_POINTER)) {
                  gpsInfoPointer = ifd.getTIFFField(EXIFParentTIFFTagSet.TAG_GPS_INFO_IFD_POINTER);
                  System.out.println("Already has GPS Metadata");
                  return exif;
              } else {
                  // this assumes that the EXIFParentTIFFTagSet is allowed on the tiff image reader
  
                  // first construct the directory to hold the GPS data
                  TIFFDirectory gpsData = createDirectory();
  
                  // Create the pointer with the data
                  EXIFParentTIFFTagSet parentSet = EXIFParentTIFFTagSet.getInstance();
                  gpsInfoPointer = new TIFFField(parentSet.getTag(EXIFParentTIFFTagSet.TAG_GPS_INFO_IFD_POINTER),
                          TIFFTag.TIFF_LONG, 1, gpsData);
                  System.out.println("is pointer =" + gpsInfoPointer.getTag().isIFDPointer() + " data type is ok="
                          + gpsInfoPointer.getTag().isDataTypeOK(TIFFTag.TIFF_LONG));
              }
              ifd.addTIFFField(gpsInfoPointer);
              outExif = ifd.getAsMetadata();
  
          } catch (IIOInvalidTreeException e) {
              e.printStackTrace();
          }
  
          return outExif;
  
      }
  
      private TIFFDirectory createDirectory() {
  
          EXIFGPSTagSet gpsTags = EXIFGPSTagSet.getInstance();
  
  