MokaByte Numero 15 - Gennaio 1998

 
Gestione degli Zip col JDK 1.1 
di
Michele
Sciabarrà
Sulla base di un package contenuto nel JDK 1.1 vediamo come gestire ed anche creare archivi in formato zip con Java



Zip in Java
Il JDK 1.1 comprende un interessante ed utile package: java.util.zip. Come si può immaginare, si tratta proprio delle classi necessarie per la gestione di archivi compressi in formato ZIP standard. Il JDK 1.1 supporta il formato JAR di archiviazione dei programmi in Java, che sono in realtà in formato ZIP. Niente di strano quindi che internamente sia presente il supporto per i file ZIP. Saggiamente quindi JavaSoft ha deciso di rendere disponibili delle classi per l'uso generale del formato zip. Da notare che gli zip non sono interamente gestiti in Java. Infatti tra le DLL del JRE (Java Runtime Environment) esiste una zip.dll che chiaramente implementa in C le chiamate native necessarie per la gestione degli ZIP. Si tratta, con tutta probabilità, di codice derivato dalla versione freeware dello zip nota come Info-Zip.
In realtà il supporto al formato ZIP non è completo. Lo ZIP prevede diversi metodi di compressione, mentre è previsto il solo supporto per la compressione deflating, che ha 9 livelli diversi di compressione, e per lo storing senza compressione. Altresì è previsto il supporto per la pura compressione deflating senza archiviazione ZIP, il formato utilizzato dalla famora utility gzip, che come è noto non è un archiviatore ma solo un compressore.
Si tratta comunque di tutto e solo quello che è necessario in pratica: le altre forme di compressione dello ZIP sono un residuo storico del PKZIP 1.0, mentre il deflating è la compressione introdotta nel PKZIP 2.0 che è la più efficiente in pratica. Più è alto il livello di compressione, più il file finale è piccolo, ma più la compressione è lenta.
In questo articolo metteremo alla prova la gestione degli zip e ne impararemo l'uso, implementando una piccola utility a linea di comando, jzip, che permette di listare, scompattare e creare degli zip.
La sintassi dell'utility (che ricorda quella del comando tar ) è:
 

Il completo sorgente dell'utility è riportato in fondo all'articolo, e comunque mi può essere richiesto inviando una mail a msciab@insinet.it.
Tralasciamo di discutere i dettagli (piuttosto ovvi) relativi alla gestione della riga di comando e concentriamoci nell'analisi dei tre metodi principali della classe jzip.Jzip che sono:
  Listare uno zip
In questo paragrafo faremo riferimento a JZip.list. Per accedere ad un file zip si utilizza la classe ZipFile; uno zip è un archivio, e contiene al suo interno altri file. Ogni file contenuto è rappresentato dalla classe ZipEntry. Un oggetto ZipFile può essere costruito specificando un file per nome (una stringa) o un oggetto di tipo File.
In JZip.list  uno ZipFile viene costruito usando come argomento il nome del file specificato sulla riga di comando. Tramite ZipFile.entries(), ottenieamo una enumerazione delle ZipEntry contenute nel file. La classe ZipEntry fornisce i metodi per estrarre le informazioni sui file contenuti nello zip. Quindi per listare i contenuti la nostra utility deve enumerare le entry; per ciascuna deve leggere le informazioni e stampare il risultato. Ci sono alcuni dettagli a cui dobbiamo stare attenti.
Notiamo innanzitutto l'uso del SimpleDateFormat per stampare la data. Infatti ZipEntry.getSize() fornisce la data in secondi dal primo gennaio 1970, che deve essere convertito in una stringa con l'uso dell'apposito formattatore di date. Un altro trucco è quello utilizzato per allineare a destra un numero, cioè la lunghezza del file in byte: abbiamo aggiunto degli spazi in testa ed estratto la sottostringa di coda. Notiamo infine come, listando i file contenuti in uno zip, i file hanno sempre il pathname separato da slash diritti anche se sono stati originariamente creati in ambiente Windows. Questa caratteristica degli ha delle conseguenza a cui dobbiamo fare attenzione quando tentiamo di scompattare.
Scompattare uno zip
La procedura di scompattamento assomiglia al listing nella parte relativa all'estrazione delle entry. Per ciascuna entry dobbiamo però dearchiviare il file zip anzichè esaminare e stampare la entry. Un fle viene estratto utilizzando un output stream, fornito dalla classe ZipFile specificando la entry. Cioè se zip è uno ZipFile e entry è una ZipEntry corrispondente al file che vogliamo estrarre, otteniamo l'input stream in con:

    InputStream in = zip.getInputStream(entry);

