Chapter 4. Where Am I? Execution Context

Java claims—and rightly so—to be a “compile once, run anywhere” language. But when a program starts, that “anywhere” is now a specific somewhere. When running a Java application on Linux, or any environment for that matter, the question arises, “Where am I?” (context, environment, familiar landmarks). What can a Java program find out about its environment? In particular, on the Linux platform, (how) can we get at:

  • Command-line parameters?

  • The current shell’s environment variables?

  • The current working directory?

  • The location of data files?

The answers to these questions will depend on what kind of Java application you are creating, and just how portable you want your application to be.

What You Will Learn

We’ll show you how Java provides access to the command-line parameters and environment variables. We’ll also discuss the Java RunTime and Property classes. Java’s use of the standard input/output/error streams is also briefly covered, along with an introduction to those concepts. We’ll end with a short word on portability concerns.

A Simple Start

The most basic external information that a program may use is the information supplied on its invocation—simple parameters or arguments, such as filenames or options, that can direct its running and make it a more flexible tool. Let’s start with getting at that information from a Java program.

Command-Line Arguments

When a program is run from the command line, more than just the program name can be supplied. Here are some examples:

$ javac Hi.java
$ mv Acct.java core/Account.java
$ ls -l

In the first example, we invoked a program called javac and gave it the parameter Hi.java, the name of the file containing the Java program that we want javac to compile to Java byte code. (We’ve got a whole chapter on how to set up and run the Java compiler, see Chapter 5.) The mv got two command-line arguments, Acct.java and core/Account.java, which look a lot like pathnames. The ls command has one argument, -l, which in Linux usually indicates, by its leading minus sign, that it is an option for altering the behavior of the command. (In this case it produces the “long” version of the directory listing.)

Even point-and-click GUIs allow such parameters to be supplied, though often not visible to the user. In KDE, one can create a new desktop icon that is a link to an application. Such an icon has a property sheet that lists, on the Execute tab, the command to be run, including any parameters.

In Java, the parameters supplied on the command line are available to the main() method of a Java class. The signature for this method is:

public static void main(String args[])

From within main(), the various parameters are available as the elements of the array of Strings. The class in Example 4.1 will display those parameters when the program is run.

Example 4.1. Java program to dump command-line arguments

/*
 * simple command-line parameter displayer
 */

public class
CLine
{
  public static void
  main(String [] args)
  {
    for (int i = 0; i < args.length; i++)
    {
      System.out.println(args[i]);
    }

  } // main

} // class CLine

We compile and run the example, providing a few command-line parameters:

$ javac CLine.java
$ java CLine hello world file.txt blue
hello
world
file.txt
blue
$

Not all classes will have main() methods, but any can. Even if several classes in a package have main() methods, that is not a problem. Which one will be the “main” main()? It’s the class we specified when we invoked our program. In Example 4.1, the main() that is executed is the one in the CLine class. Even if CLine used other classes (it does—String is a class) it doesn’t matter if those other classes have main() methods or not.

Unit Testing Made Easy

Why all the fuss about main() and command-line parameters? Such main() methods are a handy way to provide unit tests for a class. The tests can be controlled by the command-line parameters. By testing each class you can reduce the time to integrate the parts of an application. Furthermore, a set of unit tests can be built up (e.g., as shell scripts) to provide automated regression tests for the entire project. As a more rigorous and systematic approach to unit testing, we discuss junit in Chapter 13.

The System Class

The Java System class provides some of the answers to questions about our environment. What follows is not an exhaustive discussion of all the methods in the System class, but only of those areas that touch on our specific focus—input/output (I/O) and environment variables.

Be aware that all of the methods in the System class are static. Therefore you never need to (and you can’t) call a constructor on System. You just use the “class name, dot, method name” syntax to call the method (e.g., System.getProperties()). Similarly, the accessible fields in System are all static, so for some of the I/O-related methods you use the “class name, dot, field name, dot, method name” syntax (e.g., System.out.println()). As of Java 5.0, you can shorten this, by using a static import, that is:

