04 January 2010

Controlling your Java command line applications in much an increased performance way

As I am a GNU/Linux fanatic, and do not plan to switch M$ Windows nor in the near or far future, and unfortunately I haven't an Internet connection at home and because my native language is the Arabic so, I've decided to write an English-Arabic dictionary (I know there's already exists good one, but also for historical reasons :) ).

There is a great community called arabeyes that have a good file-based database of translated words , So I've decided to write a simple program that search in this file-based database and retrieve the matched word(s) for me.

This database is all about a 26 files each file for all words for an English character, as there's a file translating words starts with A, other for B and so on.

Really I didn't want to write a Java application from scratch ( Actually I've written on in the past but needn't to rewrite it again), So I decided to utilize some Perl script that do the search process.

Here's the Perl Script:

perl -n -e 'if (/^msgid\s+"(.*)"/) { print "$1 : "; } if (/^msgstr\s+"(.*)"/) { print "$1\n"; }' \
$WORDLIST/full_wordlist_*.po | grep -iw <word>

I'll not comment on this Perl script because I know about perl like a .NET developer's knowledge about Spring framework :)

So, I decided to put this script in a shell file and call it from Java as a process, here' the code for that:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

import javax.swing.JOptionPane;

public class JDictionary {

public static void main(String[] args) throws IOException {

String dictionaryExeName = System.getProperty("dict.cmd");
Runtime r = Runtime.getRuntime();

String dictionaryFullCommand = "";

if (args.length >= 1) {
dictionaryFullCommand = dictionaryExeName + args[0];
}
System.out.println(dictionaryFullCommand);
Process p = r.exec(dictionaryFullCommand);

BufferedReader output = new BufferedReader(new InputStreamReader(p
.getInputStream()));

String line = "";
String result = "";
while ((line = output.readLine()) != null) {
result += line + "\n";
}

JOptionPane.showMessageDialog(null, result, "The Dictionary",
JOptionPane.INFORMATION_MESSAGE);
}
}

And I used to run this simple java class as :

java -Ddict.cmd=/path/to/perl/script <word>

I put the above command in a shell file and put it in my PATH and everything goes ok.
After a while, I became more uncomfortable with the performance of this app. Every time i need to know the meaning of a single word, I have to start the JVM !!, ohh bad idea.

So, I've decided to implement some pattern. It is not a Design pattern, but an implementation pattern.
It is a pattern for controlling you Java command line application that runs in the background ( really I captured the idea from my manager, and he told me that he captured the idea from one of Apache projects)

The idea is that, you will start you application in the background and make it accepts its input from a file and then you will pass it the controlling commands by inserting them in this file, so you application will run in the background, but you still control it.

So, I will write two components (classes), one to read the word that need to be translated from a file and pass it to the second component and then get blocked until the other component do its job, the other component will translate this word and show me the translated word and unblock the first component and block it self (the Producer/Consumer threading model).

Here's the class the will read from the file (note, I made it do regular file reading, not reading from its Standard input):

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.util.Scanner;

public class WordListener extends Thread {

private Word word;
private String oldStat;

public WordListener(Word word) {
this.word = word;
}

public void listenToWord() throws FileNotFoundException {
Scanner scanner = new Scanner(new FileInputStream("/home/mohammed/programs/dict/tmp"));
String str = scanner.nextLine();
if (str.equals(oldStat)) // to prevent it from translating the same word again
return;
word.put(str);
oldStat = str;
}

public void run() {
while (true)
try {
listenToWord();
sleep(1000); // for the processor's sake :)
} catch (FileNotFoundException e) {
e.printStackTrace();
}catch (InterruptedException ex) {
ex.printStackTrace();
}
}
}

And here's the class that do the translation:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

import javax.swing.JOptionPane;


public class WordFinder extends Thread{

private Word word;

public WordFinder(Word word) {
this.word = word;
}

public void findWord() throws IOException{
String dictionaryExeName = System.getProperty("dict.cmd");
Runtime r = Runtime.getRuntime();

String dictionaryFullCommand = "";
while (true){
dictionaryFullCommand = dictionaryExeName + " " + word.get();
Process p = r.exec(dictionaryFullCommand);

BufferedReader output = new BufferedReader(new InputStreamReader(p
.getInputStream()));

String line = "";
String result = "";
while ((line = output.readLine()) != null) {
result += line + "\n";
}

JOptionPane.showMessageDialog(null, result, "The Dictionary", JOptionPane.INFORMATION_MESSAGE);
}
}

public void run() {
try {
findWord();
} catch (IOException e) {
e.printStackTrace();
}
}
}

And here's the Word class ( The sync'd buffer) :

public class Word {
private String content;
private boolean available = false;

public synchronized void put(String word) {
if (available == true ) {
try {
wait();
} catch (InterruptedException e) { }
}
this.content = word;
avaiable = true;
notifyAll();
}

public synchronized String get() {
if (available == false) {
try {
wait();
} catch (InterruptedException e) { }
}
available = false;
notifyAll();
return content;
}
}

and here's the main class :

public class JDictionary {

public static void main(String[] args) {
Word word = new Word();
WordListener wl = new WordListener(word);
WordFinder wf = new WordFinder(word);

wl.start();
wf.start();
}
}

Look, I'll not illustrate the producer/consumer model, for more info see Java tutorial at http://java.sun.com/tutorial

So, to start the Dictionary, I'll have to start the JVM just once by this command (I can make it a system daemon):

java -Ddict.cmd=/path/to/perl/script


and to translating words (Control the running Java program), I'll execute this command :

echo "<word>" > /home/mohammed/programs/dict/tmp

of course you could put the last line in a shell file like this:

#!/bin/sh
echo $1 > /home/mohammed/programs/dict/tmp

That's all.
Note that, The code above is a dirty code that need more clean up.

thanks.

1 comment:

mhewedy said...

Note, another solution to this problem is to have my application to start listening on some port (as a ServerSocket app) and post the commands on this port to the app.

Or may use http://commons.apache.org/daemon/