Skip to main content

Bin Bash Java (Chapter 1)

Posted by evanx on May 24, 2006 at 1:37 AM PDT

Introduction

In the preceding blogs "Java is all you'll ever need" and "A Fool's Errand" I alluded to using Java for "small tasks" eg. file/system tasks, rather than shell scripts. I promised to present some examples along these lines.

This is Chapter 1 of many, and presents a basic design. We'll thrash it out in subsequent chapters.

Motivation

My motivation for trying to move away from shell scripting (or using some other scripting language like python or groovy), in favour of writing "tasklets" in Java, are as follows.

  • My personal familiarity and productivity with my favourite Java and Netbeans environment.
  • The unlimited power of available Java libraries and tools. "Yeah baby, bring it on!"
  • Using Netbeans' convenient CVS/Subversion to manage and version tasklets.
  • Using Netbeans to "keep it clean" eg. revising, commenting (javadocs) and refactoring tasklets.
  • Moving towards a portable, cross-platform solution for file and system tasks (compared to shell scripts).

Problem

A typical task is backing up our computer, so let's consider this one of our first goals. OK, this could take a while. Hopefully we'll achieve this goal in subsequent chapters. Doing a good design here would be a great start.

linux-av.png
We want our tasklets to be cross-platform. In particular, we want to use our "backup tasklet" to backup our Linux PC, Windows laptop, and MacOSX media center. So let's keep that in mind.

Of course our "framework" should be reusable for other tasks, besides the backup one. And very convenient to use. So that some day soon, we can write all our file and system tasks in Java, and never look back, woohoo!

Design Overview

Let's create some helper classes, eg. TFileHelper for handling files, and TProcessHelper for handling processes. And we will create a TTaskContext which exposes our "library" to our tasklets, and/or can be used as a superclass by our tasklet classes.

Note that I've choosen the letter T to prefix our "tasking" project classes. This avoids any potential namespace conflicts, and makes it clear which classes are our new ones.

To give us some flexibility, let's create our own classes to represent files and processes, namely TFile and TProcess. We will extend those eg. TDir and TZipFile from TFile.

folder1_man.png

Files

First let's design our file helper class. (Note that I'm using abstract declarations to highlight the interface at this stage, rather than the implementation.)

public class TFileHelper {
   public boolean exists(TFile file);
   public boolean isDirectory(TFile file);
   public void remove(TFile file);
   public void removeIfExists(TFile file);
   public void removeDirectory(TDir directory);
   public void moveToTrash(TFile file);
   public void moveToTrashDirectory(TDir directory);
   public TDir createDirectory(TDir directory);
   public void copy(TFile file, TFile destination);
   public void copyOverwrite(TFile file, TFile destination);
   public List listDirectory(TDir directory);
   public List listDirectoryRecursively(TDir directory);
   public List findRecursively(TDir directory, String pattern);

   public TZipFile zipRecursively(TFile ... paths);
   public TZipFile openZipFile(TZipFile zipFile);

   public String getDigestString(TFile file);
   public boolean equalsDigest(TFile file, TFile otherFile);
}

That's just for starters. We can expect this class to grow a lot!

yast_zip.png
The methods related to zip files might be refactored out later eg. into TArchiveFileHelper, and the digest methods to TDigestHelper, but let's not overdesign this thing quite yet. Later when we wanna support different archives, eg. tar et al, and different digests, eg. MD5 vs CRC32, then we might create generic interfaces for archives and digests, for different implementations. But not today. Rome wasn't built in a day, innit.

WindowsTasks.png
We might have these methods throw wrapped runtime exceptions, eg. TIOException, which is a RuntimeException wrapping IOException. So that's why the methods above are not throwing checked exceptions, in case you were wondering.

Processes

Next let's design our process helper class.

