Manipulating Zip Files with PeopleCode

I've seen a few forum posts that show how to zip files using both Exec and the XML Publisher PSXP_RPTDEFNMANAGER:Utility app package. Those are great options, but might not fit every scenario. Since the Java API includes support for zip files, let's investigate how we can use it to create or extract zip files.

Java allows developers to create zip files by writing data to a ZipOutputStream. We've used OutputStreams a few times on this blog to write data to files. A ZipOutputStream is just a wrapper around an OutputStream that writes contents in the zip file format. Here is an example of reading a text file and writing it out to a ZipOutputStream

REM"syntax-COMMENT2"> **"syntax-COMMENT2"> The"syntax-COMMENT2"> file"syntax-COMMENT2"> I"syntax-COMMENT2"> want"syntax-COMMENT2"> to"syntax-COMMENT2"> compress"syntax-COMMENT2">;
Local "syntax-KEYWORD3">string &fileNameToZip "syntax-OPERATOR">= ""syntax-LITERAL1">c:\temp\blah.txt";

REM"syntax-COMMENT2"> **"syntax-COMMENT2"> The"syntax-COMMENT2"> internal"syntax-COMMENT2"> zip"syntax-COMMENT2"> file's"syntax-COMMENT2"> structure"syntax-COMMENT2"> --"syntax-COMMENT2"> internal"syntax-COMMENT2"> location"syntax-COMMENT2"> of"syntax-COMMENT2"> blah.txt"syntax-COMMENT2">;
Local "syntax-KEYWORD3">string &zipInternalPath "syntax-OPERATOR">= ""syntax-LITERAL1">my/internal/zip/folder/structure"syntax-LITERAL1">";

Local "syntax-KEYWORD3">JavaObject &zip "syntax-OPERATOR">= "syntax-KEYWORD2">CreateJavaObject("syntax-LITERAL1">""syntax-LITERAL1">java.util.zip.ZipOutputStream"syntax-LITERAL1">", "syntax-KEYWORD2">CreateJavaObject("syntax-LITERAL1">""syntax-LITERAL1">java.io.FileOutputStream"syntax-LITERAL1">", ""syntax-LITERAL1">c:\temp\compressed.zip"syntax-LITERAL1">", True));

Local "syntax-KEYWORD3">JavaObject &file "syntax-OPERATOR">= "syntax-KEYWORD2">CreateJavaObject("syntax-LITERAL1">"java.io.File"syntax-LITERAL1">", &fileNameToZip);
REM"syntax-COMMENT2"> **"syntax-COMMENT2"> We"syntax-COMMENT2"> will"syntax-COMMENT2"> read"syntax-COMMENT2"> "syntax-COMMENT2">&fileNameToZip"syntax-COMMENT2"> into"syntax-COMMENT2"> a"syntax-COMMENT2"> buffer"syntax-COMMENT2"> and"syntax-COMMENT2"> write"syntax-COMMENT2"> it"syntax-COMMENT2"> out"syntax-COMMENT2"> to"syntax-COMMENT2"> &zip"syntax-COMMENT2">;
Local "syntax-KEYWORD3">JavaObject &buf "syntax-OPERATOR">= "syntax-KEYWORD2">CreateJavaArray("syntax-LITERAL1">"byte[]"syntax-LITERAL1">", 1024);

Local "syntax-KEYWORD3">number &byteCount;
Local "syntax-KEYWORD3">JavaObject &in "syntax-OPERATOR">= "syntax-KEYWORD2">CreateJavaObject("syntax-LITERAL1">""syntax-LITERAL1">java.io.FileInputStream"syntax-LITERAL1">", &fileNameToZip);

Local "syntax-KEYWORD3">JavaObject &zipEntry "syntax-OPERATOR">= "syntax-KEYWORD2">CreateJavaObject("syntax-LITERAL1">""syntax-LITERAL1">java.util.zip.ZipEntry"syntax-LITERAL1">", &zipInternalPath "syntax-OPERATOR">| ""syntax-LITERAL1">/" "syntax-OPERATOR">| &file.getName());

REM"syntax-COMMENT2"> **"syntax-COMMENT2"> Make"syntax-COMMENT2"> sure"syntax-COMMENT2"> zip"syntax-COMMENT2"> entry"syntax-COMMENT2"> retains"syntax-COMMENT2"> original"syntax-COMMENT2"> modified"syntax-COMMENT2"> date"syntax-COMMENT2">;
&zipEntry.setTime(&file.lastModified());

&zip.putNextEntry(&zipEntry);

&byteCount = &in.read(&buf);

While &byteCount > "syntax-DIGIT">0
&zip.write(&buf, 0, &byteCount);
&byteCount = &in.read(&buf);
End-While;

&in.close();
&zip.flush();
&zip.close();

To add multiple files to a single zip file, we can convert the above code into a function (preferably a FUNCLIB function) and then call it multiple times, once for each file:

