/*::.
==================================================================================================================================
=================================================¦ Copyright © 2006 Allen Baker ¦=================================================
                                                 +------------------------------+
File:       ZipFile.java
Originator: Allen Baker (2006.11.17)
LayoutRev:  5
================================================================================================================================== */



/*.
==========================================================================================
Package
------------------------------------------------------------------------------------------ */
package cosmicabyss.com.lib;



/*.
==========================================================================================
Imports
------------------------------------------------------------------------------------------ */
import java.io.*;
import java.net.*;
import java.util.*;
import java.security.*;
import java.util.zip.*;



/*::
======================================================================================================================== *//**
Instances of this class represent archive files that are implemented with the ZIP file standard. ZipFiles can be
created, have files added to them, deleted from them, etc. And since ZipFile is a subclass of XFile, all the things that
can be done with an XFile can also be done with a ZipFile.

<P>
   <DL>
      <DT>
         <B>
            Example usage:
         </B>
         <DD>
            <BLOCKQUOTE>
               <PRE id="unindent">
                  ZipFile x = new ZipFile("c:/temp/test.zip");
                  x.putEntry("tuna.txt","c:/temp/in.txt");
                  x.getEntry("tuna.txt","c:/temp/out.txt");
               </PRE>
            </BLOCKQUOTE>
         </DD>
      </DT>
      <DT>
         <B>
            View Source:
         </B>
         <DD>
            <A href="ZipFile.java.html">
               ZipFile.java
            </A>
         </DD>
      </DT>
      <DT>
         <B>
            Author:
         </B>
         <DD>
            <A href="mailto:sourcecode.v01@cosmicabyss.com">
               Allen Baker
            </A>
         </DD>
      </DT>
   </DL>
*//*
======================================================================================================================== */
public class ZipFile extends XFile
   {



   /*:                                    
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   This method creates an instance of the ZipFile class.

   <P><B>Implementation: </B><A HREF="ZipFile.java.html#000">View source</A>

   *//*
   ---------------------------------------------------------------------------------------------------- */
   public <Type> ZipFile(File parent, Type child)
      {
      super(parent,child);
      }



   /*:                                    
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   This method creates an instance of the ZipFile class.

   <P><B>Implementation: </B><A HREF="ZipFile.java.html#001">View source</A>

   *//*
   ---------------------------------------------------------------------------------------------------- */
   public <Type> ZipFile(Type pathname)
      {
      super(pathname);
      }



   /*:                                    
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   This method creates an instance of the ZipFile class.

   <P><B>Implementation: </B><A HREF="ZipFile.java.html#002">View source</A>

   *//*
   ---------------------------------------------------------------------------------------------------- */
   public <Type1,Type2> ZipFile(Type1 parent, Type2 child)
      {
      super(parent,child);
      }



   /*:                                    
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   This method creates an instance of the ZipFile class.

   <P><B>Implementation: </B><A HREF="ZipFile.java.html#003">View source</A>

   *//*
   ---------------------------------------------------------------------------------------------------- */
   public ZipFile(URI uri)
      {
      super(uri);
      }



   /*:                                    
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   This method creates an instance of the ZipFile class.

   <P><B>Implementation: </B><A HREF="ZipFile.java.html#004">View source</A>

   *//*
   ---------------------------------------------------------------------------------------------------- */
   public ZipFile(File file) throws Exception
      {
      super(file.getCanonicalPath());
      }



   /*:                                    
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   This method creates an instance of the ZipFile class.

   <P><B>Implementation: </B><A HREF="ZipFile.java.html#005">View source</A>

   *//*
   ---------------------------------------------------------------------------------------------------- */
   public ZipFile(XFile xfile) throws Exception
      {
      super(xfile.getCanonicalPath());
      }



   /*:                                    
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   This method adds or replaces a file to the zip file

   <P><B>Implementation: </B><A HREF="ZipFile.java.html#006">View source</A>

   @param
      pEntryName is the name that will be given to the file inside the zip file
   @param
      pSrcFileName is the name of the file that will be copied and compressed into the zip file
   *//*
   ---------------------------------------------------------------------------------------------------- */
   public <Type1,Type2> void putEntry(Type1 pEntryName, Type2 pSrcFileName, long pDate) throws Exception
      {
      /*.
      ==========================================================================================
      ------------------------------------------------------------------------------------------ */
      XString  theEntryName    = XString.toXString(pEntryName);
      XString  theSrcFileName  = XString.toXString(pSrcFileName);
      XString  thisZipFileName = XString.toXString(this.getCanonicalPath());
      /*.
      ==========================================================================================
      Create file descriptors for the zip and a temp zip.
      ------------------------------------------------------------------------------------------ */
      File  thisZipFile = new File(thisZipFileName.string());
      File  tempZipFile = File.createTempFile(thisZipFile.getName(),".tmp");
      /*.
      ==========================================================================================
      The zip file we're going to be putting something into must exist. Furthermore, it can't be
      empty so create a temporary phoney entry in it. The phoney entry will be removed as soon
      as we have something legitimate to put in it.
      ------------------------------------------------------------------------------------------ */
      if ( ! thisZipFile.exists())
         {
         ZipOutputStream  zip = new ZipOutputStream
            (
            new FileOutputStream(thisZipFileName.string())
            );
         zip.putNextEntry(new ZipEntry("phoneyStubEntry"));
         zip.close();
         }
      /*.
      ==========================================================================================
      Now that it exists, Open the zip file.
      ------------------------------------------------------------------------------------------ */
      java.util.zip.ZipFile  zip = new java.util.zip.ZipFile(thisZipFile);
      /*.
      ==========================================================================================
      Initialize a flag that will indicate that the zip was updated.
      ------------------------------------------------------------------------------------------ */
      boolean  zipUpdated = false;
      /*.
      ==========================================================================================
      ------------------------------------------------------------------------------------------ */
      try
         {
         /*.
         ==========================================================================================
         Create a temp zip file
         ------------------------------------------------------------------------------------------ */
         ZipOutputStream  tempZip = new ZipOutputStream
            (
            new FileOutputStream(tempZipFile)
            );
         /*.
         ==========================================================================================
         Allocate a buffer for reading entry data.
         ------------------------------------------------------------------------------------------ */
         byte[]  buffer     = new byte[2 * UMath.ONE_MEG];
         int     bytesRead;
         /*.
         ==========================================================================================
         ------------------------------------------------------------------------------------------ */
         try
            {
            /*.
            ==========================================================================================
            Open the source file.
            ------------------------------------------------------------------------------------------ */
            FileInputStream  file = new FileInputStream(theSrcFileName.string());
            /*.
            ==========================================================================================
            ------------------------------------------------------------------------------------------ */
            try
               {
               /*.
               ==========================================================================================
               Create a zip entry and add it to the temp zip.
               ------------------------------------------------------------------------------------------ */
               ZipEntry  entry = new ZipEntry(theEntryName.string());
               tempZip.putNextEntry(entry);
               entry.setTime(pDate);
               /*.
               ==========================================================================================
               Read the source file and write it into the zip.
               ------------------------------------------------------------------------------------------ */
               while ((bytesRead = file.read(buffer)) != -1)
                  {
                  tempZip.write(buffer, 0, bytesRead);
                  }
               }
            finally
               {
               file.close();
               }
            /*.
            ==========================================================================================
            Loop through the zip entries and add them to the temp zip, skipping the entry that was
            added to the temp zip already.
            ------------------------------------------------------------------------------------------ */
            for (Enumeration entries=zip.entries(); entries.hasMoreElements();)
               {
               /*.
               ==========================================================================================
               Get the next entry.
               ------------------------------------------------------------------------------------------ */
               ZipEntry  entry = (ZipEntry) entries.nextElement();
               String    name  = entry.getName();
               long      date  = entry.getTime();
               /*.
               ==========================================================================================
               If the entry has not been added already, add it. This is also where we delete the phoney
               entry; its deleted by skipping it when it is encountered
               ------------------------------------------------------------------------------------------ */
               ifb>
                  (
                     (
                        ( ! name.equals("phoneyStubEntry"))) &<_AMP_AMP_COLON_>
                        ( ! name.equals(theEntryName.string())
                     )
                  )
                  {
                  /*.
                  ==========================================================================================
                  Get an input stream for the entry.
                  ------------------------------------------------------------------------------------------ */
                  InputStream  entryStream = zip.getInputStream(entry);
                  /*.
                  ==========================================================================================
                  Read the entry and write it to the temp zip.
                  ------------------------------------------------------------------------------------------ */
                  tempZip.putNextEntry(entry);
                  entry.setTime(date);
                  while ((bytesRead = entryStream.read(buffer)) != -1)
                     {
                     tempZip.write(buffer, 0, bytesRead);
                     }
                  }
               }
            zipUpdated = true;
            }
         catch (Exception ex)
            {
            /*.
            ==========================================================================================
            Add a stub entry here, so that the zip will close without an exception.
            ------------------------------------------------------------------------------------------ */
            tempZip.putNextEntry(new ZipEntry("phoneyStubEntry"));
            }
         finally
            {
            tempZip.close();
            }
         }
      finally
         {
         zip.close();
         /*.
         ==========================================================================================
         If the zip was not updated, delete the temp zip file.
         ------------------------------------------------------------------------------------------ */
         if ( ! zipUpdated)
            {
            tempZipFile.delete();
            }
         }
      /*.
      ==========================================================================================
      If the zip was updated, delete the original zip file and rename the temp zip file to the
      original name.
      ------------------------------------------------------------------------------------------ */
      if (zipUpdated)
         {
         thisZipFile.delete();
         tempZipFile.renameTo(thisZipFile);
         }
      }



   /*:                                    
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   This method extracts a file from the zip file

   <P><B>Implementation: </B><A HREF="ZipFile.java.html#007">View source</A>

   @param
      pEntryName is the name of the file to extract from inside the zip file
   @param
      pDstFileName is the name of the file that will be decompressed and copied out of the zip file
   *//*
   ---------------------------------------------------------------------------------------------------- */
   public <Type1,Type2> void getEntry(Type1 pEntryName, Type2 pDstFileName) throws Exception
      {
      /*.
      ==========================================================================================
      ------------------------------------------------------------------------------------------ */
      XString  theEntryName    = XString.toXString(pEntryName);
      XString  theDstFileName  = XString.toXString(pDstFileName);
      /*.
      ==========================================================================================
      Open the zip.
      ------------------------------------------------------------------------------------------ */
      java.util.zip.ZipFile  zip = new java.util.zip.ZipFile(this.getCanonicalPath());
      try
         {
         /*.
         ==========================================================================================
         Get the entry.
         ------------------------------------------------------------------------------------------ */
         ZipEntry  entry = zip.getEntry(theEntryName.string());
         /*.
         ==========================================================================================
         If the entry is not null, extract it.
         ------------------------------------------------------------------------------------------ */
         if (entry != null)
            {
            /*.
            ==========================================================================================
            Get an input stream for the entry.
            ------------------------------------------------------------------------------------------ */
            InputStream  entryStream = zip.getInputStream(entry);
            try
               {
               /*.
               ==========================================================================================
               Create the output file (clobbering the file if it exists).
               ------------------------------------------------------------------------------------------ */
               FileOutputStream  file = new FileOutputStream(theDstFileName.string());
               try
                  {
                  /*.
                  ==========================================================================================
                  Allocate a buffer for reading the entry data.
                  ------------------------------------------------------------------------------------------ */
                  byte[]  buffer     = new byte[2 * UMath.ONE_MEG];
                  int     bytesRead;
                  /*.
                  ==========================================================================================
                  Read the entry data and write it to the output file.
                  ------------------------------------------------------------------------------------------ */
                  while ((bytesRead = entryStream.read(buffer)) != -1)
                     {
                     file.write(buffer, 0, bytesRead);
                     }
                  }
               finally
                  {
                  file.close();
                  }
               }
            finally
               {
               entryStream.close();
               }
            }
         }
      finally
         {
         zip.close();
         }
      }



   /*:                                    
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   This method removes the specified entry from this ZipFile.

   <P><B>Implementation: </B><A HREF="ZipFile.java.html#008">View source</A>

   @param
      pEntryName is the entry to remove from this ZipFile
   *//*
   ---------------------------------------------------------------------------------------------------- */
   public <Type> void deleteEntry(Type pEntryName) throws Exception
      {
      /*.
      ==========================================================================================
      ------------------------------------------------------------------------------------------ */
      XString  theEntryName    = XString.toXString(pEntryName);
      /*.
      ==========================================================================================
      ------------------------------------------------------------------------------------------ */
      XString  thisZipFileName = XString.toXString(this.getCanonicalPath());
      /*.
      ==========================================================================================
      Create file descriptors for the zip and a temp zip.
      ------------------------------------------------------------------------------------------ */
      File  thisZipFile = new File(thisZipFileName.string());
      File  tempZipFile = File.createTempFile(thisZipFile.getName(),".tmp");
      /*.
      ==========================================================================================
      The zip file we're going to be deleting from must exist. Furthermore, it can't be empty so
      create a temporary phoney entry in it. The phoney entry will be removed as soon as we have
      something legitimate to put in it.
      ------------------------------------------------------------------------------------------ */
      if ( ! thisZipFile.exists())
         {
         ZipOutputStream  zip = new ZipOutputStream
            (
            new FileOutputStream(thisZipFileName.string())
            );
         zip.putNextEntry(new ZipEntry("phoneyStubEntry"));
         zip.close();
         }
      /*.
      ==========================================================================================
      Now that it exists, Open the zip file.
      ------------------------------------------------------------------------------------------ */
      java.util.zip.ZipFile  zip = new java.util.zip.ZipFile(thisZipFile);
      /*.
      ==========================================================================================
      Initialize a flag that will indicate that the zip was updated.
      ------------------------------------------------------------------------------------------ */
      boolean  zipUpdated = false;
      /*.
      ==========================================================================================
      ------------------------------------------------------------------------------------------ */
      try
         {
         /*.
         ==========================================================================================
         Create a temp zip file
         ------------------------------------------------------------------------------------------ */
         ZipOutputStream  tempZip = new ZipOutputStream
            (
            new FileOutputStream(tempZipFile)
            );
         /*.
         ==========================================================================================
         Allocate a buffer for reading entry data.
         ------------------------------------------------------------------------------------------ */
         byte[]  buffer     = new byte[2 * UMath.ONE_MEG];
         int     bytesRead;
         /*.
         ==========================================================================================
         ------------------------------------------------------------------------------------------ */
         try
            {
            /*.
            ==========================================================================================
            Loop through the zip entries and add them to the temp zip, skipping the entry that is to
            be deleted.
            ------------------------------------------------------------------------------------------ */
            for (Enumeration entries=zip.entries(); entries.hasMoreElements();)
               {
               /*.
               ==========================================================================================
               Get the next entry.
               ------------------------------------------------------------------------------------------ */
               ZipEntry  entry = (ZipEntry) entries.nextElement();
               String    name  = entry.getName();
               long      date  = entry.getTime();
               /*.
               ==========================================================================================
               If the entry isn't the one we're deleting, add it to the temp zip.
               ------------------------------------------------------------------------------------------ */
               if (( ! name.equals(theEntryName)))
                  {
                  /*.
                  ==========================================================================================
                  Get an input stream for the entry.
                  ------------------------------------------------------------------------------------------ */
                  InputStream  entryStream = zip.getInputStream(entry);
                  /*.
                  ==========================================================================================
                  Read the entry and write it to the temp zip.
                  ------------------------------------------------------------------------------------------ */
                  tempZip.putNextEntry(entry);
                  entry.setTime(date);
                  while ((bytesRead = entryStream.read(buffer)) != -1)
                     {
                     tempZip.write(buffer, 0, bytesRead);
                     }
                  }
               }
            zipUpdated = true;
            }
         catch (Exception ex)
            {
            /*.
            ==========================================================================================
            Add a stub entry here, so that the zip will close without an exception.
            ------------------------------------------------------------------------------------------ */
            tempZip.putNextEntry(new ZipEntry("phoneyStubEntry"));
            }
         finally
            {
            tempZip.close();
            }
         }
      finally
         {
         zip.close();
         /*.
         ==========================================================================================
         If the zip was not updated, delete the temp zip file.
         ------------------------------------------------------------------------------------------ */
         if ( ! zipUpdated)
            {
            tempZipFile.delete();
            }
         }
      /*.
      ==========================================================================================
      If the zip was updated, delete the original zip file and rename the temp zip file to the
      original name.
      ------------------------------------------------------------------------------------------ */
      if (zipUpdated)
         {
         thisZipFile.delete();
         tempZipFile.renameTo(thisZipFile);
         }
      }



   /*:                                    
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   This method determines if the specified entry exists inside this Zipfile.

   <P><B>Implementation: </B><A HREF="ZipFile.java.html#009">View source</A>

   @return
      True is the entry does exist; otherwise, false

   @param
      pEntryName is the name of the entry to search for
   *//*
   ---------------------------------------------------------------------------------------------------- */
   public <Type> boolean hasEntry(Type pEntryName) throws Exception
      {
      String theEntryName = UString.toString(pEntryName);
      /*.
      ==========================================================================================
      Open the zip.
      ------------------------------------------------------------------------------------------ */
      java.util.zip.ZipFile  zip = new java.util.zip.ZipFile(this.getCanonicalPath());
      try
         {
         /*.
         ==========================================================================================
         Get the entry.
         ------------------------------------------------------------------------------------------ */
         ZipEntry  entry = zip.getEntry(theEntryName);
         /*.
         ==========================================================================================
         If the entry is not null, that means it is in the zip file so return true, otherwise the
         return value is false.
         ------------------------------------------------------------------------------------------ */
         return (entry != null);
         }
      finally
         {
         zip.close();
         }
      }



   /*:                                    
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   This method sets the ConsoleStream for this object.

   <P><B>Implementation: </B><A HREF="ZipFile.java.html#010">View source</A>

   @return
      A reference to this object

   @param
      pConsole is a ConsoleStream through which this objects sends its output.
   *//*
   ---------------------------------------------------------------------------------------------------- */
   public ZipFile setOut(ConsoleStream pConsole)
      {
      cOut = pConsole;
      return this;
      }



   /*:                                    
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   This boilerplate method is used for testing an instantiated object of this class and may include any
   code the developer chooses.

   <P><B>Implementation: </B><A HREF="ZipFile.java.html#011">View source</A>

   *//*
   ---------------------------------------------------------------------------------------------------- */
   public ZipFile test() throws Exception
      {
      cOut.titledPrintf
         (
         "\"HELLO WORLD!\"",
         "%s  %s  %s",
         "I'm an object of the", CLASS_NAME, "class, and I approved this message."
         );
      return this;
      }



   /*:.
   ==============================================================================================================
   @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@[  Protected  ]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
   ============================================================================================================== */



   /*:.
   ==============================================================================================================
   @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@[  Private  ]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
   ============================================================================================================== */



   /*.
   ==========================================================================================
   Class Constants
      CLASS_NAME:
         The name of this class
      DFLT_LINE_LEN:
         The default line length for word wrapping
   ------------------------------------------------------------------------------------------ */
   private static final XString  CLASS_NAME    = new XString(ZipFile.class.getName());
   private static final int      DFLT_LINE_LEN = ConsoleMessage.defaultLineLength();
   /*.
   ==========================================================================================
   Class variables
      cOut : console output.
   ------------------------------------------------------------------------------------------ */
   private static ConsoleStream  cOut = ConsoleStream.getSingleton();



   /*:.
   ==============================================================================================================
   @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@[  Inner Classes  ]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
   ============================================================================================================== */



   /*:.
   ==============================================================================================================
   @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@[  Public Static Methods  ]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
   ============================================================================================================== */



   /*:                                    
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   This method allows this class file to be unit tested as a standalone application. It's the method
   that's called when the class is invoked from the command line by using the java application launcher
   - "java". Main() is not a required method, but the practice of putting one in each class and
   wrapping class test code within it allows easy unit testing of the class; and main does not need to
   be removed when testing is complete.

   <P>
      <DL>
         <DT>
            <B>
               Command line usage:
            </B>
            <DD>
               Java cosmicabyss.com.lib.ZipFile
            </DD>
         </DT>
      </DL>

   <P><B>Implementation: </B><A HREF="ZipFile.java.html#012">View source</A>

   @param
      pArgs contains the command line arguments with which this class was invoked as an application.
   *//*
   ---------------------------------------------------------------------------------------------------- */
   public static void main(String[] pArgs) throws Exception
      {
      /*.
      ==========================================================================================
      Greetings !
      ------------------------------------------------------------------------------------------ */
      cOut.banner(CLASS_NAME);
      /*.
      ==========================================================================================
      Create an object and send its output to the ConsoleStream
      ------------------------------------------------------------------------------------------ */
      ZipFile x = new ZipFile("c:/temp/test.zip");
      x.test();
      x.putEntry("tuna.txt","c:/temp/in.txt",1164204583461L);
      x.getEntry("tuna.txt","c:/temp/out.txt");
      }



   }  // class ZipFile



   /*:.
   ==============================================================================================================
   @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@[  Notes  ]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
   ============================================================================================================== */