Introduction
Recently I had to build a private online photo album application. I planned to provide the users option to upload images as a zip file. I needed this option to be implemented in ASP.NET 1.1, as my hosting service provider didn't have .NET 2.0 support. An online search lead me to a blog with this wonderful idea of using the zip option in J# and it really attracted me. I wanted to try it out and the results were pretty cool. Even though this sample application has been written in .NET 2.0, the .NET Framework 2.0 has libraries to work with the compressing and uncompressing offiles. I will soon come up with an article to illustrate the use of .NET 2.0 libraries with compressing and uncompressing functionalities.
In this article I will explain the usage of zip functionality in J# from C# code. The code in this application has been designed to reuse in a copy/paste fashion and not as a library.
Background
This application consumes J# classes internally. For this we must first refer to the J#.NET library. Physically it resides as a file named vjslib.dll. If you are not very sure how to refer to a library in your project please follow the below steps:
Right click your project in Server Explorer and click on "Add Reference" -> Select the .Net tab -> Scroll down and select "vjslib" -> Click OK and you are there. Now you can refer the Java library classes within your application.
This was the first time trying to refer to the J# classes and it was a moment in my programming life that I will never forget.
Import the following namespaces for ease of coding.
java.util; java.util.zip; java.io;
java.util.zip
namespace contains the classes and methods to implement the compress and uncompress functionalities within our code. The main classes used from the above namespaces are:
- ZipFile
- ZipEntry
- ZipOutputSteam
- Enumeration
Programmatically, a ZipFile
object can be considered equivalent to a physical Zip file. A ZipFile
can contain multiple ZipEntry
objects apart from the actual content of the zipped files. In fact, each ZipEntry
object is the metadata about a zip file. The ZipOutputStream
class represents a writable stream pointing to a zip file. This stream can be used to write ZipEntry
objects and content to the zip file. Enumeration
enables iteration through each element in a collection.
Using the code
Below is a code listing how to create a zip file.
private void Zip(string zipFileName, string[] sourceFile) { FileOutputStream filOpStrm = new FileOutputStream(zipFileName); ZipOutputStream zipOpStrm = new ZipOutputStream(filOpStrm); FileInputStream filIpStrm = null; foreach(string strFilName in sourceFile) { filIpStrm = new FileInputStream(strFilName); ZipEntry ze = new ZipEntry(Path.GetFileName(strFilName)); zipOpStrm.putNextEntry(ze); sbyte[] buffer = new sbyte[1024]; int len = 0; while ((len = filIpStrm.read(buffer)) > = 0) { zipOpStrm.write(buffer, 0, len); } } zipOpStrm.closeEntry(); filIpStrm.close(); zipOpStrm.close(); filOpStrm.close(); }
The above Zip()
method accepts two parameters,
zipFileName
- Zip file name including the path andsourceFile
- string array of file names that are to be zipped.
The FileOutputStream
class is capable of writing content to a file. Its constructor accepts the path of the file to which we wish to write. FileOutputStream
object is then supplied to an instance of ZipOutputStream
class as parameter. The ZipOutputStream
class represents a writable stream to a zip file.
The foreach
loops through each file to be zipped, creates corresponding zip entries and adds it to the final zip file.
Taking a deeper look into the code, a FileInputStream
object is created for each file to be zipped.FileInputStream
object is capable of reading from a file as a stream. Then a ZipEntry
object is created for each file to be zipped. The constructor of the ZipEntry
class accepts name of the file. Path.GetFileName()
returns the file name and extension of the specified path string.
The newly created ZipEntry
object is added to the ZipOutputStream
object using its putNextEntry()
method. In fact, a ZipEntry
merely represents metadata of file entry. You still need to add actual contents into the zip file. Therefore, you need to transfer data from source FileInputStream
to destination FileOutputStream
. This is exactly what the while
loop does in the above piece of code. It reads content from the source file and writes it into the output zip file. Finally, the closeEntry()
method of ZipOutputStream
class is called and this caused the physical creation of the zip file. All the other streams created are also closed.
private void Extract(string zipFileName, string destinationPath) { ZipFile zipfile = new ZipFile(zipFileName); List zipFiles = GetZippedFiles(zipfile); foreach (ZipEntry zipFile in zipFiles) { if (!zipFile.isDirectory()) { InputStream s = zipfile.getInputStream(zipFile); try { Directory.CreateDirectory(destinationPath + "\\" + Path.GetDirectoryName(zipFile.getName())); FileOutputStream dest = new FileOutputStream(Path.Combine(destinationPath + "\\" + Path.GetDirectoryName(zipFile.getName()), Path.GetFileName(zipFile.getName()))); try { int len = 0; sbyte[] buffer = new sbyte[7168]; while ((len = s.read(buffer)) > = 0) { dest.write(buffer, 0, len); } } finally { dest.close(); } } finally { s.close(); } } } }
The ExtractZipFile()
method accepts two parameters: the zip filename (including path) to be extracted, and destination path where the files are to be extracted. It then creates a ZipFile
object and retrieves entries in the ZIP file using the GetZipFiles()
method. This method will be discussed afterwards in this article. The for each
loop iterates through all the entries in the zip file and in each iteration the entry is extracted to the specified folder. The code in the for each
loop executes only if the entry is not a folder. This condition is verified using theisDirectory()
method of ZipEntry
object. Each entry is read into an InputStream
using getInputStream()
method of ZipFile
object. This InputStream
acts as the source stream. The destination stream is aFileOutputStream
object which is created based on the specified destination folder. Here we use the getName()
method of ZipEntry
object to get the file name(including path) of the entry.
During the extraction, the original folder structure is maintained. In the while
loop that follows, content from the source InputStream
is written to the destination FileOutputStream
. The source stream is read to a buffer using the read()
method. It reads 7 KB in a sequence into a temporary buffer and the write(
) method of destinationFileOutputStream
writes the content to the stream from the buffer, using the write method. It is in the final block that follows, the destination FileOutputStream
is closed and the content is physically written to disk.
private List GetZipFiles(ZipFile zipfil) { List lstZip = new List(); Enumeration zipEnum = zipfil.entries(); while(zipEnum.hasMoreElements()) { ZipEntry zip = (ZipEntry)zipEnum.nextElement(); lstZip.Add(zip); } return lstZip; }
The GetZipFiles()
method returns a generic List
of ZipEntry
objects taking a ZipFile
object as argument. The method creates a generic collection of ZipEntry
type. Now comes the use of an interesting feature in the Java language, the use of Enumeration
. Note that its not the enum
type that we have in C#. An object that implements the Enumeration
interface generates a series of elements, one at a time. Successive calls to the nextElement()
method return successive elements of the series. The hasMoreElements()
method returns a boolean
value indicating if the Enumerator
contains more elements. Here, the entries(
) method of ZipFile
class returns anEnumeration
of ZipEntry
objects. The code then iterates through the Enumeration
and populates the List. Finally, the populated List is returned.
Apart from the above listed code, the downloadable source code for the sample application, contains some extracode to handle the UI part of the application, i.e., entries made to the ListBox and handling the progress bar. I haven't included them in this article because I didn't want to lose focus from the main objective of the article. Thecode is comprehensive, but the UI controls can be handled in better ways, keeping performance and usability in mind. One good option might be to keep our zip functionality separate from the UI thread, so that interactivity is well maintained.
Points of Interest
One interesting fact we note while using the Java library is the difference in naming conventions, especially the naming of methods. For C# coders, methods give the feel of variable names. Also, the way the classes are named is a distinguishable factor.
Even though we can't use the features of Java beyond a point because runtime decides the main advantages of a platform; we would take advantage of using the Java libraries. This gives an upper hand of Java over C#.
I hope Microsoft continues to support J#.
No comments:
Post a Comment