"syntax-KEYWORD1">Function AddFileToZip(&zipInternalPath, &fileNameToZip, &zip)
Local "syntax-KEYWORD3">JavaObject &file "syntax-OPERATOR">= "syntax-KEYWORD2">CreateJavaObject("syntax-LITERAL1">"java.io.File"syntax-LITERAL1">", &fileNameToZip);
REM"syntax-COMMENT2"> **"syntax-COMMENT2"> We"syntax-COMMENT2"> will"syntax-COMMENT2"> read"syntax-COMMENT2"> "syntax-COMMENT2">&fileNameToZip"syntax-COMMENT2"> into"syntax-COMMENT2"> a"syntax-COMMENT2"> buffer"syntax-COMMENT2"> and"syntax-COMMENT2"> write"syntax-COMMENT2"> it"syntax-COMMENT2"> out"syntax-COMMENT2"> to"syntax-COMMENT2"> &zip"syntax-COMMENT2">;
Local "syntax-KEYWORD3">JavaObject &buf "syntax-OPERATOR">= "syntax-KEYWORD2">CreateJavaArray("syntax-LITERAL1">"byte[]"syntax-LITERAL1">", 1024);

Local "syntax-KEYWORD3">number &byteCount;
Local "syntax-KEYWORD3">JavaObject &in "syntax-OPERATOR">= "syntax-KEYWORD2">CreateJavaObject("syntax-LITERAL1">""syntax-LITERAL1">java.io.FileInputStream"syntax-LITERAL1">", &fileNameToZip);

Local "syntax-KEYWORD3">JavaObject &zipEntry "syntax-OPERATOR">= "syntax-KEYWORD2">CreateJavaObject("syntax-LITERAL1">""syntax-LITERAL1">java.util.zip.ZipEntry"syntax-LITERAL1">", &zipInternalPath "syntax-OPERATOR">| ""syntax-LITERAL1">/" "syntax-OPERATOR">| &file.getName());

REM"syntax-COMMENT2"> **"syntax-COMMENT2"> Make"syntax-COMMENT2"> sure"syntax-COMMENT2"> zip"syntax-COMMENT2"> entry"syntax-COMMENT2"> retains"syntax-COMMENT2"> original"syntax-COMMENT2"> modified"syntax-COMMENT2"> date"syntax-COMMENT2">;
&zipEntry.setTime(&file.lastModified());

&zip.putNextEntry(&zipEntry);

&byteCount = &in.read(&buf);

While &byteCount > "syntax-DIGIT">0
&zip.write(&buf, 0, &byteCount);
&byteCount = &in.read(&buf);
End-While;

&in.close();
End-Function;


Local "syntax-KEYWORD3">JavaObject &zip "syntax-OPERATOR">= "syntax-KEYWORD2">CreateJavaObject("syntax-LITERAL1">""syntax-LITERAL1">java.util.zip.ZipOutputStream"syntax-LITERAL1">", "syntax-KEYWORD2">CreateJavaObject("syntax-LITERAL1">""syntax-LITERAL1">java.io.FileOutputStream"syntax-LITERAL1">", ""syntax-LITERAL1">c:\temp\compressed.zip"syntax-LITERAL1">", True));

AddFileToZip(""syntax-LITERAL1">folder1", "syntax-LITERAL1">""syntax-LITERAL1">c:\temp\file1.txt"syntax-LITERAL1">", &zip);
AddFileToZip(""syntax-LITERAL1">folder1", "syntax-LITERAL1">""syntax-LITERAL1">c:\temp\file2.txt"syntax-LITERAL1">", &zip);
AddFileToZip(""syntax-LITERAL1">folder2", "syntax-LITERAL1">""syntax-LITERAL1">c:\temp\file1.txt"syntax-LITERAL1">", &zip);
AddFileToZip(""syntax-LITERAL1">folder2", "syntax-LITERAL1">""syntax-LITERAL1">c:\temp\file2.txt"syntax-LITERAL1">", &zip);

&zip.flush();
&zip.close();

The contents to zip doesn't have to come from a static file in your file system. It could come from the database or... well, anywhere. Here is an example of zipping static text. In this example I intentionally left the internal zip file path (folder) blank to show how to create a zip file with no structure.

Local "syntax-KEYWORD3">JavaObject &textToCompress "syntax-OPERATOR">= "syntax-KEYWORD2">CreateJavaObject("syntax-LITERAL1">""syntax-LITERAL1">java.lang.String"syntax-LITERAL1">", ""syntax-LITERAL1">This "syntax-LITERAL1">is "syntax-LITERAL1">some "syntax-LITERAL1">text "syntax-LITERAL1">to "syntax-LITERAL1">compress... "syntax-LITERAL1">probably "syntax-LITERAL1">a "syntax-LITERAL1">bloated "syntax-LITERAL1">XML "syntax-LITERAL1">document "syntax-LITERAL1">or "syntax-LITERAL1">something "syntax-LITERAL1">;)");
Local "syntax-KEYWORD3">string &zipInternalFileName "syntax-OPERATOR">= ""syntax-LITERAL1">contents.txt";

