HomeDigital EditionSys-Con RadioSearch Java Cd
Advanced Java AWT Book Reviews/Excerpts Client Server Corba Editorials Embedded Java Enterprise Java IDE's Industry Watch Integration Interviews Java Applet Java & Databases Java & Web Services Java Fundamentals Java Native Interface Java Servlets Java Beans J2ME Libraries .NET Object Orientation Observations/IMHO Product Reviews Scalability & Performance Security Server Side Source Code Straight Talking Swing Threads Using Java with others Wireless XML
 

More recently, due in part to Sun's "Java Everywhere" campaign, we are beginning to see applications featuring server-side Java (servlets) and imbedded Java devices (phones, light switches, etc.), as well as large-scale standalone applications (Sun Java Server). What continues to be a seldom discussed subject is small-scale standalone Java applications.

While there have been several major standalone Java applications released, many are geared toward the Internet. Some are even more narrowly focused as Java development tools. What many of these applications have in common is that they tend to be graphical in nature and are invoked via a wrapper function. These wrapper functions are typically written in shell or batch script. They define the proper Java runtime environment and then invoke the application.

What is missing today is the ability for non-Internet developers to easily use Java in place of C or Perl to write standalone programs. This article will describe an automated method whereby the necessary Java runtime environment can be set up prior to invoking a Java application. With this environment, you can begin to use Java in place of other languages (Korn shell, bash, Perl, etc.) for your normal daily systems administration needs. These small utilities can then be deployed into your system bin directory for all to use.

For the purposes of this article, I assume that you have a good understanding of client/server technologies, object-oriented programing and C/C++ as well as a basic understanding of the Unix operating system and shell programming.

Clarification
It should be noted that several third-party Java compilers now support "native" code generation. Namely, the compile process generates an executable image that will run only on the target platform, much like a C compiler generates a binary executable file. To accomplish this, most compiler vendors are imbedding the Java Virtual Machine (JVM) in the binary object. While this solves the problem of a standalone application's runtime environment, these compilers are mostly limited to 95/NT platforms. This will be a suitable solution once these compilers are available on all platforms. Until then we must make do with supplying our own runtime environment.

The Goal
Our objective is to create a facility whereby we can write "Unix type" filters in Java. For this discussion, we will be focusing on the filter framework and not the filter applications themselves.

In general, a Unix filter is a process that reads standard input (stdin), or a file, and writes to standard output (stdout), or a file, all the while applying a filter algorithm to the input data. A filter can be something simple like transforming all upper case letters to lower or it can consist of complex mathematical formulations.

A good filter should support the following features:

  1. Unix-style optional switches: a dash "-" followed by either a letter or word
  2. switches that can have other optional arguments: "-filename foo.txt"
  3. Redirection and pipes, optionally
  4. systematic error checking on all switch settings.
  5. Be cross-platform compatible
  6. Once installed in the system bin directory, the ability to be invoked directly by entering the name of the filter at the command prompt.

To achieve these goals we must develop two distinct entities:

  1. A standardized Java front-end for processing filter switch settings
  2. A standardized script front-end for creating the necessary run-time environment for a given filter
Additionally, a method whereby the wrapper script is married to the application filter in an automated process would help to simplify the make process.

Java Filter Front-End
For our discussions, the front-end filter process will be designed to read switch settings, detect user input errors and display on-line help. More importantly, it will also invoke the core program logic. Listing 1, Java Filter Front-End, depicts just such a program. This program consists of the following sections:

  1. Main function declaration - Necessary for all standalone Java programs.
  2. Switch parsing section - Two switches are defined by default: verbose and help
  3. On-line help - If the help option is specified, a detailed usage message will be displayed.
  4. Error handler - If an illegal or malformed switch is set, the error handler will display a message and terminate.
  5. Application code - Once all switches are successfully processed, normal/core execution can be resumed.

With this simple template file, we now have the structure necessary to support most of the desired functionality - namely, five of the six features of a standalone filter. By modifying this structure we can easily support additional switch settings, input and output arguments.

Unfortunately, this template program is not sufficient to solve the sixth requirement: "direct invocation of the target program from the command line." To achieve this last requirement, we need to resort to a wrapper script.

