Tuesday Jun 18, 2013

Integrating ZIP Features in a NetBeans Platform Application

Let's say your requirement is that you need to open a ZIP file into an application, modify its content, and then ZIP it all up again.

And you're using the NetBeans Platform. Here's the general approach to take:

  1. Learn from Similar Code. The very first thing to do is to ask yourself: "Does NetBeans IDE or any other NetBeans Platform application do something similar?" The answer in this case is Yes because NetBeans IDE has "File | Import Project | From ZIP". So you can look in the NetBeans source code and get some nice code relating to java.util.zip.ZipInputStream, while FileUtil.copy is also great to use.

  2. Create a NetBeans Platform Application. When you create the application, go to the "ide" cluster in the Project Properties dialog of the application and include the "Image" module, as well as the "XML Text Editor" module, together with all the related modules (simply click the Resolve button) and then you'll have some cool editors immediately available when you open the content of the ZIP file in the application. Also include "User Utilities" so that you have "File | Open File" in your application.

  3. Use CentralLookup. We're going to end up creating a Node hierarchy, i.e., we'll be using classes such as Node, ChildFactory, BeanTreeView, and ExplorerManager. These integrate very well with the Lookup concept. That is, when objects of interest (e.g., a folder that contains the newly unzipped ZIP file) are registered in the Lookup, you will create new Nodes in the Node hierarchy.

    But which Lookup to use? Lookup.getDefault? No. That gives you access to META-INF/services, which is the standard Java 6 extension mechanism. Utilities.actionsGlobalContext? No. That gives you access to the currently selected object. We don't care about selection in this scenario. Whenever a ZIP file is unzipped into a new folder, we need to add that folder to some kind of observable list, i.e., Lookup, to which our Node hierarchy is listening. Therefore, copy CentralLookup into your application and use that. Here it is.

  4. Recognize ZIP Files. Create a new MIME type (i.e., use the New File Type wizard) to recognize files with a ZIP extension as "application/x-zip", remove all the editor related code in the DataObject (because you don't want to open the ZIP file into an editor, instead, you want to open the files within the ZIP file into an editor), and implement Openable.

    When the OpenAction is invoked, which happens automatically when you try to open a file via "File | Open File" (or from the Favorites window), your implementation of Openable will be called. In that Openable implementation, unzip the ZIP file into a new folder and then register that folder as a FileObject in the CentralLookup.

  5. Create a Node Hierarchy. Create a TopComponent in "explorer" mode. Create a ChildFactory that implements LookupListener, listening to CentralLookup for new FileObjects. When a new FileObject is present, wrap it in a FilterNode and refresh the Node hierarchy.

  6. Listen for Changes in the Folder. Use FileObject.addRecursiveListener() to listen for additions/deletions/changes in the folder. That gives you a way to mark the Node hierarchy as being "dirty", in some way, e.g., publish a Savable into the Lookup of the TopComponent or the Node and then the Save action will become enabled to save the Node hierarchy back into a ZIP file. And how to implement that? Well, look at the source code for "File | Export Project | To ZIP" in the NetBeans sources.

I've completed the first 5 steps above (and everything in step 6 is done, too, i.e., the Savable is added to the Node Lookup when appropriate and, when invoked, an "Export to ZIP" dialog is shown, which works as one might expect) and checked in the sources here:


Here's what the result looks like after opening a ZIP file, via "File | Open File", e.g., the images that you see will open into the NetBeans image editor automatically when they are double-clicked, because FilterNode is used to wrap the unzipped folder and its children:

Just for fun, I made the feature modular, i.e., the infrastructure for recognizing and parsing ZIP files is in one module, the explorer view is in another module, and a Utils module is included to provide the CentralLookup, which is the communication mechanism between the two modules:

Above you see everything I did to make this work, not very much when you consider the amount of features provided at the end, e.g., an application frame, window system, modular infrastructure, plugin system, menubar, toolbar, image editor, XML editor, ZIP file recognition, ZIP file parsing, rendering of ZIP file content in a Swing JTree (via ExplorerManager and BeanTreeView):

Finally, note that what you see above in the "ZIP Window" are not actually ZIP files. They're FilterNodes on top of folders created from ZIP files, with the display name of the FilterNode (and all its content, i.e., its children) derived from the original node, which is the folder created from unzipping the ZIP file. 


Geertjan Wielenga (@geertjanw) is a Principal Product Manager in the Oracle Developer Tools group living & working in Amsterdam. He is a Java technology enthusiast, evangelist, trainer, speaker, and writer. He blogs here daily.

The focus of this blog is mostly on NetBeans (a development tool primarily for Java programmers), with an occasional reference to NetBeans, and sometimes diverging to topics relating to NetBeans. And then there are days when NetBeans is mentioned, just for a change.


« June 2013 »