public class TProcessHelper {
   public void kill(TProcess ... processes);
   public boolean killAll(TProcess process);
   public TFile pipe(TProcess ... processes);
   public TFile exec(TProcess process, Object ... args);
   public List getProcessList(TProcess ... processes);

   public TProcess fileWriter(TFile outputFile);
   public TProcess fileReader(TFile inputFile);
   public TProcess gzipProcess(TFile gzipFile);
   public TProcess tarProcess(TFile tarFile);
   public TProcess xargProcess(TProcess execProcess);
   public TProcess findProcess(TProcess execProcess);
}

The file reader and writer processes above would be used for redirection, eg. the file reader as the first argument in the pipe(), and/or the file writer as the last, as follows.

   processHelper.pipe(findProcess, grepProcess,
      xargProcess, gzipProcess, fileWriter); 

As you can see, we are loving Java5 varargs :)

Context

For convenience, let's introduce a superclass for our tasklets.

public class TTaskContext {
   public TFileHelper fileHelper = new TFileHelper();
   public TProcessHelper processHelper = new TProcessHelper();
   public TCalendarHelper calendarHelper = new TCalendarHelper();

   public TDir toDir(String dirName);
   public TFile toFile(String fileName);
   public TProcess toProcess(String processName);
}

Let's slide this baby into gear and start putting rubber to road.

Example Implementation

So let's imagine what our first backup tasklet implementation might look like, eg. for zipping up our home directory on Linux.

public class BackupTask extends TTaskContext {

   @TArgument TDir targetDir = toDir("/home/evan");
   @TArgument TDir archiveDir = toDir("/backups/evan");
   @TArgument String zipBaseFileName = "evan";

   public void run() {
      String timestamp = calendarHelper.getNumericTimestamp();
      TZipFile zipFile = fileHelper.zipRecursively(targetDir);
      zipFile.setOutputDirectory(archiveDir);
      zipFile.saveAs(zipBaseFileName + timestamp);
   }
}

Ok, so if we wanna write tasklets like the above, we still gotta bit of work to do. It's gonna rock tho, innit.

So you spotted those annotations. The idea is that our framework could prompt for those eg. if not specified on the command-line...

Future plans

So we gonna use annotations to tell the framework what it needs to know to provide a "tasklet container" with a management console in future maybe. Now we're talking!

I'm getting way ahead of myself here, but let's dream on...

This "management console" could have a Swing interface, and/or a web interface.

yast_remote.png
A Swing interface could support a file chooser, which would be very handy, and would be quick to throw together using Netbeans/Mattise. Hey, it could even let us invoke remote tasklets, on lists of multiple machines... Hang on, this could grow up to be a network management system! "Heel, boy!"

On the other hand, a web interface would be quick and easy for administering remote boxen, eg. backing up our media center in the lounge using our laptop. Even on a local machine, having a web-based console for tasks would be handy.

You know what, I just decided we gonna embed a web server into our tasklet framework. So that our tasklets are always remoteable, and deployable via a single self-contained jar...
OK, I better stop, I'm getting too excited here!

Conclusion

TProcessHelper2.png
This concludes "Chapter 1." We made a start which is the most important thing. We came up with a design. Hopefully it'll hold up. We'll find out soon enough.

Like when you are reversing your car towards a wall, you find out very quickly when you've gone too far. Rather that than driving in the wrong direction for hours on end, and not knowing it.

Let's hope for accidents of the former "loud and clear" type, rather than misdirections of the latter, later type.

In the next chapter, we might refine this design before we kick off, so please post comments to that effect.

Then we can start rolling up our sleeves to implement some of the file IO functionality. Uh oh, sounds like work. So we'll google and cut and paste. We are developers after all, with a limited amount of time, so the less code we actually write the better, innit ;)

Update. Check out NailGun which aims to obviate the start time of the JVM, eg. for Java command-line tasks. They say, "Java has an extensive and robust core API and huge number of available open source libraries. It's a great big hammer, making almost any programming project look like a nail." :)

Related Topics >>