Invoking a Standalone Java Program
All Java programs must be invoked via the Java Virtual Machine. Java programs cannot be invoked directly - as is the case for C/C++ programs, batch files, or script files. There is no equivalent of a magic cookie (e.g., #!/usr/Java/bin) for a Java program. Trying to execute a Java program by typing the class name will not work, nor will making the class file executable.

To run your program, you must make sure that the Java Virtual Machine can find your class file. This can be done in several ways: setting the CLASSPATH variable, starting the JVM from the same directory as your .class file, etc.

Example 1:

> export CLASSPATH =
/usr/local/Javabin:$CLASSPATH
> Java /usr/local/Javabin/MyApp

Example 2:

> cd /usr/local/Javabin
Java MyApp

Either of the above methods will ensure that the Java Virtual Machine can find the target class file.

If your Java program relies on other class definitions then you must specify the path to the other class files too. Again assuming the current CLASSPATH definition does not contain the necessary path information, you must specify the proper path spec during invocation. For example:

> Java -classpath
/usr/local/Javabin:
~/Javabin:$CLASSPATH
MyApp

Since our goal is to make your Java application easy for others to use, we must wrap all of the aforementioned constraints into an easy to use package. The end user should not have to type anything more than "MyApp" to invoke your application. The remainder of this article will address just how this can be accomplished.

Wrapper Script
The wrapper script (Listing 2) depicts a shell script designed to invoke your Java applications for you. The purpose of the wrapper script is to simplify the process of invoking your Java Applications to the point where you only need enter the program name along with any application specific parameters.

Let's review exactly what this script does:

  • Line 1: Magic cookie: Ensures that the script's contents can be interpreted no matter what the current shell environment is set to.
  • Line 2: Isolates the target program name from the invocation path. This allows the script to be called relatively, absolutely or via the PATH environment variable. Example:
../../myBin/MyApp
/home/kkranz/myBin/MyApp
MyApp
  • Line 3: Separates the invocation path (e.g. ../../myBin/) from the full path to the script (/home/kkranz/myBin)
  • Line 4: Assumes that the .class file and the script file are named the same: MyApp.ksh, vs. MyApp.Java, vs. and MyApp.class
  • Line 6: Computes the absolute path to the script (and presumably to the .class file - assuming that the script and class files are in the same directory)
  • Line 8: Switches to the script's bin directory
  • Lines 10-13: Looks for a .class file named after the script file in the class directory. If there is no .class file the script aborts.
  • Line 15: Adds the script's bin directory to the CLASSPATH environment variable. An acceptable alternative would have been to pass this information via the -classpath option to the Java Virtual Machine. Either method is acceptable.
  • Line 17: Invokes the Java Virtual Machine with the base name of the script as the first argument and any remaining options as the remainder of the line.
With the use of the wrapper script in Listing 3, you can make it very easy to invoke your Java applications, regardless of what the installation directory is. The only implementation criteria that must be met to use this script are:
  1. You must use the same name for the script file as you do for the .class file.
  2. The script file and the .class file must reside in the same bin directory, although you could easily modify the script to allow the script files to reside in one directory and the .class files to reside in a separate class bin directory.
  3. The script file must reside in a directory defined by the PATH variable.
  4. Other .class files that are accessed at run-time must be in a directory defined by the CLASSPATH environment variable or in a script directory.
Generating the Wrapper Script
So far, we have described a template Java application program and wrapper script that can be used to write standalone Java programs.The final step is to write a program that can generate a wrapper script for a given standalone Java program.

The Java Compiler script (Jcc, listing 3) performs the following basic functions:

  1. Invokes the Java compiler to create the .class file from your source
  2. Invokes Javadoc, to create html based documentation files
  3. Creates a wrapper script tailored toward invoking your Java application
  4. Creates a manual page for your application
Using the Java Compiler script is not mandatory. Its purpose is to simplify the process whereby your Java program and its associated files are generated. Listing 4, Example Makefile, demonstrates how the Jcc would be used in a Unix-style makefile.

Conclusion
If you are a systems administrator and are otherwise not involved in the internet, you do not have to feel left out of the Java learning curve. By using this wrapper script you can begin to use Java in places where you would have traditionally used Perl, C or a shell language. Given Java's rapid growth, it is very likely that it will become a standard feature on most future operating systems. By learning and using Java now, you will help prepare yourself for that time.

About the Author
Ken Kranz is the director of Internet Services for Interaxis Corporation, a developer of database-driven Web sites. He can be reached at [email protected] This and the expanded source code used in this article can be found at www.ebbtide.com/UnixFilters.

	

Listing 1: Java Filter Front-End. 

import Java.util.*; 
class MyApp { 

 public static void main(String args[]) { 

int verboseLevel = 0 ; 
boolean helpFlag = false; 
String usage = "-v[erbose] -h[elp]"; 
int ArgSpec= 1; // minimum number of expected arguments 
int switchCount= 0; // number of switches found 
boolean CLIError = false; // user invocation error 

for (int i=0; i < args.length; i++) { 
 if (args[i].equals("-verbose") || args[i].equals("-v")) { 
verboseLevel++; 
switchCount++; 

 } else if (args[i].equals("-help") || args[i].equals("-h")) { 
helpFlag = true; 
switchCount++; 

 } else if (args[i].startsWith("-", 0)) { 
System.err.println("Illegal switch: " + args[i]); 
CLIError = true; 
switchCount++; 
 }  
} 

if (helpFlag) { 
 System.out.println("" + 
 " -v[erbose] Increase verboseness level\n" + 
 " -h[elp]Display this on-line help message\n"); 
 System.err.println("Usage: " + usage); 
 System.exit(0); 
}  

int totalArgs = args.length - switchCount; 
int firstArgOff= switchCount; 
if (ArgSpec != totalArgs) { 
 System.err.println(totalArgs + " arguments found " +  
ArgSpec + " expected\n"); 
 System.err.println("Usage: " + usage); 
 System.exit(1); 
} else if (CLIError) { 
 System.err.println("Invalid/illegal switches encountered"); 
 System.err.println("Usage: " + usage); 
 System.exit(2); 
} 

 /****************************************/ 
** Place your application code here**/ 
 /****************************************/  
 } 
} 

Listing 2: Wrapper Script 

1 #!/sbin/ksh  # magic cookie 
2 BASENAME=$(basename $0)  # eg: MyApp 
3 ME=$(whence $0 )  # eg: /usr/local/bin/MyApp 
4 CLASS=${BASENAME}.class  # eg: MyApp.class 
5  
6 PATH2ME=$(dirname $ME )  # eg: /usr/local/bin 
7  
8 cd $PATH2ME  # go to the bin directory 
9  
10 if [[ ! -a $CLASS ]]; then  
11echo "$BASENAME: unable to locate target: $CLASS" >&2  
12exit 1  
13 fi  
14  
15 export CLASSPATH=$PATH2ME:$CLASSPATH  
16  
17 Java $BASENAME [email protected]  # invoke the application 

Listing 3: Java Compiler (JCC). 

#!/usr/bin/ksh 

# initialize shell overhead variables 
BASENAME=$(basename $0) 
USAGE="USAGE: $BASENAME [-c] [-d] [-s] [-o string] [-p CLASSPATH] [-m] \ 
 [-v [-v]] [-h] filename[.Java]" 
(( ARG_COUNT = 1 )) # This nubmer must reflect true argument count 
(( OPT_FLAG = 0 )) # Command line mistake flag 
(( OPT_COUNT = 0 )) # Number of options on the command line 
(( HELP_FLAG = 0 )) # Default: no help required 
(( WARNING = 0 )) # General purpose no fail warning flag 
(( JAVAC = 1 )) # call the compiler 
(( JAVADOC = 1 )) # call Javadoc 
(( SCRIPT= 1 )) # generate the script 
(( PATH_OPT= 0 )) # extend the CLASSPATH switch 
JAVAOPTS=""  # default Java compile options 
PATH_OPT_VAL=""  # default extended CLASSPATH 

if [[ $VERBOSE_FLAG = "" ]]; then 
 (( VERBOSE_FLAG= 0 )) # Default: no verbose 
fi 

# Parse command line options 
while getopts :p:cdso:hv arguments  
do 
 case $arguments in 
p)(( PATH_OPT = 1 ))  # extend the CLASSPATH 
 PATH_OPT_VAL=$OPTARG;; 