Local "syntax-KEYWORD3">JavaObject &zip "syntax-OPERATOR">= "syntax-KEYWORD2">CreateJavaObject("syntax-LITERAL1">""syntax-LITERAL1">java.util.zip.ZipOutputStream"syntax-LITERAL1">", "syntax-KEYWORD2">CreateJavaObject("syntax-LITERAL1">""syntax-LITERAL1">java.io.FileOutputStream"syntax-LITERAL1">", ""syntax-LITERAL1">c:\temp\compressed.zip"syntax-LITERAL1">", True));
Local "syntax-KEYWORD3">JavaObject &zipEntry "syntax-OPERATOR">= "syntax-KEYWORD2">CreateJavaObject("syntax-LITERAL1">""syntax-LITERAL1">java.util.zip.ZipEntry"syntax-LITERAL1">", &zipInternalFileName);
Local "syntax-KEYWORD3">JavaObject &buf "syntax-OPERATOR">= &textToCompress.getBytes();
Local "syntax-KEYWORD3">number &byteCount "syntax-OPERATOR">= &buf.length;

&zip.putNextEntry(&zipEntry);

&zip.write(&buf, 0, &byteCount);

&zip.flush();
&zip.close();

And, finally, unzipping files. The following example prints the text inside each file from a zip file named "compressed.zip" that contains four fictitious text files named file1.txt, file2.txt, file3.txt, and file4.txt.

Local "syntax-KEYWORD3">JavaObject &zipFileInputStream "syntax-OPERATOR">= "syntax-KEYWORD2">CreateJavaObject("syntax-LITERAL1">""syntax-LITERAL1">java.io.FileInputStream"syntax-LITERAL1">", ""syntax-LITERAL1">c:\temp\compressed.zip");
Local "syntax-KEYWORD3">JavaObject &zipInputStream "syntax-OPERATOR">= "syntax-KEYWORD2">CreateJavaObject("syntax-LITERAL1">""syntax-LITERAL1">java.util.zip.ZipInputStream"syntax-LITERAL1">", &zipFileInputStream);
Local "syntax-KEYWORD3">JavaObject &zipEntry "syntax-OPERATOR">= &zipInputStream.getNextEntry();
Local "syntax-KEYWORD3">JavaObject &buf "syntax-OPERATOR">= "syntax-KEYWORD2">CreateJavaArray("syntax-LITERAL1">"byte[]"syntax-LITERAL1">", 1024);
Local "syntax-KEYWORD3">number &byteCount;

While &zipEntry <> Null

If (&zipEntry.isDirectory()) "syntax-KEYWORD1">Then
REM"syntax-COMMENT2"> **"syntax-COMMENT2"> do"syntax-COMMENT2"> nothing"syntax-COMMENT2">;
Else
Local "syntax-KEYWORD3">JavaObject &out "syntax-OPERATOR">= "syntax-KEYWORD2">CreateJavaObject("syntax-LITERAL1">""syntax-LITERAL1">java.io.ByteArrayOutputStream"syntax-LITERAL1">");
&byteCount "syntax-OPERATOR">= &zipInputStream.read(&buf);

While &byteCount > "syntax-DIGIT">0
&out.write(&buf, 0, &byteCount);
&byteCount "syntax-OPERATOR">= &zipInputStream.read(&buf);
End-While;

&zipInputStream.closeEntry();
MessageBox("syntax-DIGIT">0, ""syntax-LITERAL1">", 0, "syntax-DIGIT">0, &out.toString());
/*Else
"syntax-COMMENT1"> "syntax-COMMENT1"> "syntax-COMMENT1"> "syntax-COMMENT1"> "syntax-COMMENT1">&log.writeline("&zipEntry"syntax-COMMENT1"> is"syntax-COMMENT1"> a"syntax-COMMENT1"> directory"syntax-COMMENT1"> named"syntax-COMMENT1"> ""syntax-COMMENT1"> |"syntax-COMMENT1"> "syntax-COMMENT1">&zipEntry.getName);*/
End-If;

&zipEntry "syntax-OPERATOR">= &zipInputStream.getNextEntry();
End-While;

&zipInputStream.close();
&zipFileInputStream.close();

What about unzipping binary files into the file system? I'll let you write that one.

Password protected zip files? Java doesn't make this easy. There are a few Java libraries, but as Chris Rigsby points out here, using non-standard Java classes (including your own) can be hazardous. At this time, it seems the best way to password protect a zip file is to use Exec to call a command line zip program. On Linux with the zip utility, use the -P parameter to encrypt with a password.