import static java.lang.System.*;

Then in your other references you can leave off System, for example, getProperties() and out.println().

Java and Standard I/O

Java adopted the UNIX concept of standard I/O (see Section 1.3.1.1). The Linux file descriptors are available to a running Java program as I/O streams via the System class. The System class contains three public static fields named in, out, and err. You’ve probably already seen out in Java programs with statements like this:

System.out.println("Hello, world.");

You can also write:

System.err.println("Error message here
");

and

BufferedReader in = new BufferedReader(new
                        InputStreamReader(System.in));
while ((line = in.readLine()) != null) {
...
}

Java parallels Linux nicely on I/O descriptors. If you redirect any of those file descriptors from the shell command line when you execute a Java program, then that redirected I/O is available to your Java application—with no additional work on your part.

In the example above, if you have System.in all wrapped up into a BufferedReader from which your program is reading lines, then you can run that program as:

$ java MyCode

and it will read input as you type it on your keyboard. This may be how you test your program, but when you put this program to its intended use, you may want it to be able to read from a file. This you can do without any change to the program—thanks to file descriptors, input streams, and redirecting input, for example:

$ java MyCode < file2

which will let the same Java program read from the file named file2 rather than from keyboard.

Your Java program can also set the values of System.in, System.out, and System.err as it executes, to change their destinations.

One common example is changing the destination of System.out, the typical recipient of debugging or logging messages. Say you’ve created a class or even a whole package of classes that write log messages to System.out (e.g., System.out.println("some message")). Now you realize that you’d like the output to go somewhere else.

You could redirect standard out, as in:

$ java SomeClass > log

but that requires the user to remember to redirect the output every time the program is invoked. That’s fine for testing, or if the output is intended to go to a different place each time it is invoked. But, in this example, we always want the output to go to the same location.

Without changing any of the System.out.println() statements, all the messages can be sent to a new location by reassigning the System.out print stream. The System class has a setter for out—that is, a method which will let you set a new value for out. In your Java program, open the new destination file and give this to the System class:

PrintStream ps = new PrintStream("pathname");
System.setOut(ps);

It will be used from that point forward in the execution of this program as its out output stream.

Caution

Changing standard out (or in, or err) will make the change for all classes from here on in this invocation of the Java runtime—they are static fields of the one System class. Since this is so serious a move, the Java Security Manager (see Section 5.8.4.2) provides a check for setIO to see if the Java program is allowed to make such changes. If such a security manager is in place and you are not allowed to make such changes, an exception (SecurityException) will be thrown. Note also that the permission applies to setting any of the fields; it doesn’t divide the permission into setting one (e.g., out) but not another (e.g., in).

Environment Variables

When Linux programs are run they have the open file descriptors described above. They also carry with them a list of “name=value” pairs called their environment. These environment variables allow for context to be shared among several successively executed programs. Some examples of environment variables are:

  • USER is the name you used to log in.

  • HOME is the directory where you start when you log in.

  • PATH is the list of directories searched for executable files.

To see the environment variables defined in your current shell, type env at the command prompt:

$ env
HOME=/home/user01
USER=user01
PATH=/bin:/usr/bin:/usr/local/bin:/home/user01/bin
...
$

The names of environment variables, sometimes referred to as shell variables, are traditionally uppercase, though that is only a convention. The variable names are treated in a case sensitive fashion (e.g., Home != HOME).

You can set environment variables for use in the current shell with a simple assignment statement:

$ VAR=value

That will set the value for the duration of this shell, but not for any of its subprocesses. Since running another program is a subprocess, such an assignment won’t be visible in your running program. Instead, you can export the variable so that it is carried forward to all subprocesses: [1]

$ export VAR=value

Java and Environment Variables

If these environment variables are available to all Linux processes, then how do we get at them from a Java program? Well, we can’t do it quite as directly as you might think. In previous (1.2 and older) versions of Java, the System class had a getenv() method. Its argument was a String name of an environment variable and it returned the environment variable’s value as a String. This has been deprecated. In fact, an attempt to use getenv() in more recent versions of Java will result in an exception. Sun decided that this was too platform-specific; not all platforms have environment variables.

Example 4.2. Java program to dump environment variables

/*
 * simple environment examiner
 */
import java.util.*;

public class
AllEnv
{
  public static void
  main(String [] args)
  {
    Properties props = java.lang.System.getProperties();
    for (Enumeration enm = props.propertyNames(); enm.hasMoreElements();)
    {
      String key = (String) enm.nextElement();
      System.out.print(key);
      System.out.print(" = ");
      System.out.println(props.getProperty(key));
    }

   } // main

}  // class AllEnv

Now (Java 1.3 and beyond) the preferred approach is to use the getProperties() and getProperty() methods of the System class. How are these different from the getenv() approach? To a Linux developer, getenv() was easy and straightforward—just not very portable. To accommodate other systems, Java defines a set of properties that are reasonable to expect to be defined on any system, and provides a Java property name for each one.

To see the entire list, call the getProperties() method. It returns a Properties class, which is an extension of the Hashtable class. From this class you can get an Enumeration of the names, as Example 4.2 demonstrates.

Now compile and run this example:

$ javac AllEnv.java
$ java AllEnv

and you will get a long list of properties—in no particular order. They are kept in a hashtable and thus not sorted. Of course it would be easier to use this list if they were sorted. Linux to the rescue.

$ java AllEnv | sort

It’s often in simple little steps like this that one begins to see the power of Linux. In Linux, not every desirable feature has to be crammed into every possible place where it might be used. Instead, features can be written once and connected to one another as needed. Here what we need is to have the list of properties sorted. We don’t need to worry that our class didn’t sort its output. In Linux we just connect the standard output of the Java program with a sort utility that Linux provides.

So what are all these properties? Many of them have to do with Java-related information (java.version, and so on), but a few are more general. Those that parallel the typical Linux environment variables are:

  • file.separator is the file separator (“/” on Linux).

  • path.separator is the path separator (“:” on Linux).

  • line.separator is the line separator (“ ” on Linux).

  • user.name is the user’s account name.

  • user.home is the user’s home directory.

  • user.dir is the user’s current working directory.

But that leaves out so many environment variables, especially the application-specific ones (e.g., CVSROOT). How would a Java program get at these?

Because of this new, more portable way to describe the environment, there is no easy way to get at other environment variables. There are a few approaches, but they are all indirect.

First, you can add to the properties list by defining new properties on the command line when invoking the program, for example:

$ java -Dkey=value AllEnv

You can list several properties on the line by repeating the -D parameter:

$ java -DHOME=/home/mydir -DALT=other -DETC="so forth" AllEnv

Instead of typing those values, you’d probably want to let the Linux shell put in the values from its environment. So you’d use shell variables, for example:

$ java -DHOME="${HOME}" -DALT="${ALT}" -DETC="${ETC}" AllEnv

assuming that HOME, ALT, and ETC have already been defined in the shell’s environment. [2]

If there are only a few variables that you need to pass to Java, put them on the command line as shown above. Put that command line into a shell script and use the script to invoke the program so that the parameters are supplied every time.

But if you want to access many or all of the environment variables then you may want to do something a little more complex. Notice the syntax of the output of the env command. It is in the same format (name=value) as are properties. So if we use a shell script to invoke our program, we can have it place all these values into a file by redirecting output, then open this file as a Java properties file and thus make all the name/value pairs accessible.

The following commands in a shell script attempt to do just that:

env > /tmp/$$.env
java -DENVFILE=/tmp/$$.env MyClass
rm /tmp/$$.env

where MyClass is the Java program that you wish to run.

Tip

The shell variable $$ is the numeric process ID of the running process. This provides a unique ID during each invocation of the program. Each run of the script will have its own process and thus its own process ID. Thus a single user could execute this script multiple times concurrently without fear of collision with himself or others.

We remove the temporary file with the rm command in the last line of the script to avoid cluttering our /tmp directory with lots of these files.

But now we have to add code to MyClass to open the file defined by ENVFILE and read the properties it contains. This leads us naturally to the Java Properties class, the subject of our next section, where we’ll talk more about this example.

The Properties Class

The Javadoc page for the Properties class describes it as “a persistent set of properties . . . saved to . . . or loaded from . . . a stream.” In other words, it is a hashtable (a set of name/value pairs) that can be read from or written to a stream—which typically means a file. (Other things can be streams, but for now, think “file”.)

The great thing about name/value pairs is how readable and usable they are. When they are written to a file, there’s no fancy formatting, no fixed width fields, no unreadable encryptions and special characters; it’s just name=value. You could say that the “=” and the newline are the special characters that provide all the formatting you need. It means that you can type up a properties file with the simplest of editors, or even generate one quickly as we saw in the previous example (here we use a simple filename):

$ env > propertyfile

Properties are also easy to use. Since they’re based on hashtables, there is no searching code to write. You call a method giving it the name, it returns the value.

If we pass in the name of the file via the -D parameter, then we can get that filename in Java with:

System.getProperty("ENVFILE");

where ENVFILE is a name that we made up and used on the command line:

$ java -DENVFILE=propertyfile MyClass

We could also have used:

$ java MyClass propertyfile

so that args[0] [3] in the Java code to get the name of the file (see Section 4.2.1), but since we want to learn about properties, we’ll use the property methods here.

Now let’s open that property file (Example 4.3).

Example 4.3. Demonstrating the Properties class

import java.io.*;
import java.util.*;

public class
EnvFileIn
{
  public static void
  main(String [] args)
    throws IOException
  {
    String envfile = System.getProperty("ENVFILE", ".envfile");

    BufferedInputStream bis = new BufferedInputStream(
                                new FileInputStream(envfile));
    Properties prop = new Properties();
    prop.load(bis);
    bis.close();

    prop.list(System.out); // dumps the whole list to System.out

  } // main

} // class EnvFileIn

Notice the way that we got the value for the environment file’s name. This form of the getProperty() call provides not only the name we are looking up (ENVFILE) but also lets us specify a default value in case the name is not found in the properties list. Here our default value is .envfile.

Just as it was a simple matter of using the load() method to read up an entire file of properties, so you can write out the entire list of properties to the screen with the list() method. The argument to list() is either a PrintStream or a PrintWriter. System.out is a PrintStream, so that will work.

The format of the properties file is name=value. But it is also possible to put comments in a properties file. Any line beginning with a “# is ignored. Try it.

It’s also easy to (re)write a file of properties with the store() method. The parameters are an OutputStream and a String; the latter will serve as a label for the parameters, written to an opening comment in the properties file.

If your program needs to examine the list of property names, you can get an Enumerator of the entire list via the propertyNames() method. Modify Example 4.3 to replace the list() call with a do-it-yourself version that uses the Enumerator returned from propertyNames() to list all the names and values. Hint: Use getProperty() on each name retrieved via the enumeration.

The Java Properties class extends the java.util.Hashtable class. This means, in part, that all the other Hashtable methods are available to a Properties class. Methods such as containsKey() or containsValue() can be helpful, as can isEmpty(). One caution, though. You should use setProperty() if you want to add values to Properties, rather than the Hashtable’s put() method. They do largely the same thing, but setProperty() enforces that its parameters are Strings. This is important if you want to write out the properties to a file, as it’s meant for Strings only.

The Runtime Class

Let’s discuss one last way to get to the underlying (Linux) system information. Be warned, though, that this is the least portable approach of all we have mentioned.

exec()

Familiar to C/C++ programmers, the exec() call in the Java Runtime class does much the same thing. It gives you a way to start another program outside of the current Java Virtual Machine. In doing so, you can connect to its standard in/out/err and either drive it by writing to its standard in, or read its results from its standard out. (Yes, that’s correct—we write to its input and read from its output. If that sounds wrong, think it through. Our Java code is on the opposite side of the I/O fence. The external program’s output becomes our input.)

Example 4.4 shows a Java program that can invoke an arbitrary Linux program. The output of the program is displayed.

Example 4.4. Java program to execute any Linux program

import java.io.*;

public class
Exec
{
  public static void
  main(String [] args)
    throws IOException
  {
    String ln;
    Process p = Runtime.getRuntime().exec(args);
    BufferedReader br = new BufferedReader(
                          new InputStreamReader(
                            p.getInputStream()));

    while((ln = br.readLine()) != null) {
      System.out.println(ln);
    }
    System.out.println("returns:" + p.exitValue());

  } // main

} // class Exec

The command-line arguments are taken to be the command to be executed and its arguments. For example:

$ java Exec ls -l

Be aware that in this example, only the standard output is captured and displayed from the invoked process. Error messages written to standard err will be lost, unless you modify the program to handle this. We leave that as an exercise for the reader.

Check your Linux knowledge—see if you understand the distinction. If you invoke the sample Exec program as:

$ java Exec ls -l *.java

the shell does the wildcard expansion before invoking the Java runtime. The *.java becomes many files listed on the command line (provided that you have .java files in this directory). If you try to pass the *.java through literally to exec(ls '*.java') it will likely return an error (which won’t be displayed using our example code) and you’ll see a nonzero return status (e.g., 1). That’s because ls doesn’t expand the *. The shell does that. So ls is looking for a single file named *.java, which we hope doesn’t exist in your directory.

Portability

Be aware that the more environment-specific code you build, the less portable your application becomes. It’s not uncommon to use a properties file as a way to parameterize your program, to customize its behavior in a given installation. But keep these to a minimum to stay portable. Avoid invoking other programs, they are likely not available in all environments where Java can run. Java’s claim to “compile once, run anywhere” is amazingly true—provided you keep away from logic in your program that goes looking for trouble.

Review

Java command-line parameters are not that different from C/C++ command-line parameters. Environment variables are a different story. Most of the shell’s environment variables are not readily accessible, and we looked at how you might deal with this situation.

We have discussed, among other things, some uses for these classes: java.util.Properties, java.lang.System, and java.lang.Runtime, but we have only barely scratched the surface. There are many more methods available in these classes with which you can do lots more.

What You Still Don’t Know

The biggest topic in this area that we’ve avoided for now is the Java Native Interface (JNI), a mechanism whereby you can get outside of the Java environment to make calls to existing (native) libraries—for example, Linux system calls. In a coming chapter we’ll actually give you an example of such a call. Then you’ll really be able to make your application nonportable and system-dependent. (But sometimes portability isn’t your goal, right?)

Resources

Perhaps the best resource for the specifics that you’ll need to work with the topics mentioned in this chapter is the Javadoc documentation on the classes that we have mentioned. Learn to read Javadoc pages (see Section 3.2.2.3), bookmark them in your browser, and keep them handy as you write your Java code.



[1] If you are using csh (the C-shell, another Linux command-line interpreter), then the syntax is slightly different. Instead of export name=value use setenv name value (note the different keyword and no equal sign).

[2] The quotations around the shell variables keep any embedded spaces as part of the variable’s value. The curly braces are not strictly necessary in this use.

[3] In C language, the arg[0] is the command being invoked; not so in Java. In Java, the first element of the array is the first argument of the command line (propertyfile in our example).

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.116.21.109