s)(( SCRIPT= 0 ));;  # generate script (& man page) 
c)(( JAVAC = 0 ));;  # compile the Java program 
d)(( JAVADOC = 0 ));;  # create the Java Documentation 
o)JAVAOPTS=$OPTARG;;  # read compile switches 
v)(( VERBOSE_FLAG = VERBOSE_FLAG + 1 )) 
  if (( VERBOSE_FLAG > 1 )); then 
 vbs=" -verbose "  
  fi 
 ;; 
h)(( HELP_FLAG = 1 ));;  # display help 
\?) echo "Illegal switch: $OPTARG" # flag illegal switch 
 (( OPT_FLAG = 1 ));; 
 esac 
done 
(( OPT_COUNT = OPTIND - 1 )) 
shift $OPT_COUNT 
  

# check for help 
if (( $HELP_FLAG == 1 )); then 
 echo " Usage: $USAGE" 
 echo " -c  Do not compile the target file" 
 echo " -d  No not create Java Documents" 
 echo " -h  Display this help message" 
 echo " -o string Javac compiler options" 
 echo " -p string Extend the CLASSPATH" 
 echo " -s  No not create the front-end script" 
 echo " -v  Display status information during execution" 
 exit 0 
fi 

# check for illegal switches 
if (( $OPT_FLAG == 1 )); then 
 echo "$BASENAME: Illegal or invalid switche(s) encountered" 
 echo "Usage: $USAGE" 
 exit -1 
