Sunday, January 23, 2011

SlickEdit 2011 Wish List

Well, we're nearly through January 2011, and the SlickEdit 2011 beta is due any day now.

I've been using SlickEdit since 1996, and I still get kind of excited around this time of year when a new version comes out. Sometimes the team surprises me.

Here is my wish list for features in SlickEdit 2011:


The first three are hot JVM languages that I'd love to see support for. I don't really expect to see them, but you never know. Last year, SlickEdit added support for Erlang, Haskell and F#, so they aren't completely in the dark about hot languages.

The next three items are popular distributed version control systems. Again, I don't really expect much from SlickEdit on that, yet. Forum posts on the topic have met with disappointing reponses from SlickEdit staff -- doesn't look like they "clicked" on DVCS yet. It took them an awful long time to move on from CVS to Subversion themselves, and even now the Subversion support is miserable compared to any other tool I've used. And so, my wish, modernise the Subversion support.

SlickEdit has a lot of advanced features for C/C++ programmers. C/C++ programmers probably make up a very large chunk, if not the majority, of SlickEdit users. And as far as I can tell, SlickEdit is actually one of the best "IDE"s available for C/C++. I don't do C/C++ any more myself though, I do JVM-based languages mostly. And with Java, SlickEdit also tries to be an uber-IDE, with Project Types, JUnit, Ant support and more. But here it falls far, far short of industry standards. Java programmers are really spoiled by the superb IDEs aavailable for them, and two of the best ones are even free. Anyway, I don't wish for SlickEdit to improve its Java IDE features. I'm happy to use a Java IDE for that kind of work. The point I'd like to make is that supporting current VCS systems, and supporting them really well, would benefit all SlickEdit users. I can't imagine many SlickEdit users are not using VCS, and many of them probably use a modern VCS, such as Git. It's really about time SlickEdit caught up with the VCS game.

For a couple of examples of excellent VCS integration, look at:


SlickEdit 2011 included some rather dubious new features. My favorite "non useful feature" was Subword Navigation. You can move the cursor through camel-cased words such as AbstractBeanFactory. But when would anyone want to do that? Far more useful would be file or class completion/loading using smart "camel typing", as introduced by IntelliJ IDEA and copied by other tools. With IDEA, I can press Ctrl+N to open a class, then type "ABF" or "AbBeFa" to open the AbstractBeanFactory class. This is really useful, and would be something SlickEdit could really benefit from.

Anyway, I'm sure SlickEdit 2011 will contain a few pleasant surprises, as well as a few new annoying bugs. As always, it will be interesting to figure whether the feature-to-bug ratio improves, or not. I'm looking forward to the beta.

Friday, January 7, 2011

Groovy DSL/Builders: ZIP Output Streams

Let's follow up last week's post with another example of a very similar, very simple builder.

This one is for outputting ZIPped data to a stream. Let's take the standard example of using Java's ZIP support to zip up a folder of files.

Because the JDK does not include methods to traverse the filesystem, we need to define a method to be called recursively for subdirectories:


private void zipDirectory(File dir, ZipOutputStream zos) throws IOException {
for (File file : dir.listFiles()) {
if (file.isDirectory()) {
zipDirectory(file, zos);
}
else {
ZipEntry entry = new ZipEntry(file.getPath());
entry.setSize(file.length());
entry.setTime(file.lastModified());
zos.putNextEntry(entry);
IOUtils.copy(new FileInputStream(file), zos);
}
}
}


We cheated a little here by using the Apache Commons IO IOUtils class to actually copy the file bytes to the ZIP file. Also, we don't do anything here with IOExceptions.

With this method in place, we can create a ZIP file from a folder using:


ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFile));
zipDirectory(new File(dir), zos);
zos.close();


Groovy's JDK IO extensions, and filesystem traversal methods, make this job quite a bit easier. Here's the Groovy code to do the same thing:


new ZipOutputStream(new FileOutputStream(zipFile)).withStream {zos ->
new File(dir).traverse(type: FileType.FILES) {File file ->
def entry = new ZipEntry(file.path)
entry.size = file.length()
entry.time = file.lastModified()
zos.putNextEntry(entry)
zos << file.bytes
}


This code is still a bit awkward in how it interacts with Java's ZIP API, in particular the creation of the ZipEntry object.

Using a simple builder, we can rewrite this as follows:


new ZipBuilder(new FileOutputStream(zipFile)).zip {
new File(dir).traverse(type: FileType.FILES) {File file ->
entry(file.path, size: file.length(), time: file.lastModified()) {it << file.bytes}
}
}

The ZipBuilder provides two methods:

  • zip(): creates and manages the ZipOutputStream

  • entry() (nested): creates and adds a ZipEntry to the enclosing zip stream


As with other builders, this builder promotes readable code that reflects the structure of the object to be created.

Here's the code for the builder itself:


class ZipBuilder {

@InheritConstructors
static class NonClosingOutputStream extends FilterOutputStream {
void close() {
// do nothing
}
}

ZipOutputStream zos

ZipBuilder(OutputStream os) {
zos = new ZipOutputStream(os)
}

void zip(Closure closure) {
closure.delegate = this
closure.call()
zos.close()
}

void entry(Map props, String name, Closure closure) {
def entry = new ZipEntry(name)
props.each {k, v -> entry[k] = v}
zos.putNextEntry(entry)
NonClosingOutputStream ncos = new NonClosingOutputStream(zos)
closure.call(ncos)
}

void entry(String name, Closure closure) {
entry([:], name, closure)
}
}


This builder uses the same style with Closures as the HSSFWorkbookBuilder described earlier.

There are a few other Groovy (and Java) features to note:

  • Java's ZIP library requires clients to write to the ZipOutputStream for each entry created. We need to make sure that no entry closes the ZipOutputStream -- it must be closed only when the zip stream is finished. (Many of Groovy's output methods close streams automatically.) For this reason, we wrap the output stream in a NonClosingOutputStream before passing it to an entry. This class is simply defined as a FilterOutputStream (OutputStream decorator) with a no-op close() method.

  • We use Groovy's @InheritConstructors to save repeating the trivial constructor.

  • The entry() method creates a new ZipEntry with its mandatory name property. It then populates additional optional properties from a Map, using Groovy's support for setting Java Beans properties as Map keys. These properties are intended to be provided as named arguments to the method, as shown in the example earlier. This makes for a very concise and intuitive way to set the properties.

  • The main overload of entry() is declared to take its arguments in this order: Map props, String name, Closure closure. When called, entry() is (typically) given arguments in a different order: String name, Map props, Closure closure. This is due to Groovy's convention for passing named arguments to a method, described here, in the section "Named Arguments".


One final note about this builder -- it doesn't just work with files. Because the constructor takes an OutputStream, it can write to any stream. So it could be used to write directly to a servlet response, for example. Similarly, the entries are populated as streams, so they can be filled by anything that can write to a stream.