Una volta ottenuto l'input stream, possiamo leggerlo e scriverlo in un file.
Per creare e scrivere un file possiamo utilizzare un FileOutputStream: in teoria basterebbe

però questo codice non funziona per due motivi. Il primo è che gli slash sono sempre diritti, mentre sotto Windows devono essere inversi. Il secondo è che la directory in cui si vuole scrivere il file deve già esistere, cosa che in generale non è vera. Per ovviare a questi problemi, per prima cosa "naturalizziamo" il nome del file, trasformando gli slash nel separatore di file locale (leggibile nella variabile statica File.pathSeparatorChar).
  Fatto questo creiamo la directory dove si trova il file sfruttando File.mkdirs, e ovviamente non creando alcun file se l'entry è relativa ad una directory.
  A questo punto non ci resta che estrarre il file:
  Creare uno zip
La sintassi della nostra utility segue quella del comando tar  di Unix (analogamente al comando jar del JDK); questa sintassi prevede che occorra specificare i file da archiviare sulla riga di comando. A differenza del PKZIP, il nostro JZip non espande le wildcard (cioè *.*). Per archiviazioni di intere directory tuttavia è previsto che, quando si specifica una directory, si intendono tutti i file della directory e delle sottodirectory. Quindi per lavorare agevolmente ci serve una utility che visita l'intero albero di una directory. La funzione ricorsiva JZip.recurse (utile anche in altre occasioni) data una lista di file, espande le sottodirectory e ritorna un array di stringhe contenente tutti i file contenuti in tutte le sottodirectory.Grazie a questa funzione è più semplice scrivere il metodo JZip.zip: dopo aver espanso gli argomenti, dobbiamo "soltanto" archiviare ogni file dell'array ritornato.Dobbiamo creare uno ZipOutputStream, e scrivere ogni file nell'output stream. Prima di scrivere ogni file dobbiamo creare una entry con putNextEntry(ZipEntry). Quindi, se in String[] res abbiamo tutti i file che dobbiamo archiviare, il codice (semplificato) è:
  Notiamo che non vengono scritte le entry relative alle directory. Infatti la classe ZipEntry stranamente non prevede un metodo setDirectory, senza il quale non è possibile specificare una entry relativa alla directory. Non so se ci sia un altro modo per specificare che si vuole una entry directory, ma credo di no.
La classe jzip.JZip
Segue il codice di jzip.JZip, utilizzabile sia come utility a riga di comando che come classe utile in pratica per gestire in modo comodo i file zip
 

// Class:       jzip.JZip

// Description: zip and unzip files given filenames

// Copyright:   © 1997 Studio Satori

// Author:      Michele Sciabarrà

package jzip;import java.util.*;

import java.util.zip.*;

import java.text.*;

import java.io.*;
 
 

public class JZip

{

  static public

  void main(String[] args)

  throws java.io.IOException

  {

         if(args.length<2)

            usage();

         else if(args[0].startsWith("-t"))

              list(args[1]);

         else if(args[0].startsWith("-x"))

              unzip(args[1]);

         else if(args[0].startsWith("-c")) {

              if(args.length < 3)

                 usage();

              else {

                   String[] files = new String[args.length-2];

                   System.arraycopy(args, 2, files, 0, files.length);

                   zip(args[1], files);

              }

         } else

           usage();

  }
 
 