elif (( $# != $ARG_COUNT )); then 
 echo "$BASENAME: $# arguments found, at least $ARG_COUNT expected." 
 echo "Usage: $USAGE" 
 exit -1 
fi 

# reset umask to all files are rw-able 
umask 000 

dir=$(echo $1 | dirname ) 
target_basename=$(basename $1) 
target=$(echo $target_basename | cut -d\. -f1) 
ext=$(echo $target_basename | cut -d\. -f2) 

if [[ $ext = $target ]]; then 
 # assume .Java 
 ext="Java" 
elif [[ $ext != "Java" ]]; then 
 echo "$BASENAME: invalid extension" 
 exit 1 
fi 

source=${target}.Java  # a.k.a MyApp.Java 
class=${target}.class  # a.k.a MyApp.class 
html=${target}.html  # a.k.a MyApp.html 
script=${target}  # a.k.a MyApp 
manpage=${target}.1  # a.k.a MyApp.1 
  

# compile program to create target.class from target.Java 
if (( JAVAC )); then 
 if (( VERBOSE_FLAG )); then 
echo "$BASENAME: Javac $JAVAOPTS $vbs ${source}" 
 fi 
 Javac $JAVAOPTS $vbs ${source} 
 status=$? 
 if (( status != 0 )); then 
echo "$BASENAME: Java compiler error: $status" 
exit $status 
 fi 
fi 

# create the target.html file 
if (( JAVADOC )); then 
 if (( VERBOSE_FLAG )); then 
echo "$BASENAME: Javadoc $vbs ${source}" 
 fi 
 Javadoc $vbs ${source} 
fi 

# if the script section is not selected then terminate 
if (( ! SCRIPT )); then 
 exit 0 
fi 
# otherwise create the shell script that will invoke the .class file 

if (( VERBOSE_FLAG )); then 
 echo "$BASENAME: creating $script" 
fi 

# remove the old "executable" version and man page 
rm -f $script $manpage 
  
# insert magic cookie and set debug flags (if required) 
ShellPath="$(which ksh)" 
jar=$? 
if (( jar != 0 )); then 
 echo $COOKIE 
 echo "$BASENAME: Error: Unable to determine magic cookie, aborting..." 
 exit 1 
fi 

# create the execut script 
COOKIE="$ShellPath $StartFlag" 
echo "#!$COOKIE${SHDEBUG}"     > $script 
echo "BASENAME=\$(basename \$0) "    >> $script 
echo "ME=\$(whence \$0 ) "     >> $script 
echo "TARGET=\${BASENAME}.class "    >> $script 
echo ""       >> $script 
echo "PATH2ME=\$(dirname \$ME ) "    >> $script 
echo ""       >> $script 
echo "cd \$PATH2ME "      >> $script 
echo ""       >> $script 
echo "if [[ ! -a \$TARGET ]]; then "    >> $script 
echo " echo \"\$BASENAME: unable to locate target: \$TARGET\" >&2 " >> $script 
echo " exit 1 "      >> $script 
echo "fi "       >> $script 
echo " "       >> $script 
echo "export CLASSPATH=\$PATH2ME:\$CLASSPATH "   >> $script 
if (( PATH_OPT )); then 
 echo "export CLASSPATH=$PATH_OPT_VAL:\$CLASSPATH " >> $script 
fi 
echo ""       >> $script 
echo ""       >> $script 
echo "Java \$BASENAME \[email protected] "     >> $script 
  

# create a dummy man page file 
if (( VERBOSE_FLAG )); then 
 echo "$BASENAME: creating $manpage" 
fi 
echo "" >> $manpage 
echo "${target} is a front-end to the Java application ${target}.class" \ 
 >> $manpage 
echo "please see ${target}.html for more details."\ 
 >> $manpage 
echo "" >> $manpage 
echo "For details regarding Java script wrappers please see $BASENAME" \ 
 >> $manpage 
echo "" >> $manpage 

# make the script executable 
chmod a+rx $script 

Listing 4: Example Makefile. 

BIN=/usr/local/Javabin 
MANBIN=/usr/share/man/cat1/ 
HTMLBIN=/usr/people/kkranz/www/htdocs/ManPages 

MyApp.class: MyApp.Java 
  jcc -v MyApp 

install: 
  cp -p MyApp $(BIN) 
  cp -p MyApp.class $(BIN) 
  cp -p MyApp.1 $(MANBIN) 
  cp -p MyApp.html $(HTMLBIN) 


 

All Rights Reserved
Copyright ©  2004 SYS-CON Media, Inc.
  E-mail: [email protected]

Java and Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries. SYS-CON Publications, Inc. is independent of Sun Microsystems, Inc.