Unzip file via java.util.zip package from .NET code
This week I had to modify a legacy java application that does quite a bit of unpacking of various zip files. Actually the file extensions are .EAR, .WAR, .JAR; those are all zip files. The target application will run under JRE 1.6.0_12.
I had to find a way to code the logic in .NET, but still I need a number of files unzipped (then modify various manifests and descriptors and zip into .EARs again).
Unfortunately, .NET/C# libraries do not have access to a direct equivalent of java.util.zip.* package. I considered few options, including new deflation namespace in .NET 3.5, a number of zlib-based options, use of J# namespace, and using interop to native code which in turn will JNDI to java.util.zip, purchasing a zip command line license, etc.
Finally, I ended up with the simplest solution that works great for my needs; the needs/constraints being:
- Performance hit of 100 milliseconds per unzip operation is fine
- Need 100% same behavior as the one that Sun’s java has
The solution is:
- Write small console java application, which will implement the needed unzip operations. My small java application takes
o <zip file name> <output directory name> [<zip entry name>]
- Somehow build this application
- Use this application from the .NET code. I set a small function for that.
The following 1) briefly describes the small java application; this one is actually doing the zip/unzip operations, 2) describes how to build it and 3) shows the .NET part of the code.
First, we define needed imports, with the zip-related ones:
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.IOException;
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;
Next, need few variables:
File fileToUnpack = null;
ZipFile zip = null;
String targetDirName = null;
String entryName = null; // will stay null if unzip whole file
Then we retrieve command-line arguments, validate them (like the zip/ear/war/jar file exists), and open the zip container. We are still in Sun’s java world.
try
{
zip = new ZipFile(fileToUnpack);
}
catch (java.util.zip.ZipException e)
log("Cannot open zip file: " + e.toString());
return;
catch (java.io.IOException e)
In case the command line requested unzipping the whole file, the code path is below. Note few things like normalizing the output path…; if you miss this, you end up with the directory which is only partially functional. Also note the handling of the isDirectory case. The “streamToFile” code will follow.
if (entryName == null) // unpack whole file
Enumeration en = zip.entries();
while(en.hasMoreElements())
ZipEntry entry = (ZipEntry)en.nextElement();
if (entry.isDirectory())
log("directory ignored: " +
entry.toString());
continue;
InputStream stm;
try {stm = zip.getInputStream(entry);}
log ("Cannot get input stream for '" + entry.toString() + "' in zip file '" + fileToUnpack.getAbsolutePath());
String targetFilename = targetDirName + "/" + entry.toString();
// create dir if needed
int lastSlash = targetFilename.lastIndexOf("/");
if (lastSlash > 0)
String dirName = targetFilename.substring(0, lastSlash);
new File(dirName).mkdirs();
// Copy zip entry to output directory
targetFilename = targetFilename.replace('/','\\');
streamToFile(stm, targetFilename);
Now the function that actually copied a zip entry to the file.
private static void streamToFile(InputStream stm, String targetFilename)
FileOutputStream stmOut = null;
byte[] buffer = new byte[copyBufferSize];
int bytecount;
try {stmOut = new FileOutputStream(new File(targetFilename));}
log("Error opening output file '" + targetFilename + "': " + e.toString());
System.exit(101);
while ((bytecount = stm.read(buffer)) > 0)
stmOut.write(buffer, 0, bytecount);
stmOut.close();
log("Error writing output file '" + targetFilename + "': " + e.toString());
We are almost done with java part, but not completely. The by far the most difficult part remains, at least for me: building the executable. I chose to package the result into the file that I named “main.jar”.
There is a build tool which is similar to MSBUILD. While I did not find many MSBUILD features in ant (like automatic resolution of dependencies between projects), it’s very likely I don’t know enough about ant. Anyhow, the ant works great for me.
<?xml version="1.0"?>
<project name="ops-tool" default="all">
<target name="set">
<property environment="env"/>
<property name="flavor" value="retail"/>
</target>
<target name="init" depends="set">
<path id="classpath">
</path>
<target name="compile" depends="init">
<mkdir dir="classes"/>
<javac srcdir="." destdir="classes"
classpathref="classpath">
<classpath>
<pathelement location="${classpath}"/>
</classpath>
</javac>
<target name="jar" depends="compile">
<delete dir="lib" quiet="yes"/>
<mkdir dir="lib"/>
<jar
destfile="lib/main.jar"
basedir="classes"
manifest="MANIFEST.MF"
filesonly="yes">
</jar>
<target name="all" depends="jar"/>
<target name="clean" depends="init">
<delete includeEmptyDirs="yes" quiet="yes">
<fileset dir="${lib}"/>
<fileset dir="${classes}"/>
</delete>
</project>
I grabbed few different ZIP, JAR, EAR files and run the program from the command line test script, which I named run.cmd:
m:\jdk1.6.0_12\bin\java -jar lib\main.jar utils.zip.Main %*
For example, I run like:
run unittest3\lib\EXML.jar unittest5
This is by far the easiest of course. The following is simple testing code, with the procedure to actually call the java unzip as a part of it (what I am saying, this is not the final code; this works great).
Declaration is:
namespace J2eePack
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
class Program
private const string jzip = @"…"; // this is the location of lib\main.jar
. . .
Now, let’s call couple of un-zipping functions. Only the bold part is interesting:
private const string testEar = @"I:\lm\service\se\lm8\private\service-next\private\ops\test\JZIP\unittest\deployable.ear";
private const string exmlJar = @"I:\lm\service\se\lm8\private\service-next\private\ops\test\J2eePack\J2eePack\unpack2\lib\EXML.jar";
private const string exmlOut = @"I:\lm\service\se\lm8\private\service-next\private\ops\test\J2eePack\J2eePack\unpack2\lib\EXML";
private const string targetSubDir = "unpack2";
// Target location
string current = Assembly.GetExecutingAssembly().GetModules()[0].FullyQualifiedName;
current = Path.GetDirectoryName(current);
string targetDir = Path.Combine(current, targetSubDir);
// Unpack
Unzip(testEar, targetDir);
Unzip(exmlJar, exmlOut);
// test result
string[] files = Directory.GetFiles(targetDir, "*", SearchOption.AllDirectories);
foreach (string file in files)
Console.WriteLine(file);
The actual code that is doing all the work (by calling the java console program) is:
/// <summary>
/// Build and execute %java_home%\bin\java.exe -jar [loc]\lib\main.jar utils.zip.Main [file to unzip] [targetDir];
/// </summary>
/// <param name="jar">Full or relative path to the file to unzip</param>
/// <param name="targetDir">The directory to which the zip file will be unzipped to. The directory will be created if missing.</param>
/// <returns>true if no erros detected.</returns>
/// <remarks>Note that the example code below allows only 5 seconds for the callee to complete. Modify this to meet the
/// production requirements. Also, note that the below example does not check the exit code of the java proccess.
/// Please add a line when building the production code.
/// </remarks>
private static bool Unzip(string jar, string targetDir)
// java exec
string javaHome = Environment.GetEnvironmentVariable("java_home");
if (javaHome == null)
Console.WriteLine("cannot obtain java_home");
// javaHome = @"M:\jdk1.6.0_12";
Environment.ExitCode = 101;
return false;
string java = Path.Combine(javaHome, @"bin\java.exe");
// arguments
string args = " -jar " + jzip + @"\lib\main.jar utils.zip.Main " + jar + " " + targetDir;
Console.WriteLine(args);
// shell out
Process shell = new Process();
shell.StartInfo.FileName = java;
shell.StartInfo.Arguments = args;
shell.StartInfo.UseShellExecute = false;
shell.Start();
bool ok = shell.WaitForExit(5000);
if (!ok)
Console.WriteLine("Error");
return true;