  private static void usage()

  {

           System.out.println("usage:");

           System.out.println("\tJZip -t <filename.zip>");

           System.out.println("\t\tshow zip contents");

           System.out.println("\tJZip -x <filename.zip>");

           System.out.println("\t\textract files");

           System.out.println("\tJZip -c <filename.zip> <files>..");

           System.out.println("\t\tcreate a zip with files");

  }
 
 

  private static

  void list(String filename)

  throws IOException

  {

    ZipFile zip = new ZipFile(filename);

    SimpleDateFormat fmt

        = new SimpleDateFormat("yyyy.mm.dd hh:mm:ss");

    Enumeration e = zip.entries();

    System.out.println("      Size |         Date        | Name");

    System.out.println("-----------+---------------------+--------------------------");

    while(e.hasMoreElements()) {

      ZipEntry entry = (ZipEntry)e.nextElement();

      String sz = "         "+entry.getSize();

      sz = sz.substring(sz.length() - 10);

      String tm = fmt.format(new Date(entry.getTime()));

      System.out.println(sz+" | "+tm+" | "+entry.getName());

    }

  }
 
 

  private static

  void unzip(String filename)

  throws IOException

  {

    final char sep = File.separatorChar;

    byte[] buff = new byte[1024];

    int m, n;

    // for each file in zip

    ZipFile zip = new ZipFile(filename);

    Enumeration e = zip.entries();

    while(e.hasMoreElements())

    {

      // get filename using local separator

      ZipEntry entry = (ZipEntry)e.nextElement();

      StringBuffer fixed = new StringBuffer(entry.getName());

      for( int i = 0; iif( fixed.charAt(i) == '/')

               fixed.setCharAt(i, sep);

      File file = new File(fixed.toString());
 
 

      // create dir

      if(entry.isDirectory()) {

        file.mkdirs();

        continue;

      }

      String dir = file.getParent();

      if( dir != null)

          new File(dir).mkdirs();
 
 

      // unzip file

      System.out.println("unzipping: "+file);

      OutputStream out = new FileOutputStream(file);

      InputStream in = zip.getInputStream(entry);

      while( (n = in.read(buff, 0, buff.length))!= -1)

               out.write(buff, 0 , n);

    }

  }
 
 

  private static

  String[] recurse(String[] files, String dir)

  {

   final char sep = File.separatorChar;

   Vector v = new Vector();

   File file;

   for(int i=0; i<files.length; ++i)

   {

    String filename = ( dir == null ? files[i]: dir+sep+files[i] );

    file = new File( filename );

    v.addElement(filename);

    if( file.isDirectory()){

        String[] res = recurse(file.list(), filename);

        for( int j = 0; j<res.length; ++j)

             v.addElement(res[j]);

    }

   }

    String[] res = new String[v.size()];

    v.copyInto(res);

    return res;

  }

  private static

  void zip(String filename, String[] files)

  throws IOException

  {

   InputStream in;

   int n;
 
 

   // check

   File zip = new File(filename);

   if(zip.exists()) {

      System.out.println("File "+filename+" already exist");

      System.exit(1);

   }

   // open out file and write input files

   String[] res = recurse(files, null);

   ZipOutputStream out = new ZipOutputStream(new FileOutputStream(zip));

   byte[] buff = new byte[1024];

   for( int i = 0; i<res.length; ++i) {

        File current = new File(res[i]);

        if( current.isDirectory())

            continue;

        System.out.println("zipping: "+current);

        out.putNextEntry(new ZipEntry(res[i]));

        in = new FileInputStream(current);

        while( (n = in.read(buff, 0, buff.length))!= -1)

               out.write(buff, 0 , n);

        in.close();

   }

   out.close();

  }

}


 
 
 
 


MokaByte Web  1998 - www.mokabyte.it

MokaByte ricerca nuovi collaboratori. 
Chi volesse mettersi in contatto con noi può farlo scrivendo a mokainfo@mokabyte.it