Welcome to the Core Java Technologies Tech Tips, March 4, 2003. Here you'll get tips on using core Java technologies and APIs, such as those in Java 2 Platform, Standard Edition (J2SE).
This issue covers:
Using Runtime.exec to Invoke Child Processes
Programming With File Attributes
These tips were developed using Java 2 SDK, Standard Edition, v 1.4.
This issue of the Core Java Technologies Tech Tips is written by Glen McCluskey.
USING RUNTIME.EXEC TO INVOKE CHILD PROCESSES
Suppose that you are writing a Java application, and you need to obtain the contents of a directory, that is, all the files and subdirectories found within the directory.
You're running on a UNIX system, and you're familiar with the ls command. So you decide to invoke ls from within your Java code. This tip illustrates how to do this, and it also describes why it's probably a bad idea. A command like ls is not portable. Even if the command was portable, there is a simple replacement that uses only Java library features, and thus works across multiple platforms.
Here is what the code looks like:
import java.io.*;
public class ExecDemo1 {
static void listDirContents1(String dir)
throws IOException {
// start the ls command running
Runtime runtime = Runtime.getRuntime();
Process proc = runtime.exec(
"ls" + " " + dir);
// put a BufferedReader on the ls output
InputStream inputstream =
proc.getInputStream();
InputStreamReader inputstreamreader =
new InputStreamReader(inputstream);
BufferedReader bufferedreader =
new BufferedReader(inputstreamreader);
// read the ls output
String line;
while ((line = bufferedreader.readLine())
!= null) {
System.out.println(line);
}
// check for ls failure
try {
if (proc.waitFor() != 0) {
System.err.println("exit value = " +
proc.exitValue());
}
}
catch (InterruptedException e) {
System.err.println(e);
}
}
static void listDirContents2(String dir) {
// create File object for directory
// and then list directory contents
String[] list = new File(dir).list();
int len = (list == null ? 0 : list.length);
for (int i = 0; i < len; i++) {
System.out.println(list[i]);
}
}
public static void main(String[] args)
throws IOException {
if (args.length != 1) {
System.out.println(
"missing directory");
System.exit(1);
}
listDirContents1(args[0]);
listDirContents2(args[0]);
}
}
|
When you run the ExecDemo1 program, specify a directory, for example:
java ExecDemo1 /home/techtips
Your results should list the contents of the directory, perhaps something like this:
AttrDemo1.java
AttrDemo2.java
AttrDemo3.java
AttrDemo4.java
AttrDemo5.java
ExecDemo1.class
ExecDemo1.java
ExecDemo2.java
prev
prev2
x
.
.
.
(repeat all of above)
|
The list is repeated because two methods that produce the file list are used. Each method illustrates a different approach. The first file list is produced by listDirContents1. The repetition is produced by listDirContents2.
Unfortunately, this program will work only on UNIX or Linux systems, or on systems that emulate UNIX with tools such as the MKS Toolkit available on Windows. That's because of the dependency the program has on the ls command.
Runtime.exec is used to create a Process object (Process is abstract, so actually an object of a Process subclass is created). The process runs the ls command. Note that no pathname is specified for ls. The Java system apparently observes the local PATH environment variable, although this is not mentioned in the documentation. Giving a complete path is safer, though it assumes that the ls command can be found in the same place on every system where you run your application. The previous example assumes that the ls command is found in the PATH, typically in a directory such as /bin or /usr/bin (the same assumption is made for the next example which uses the wc command). This issue is an example of the kinds of dependencies you must sort through when using Runtime.exec.
In the ExecDemo1 example, the argument to Runtime.exec is a string. There are other forms of the exec method that are available. For example, one form takes an array of string arguments, and another supports the setting of environment variables. If a string is specified as an argument, it is broken into individual tokens using StringTokenizer.
Input to and output from the child process are managed through streams, available through the Process object. For example, the standard output of the child process is treated as an input stream. This stream is read in the parent in order to obtain the output from the child process.
The waitFor method is used to wait for child process completion, and to obtain the exit status of the process. By convention, a status of 0 indicates success.
The listDirContents2 method shows another way to list directory contents. This approach uses only Java library features. It uses a lot less code, and is much more portable. So listDirContents1 cannot be recommended as a useful approach.
However, suppose that after you've studied the example above, you are still convinced that invoking ls is a good idea, and because your application runs only on UNIX and Windows systems, you think that with a slight tweak, you can make this code portable. Specifically, you decide to somehow detect what kind of system you're on. If it's a Windows system, you'll use the "dir" command to obtain a directory listing.
Unfortunately, this sort of straightforward substitution won't work. On at least some Windows systems, such as Windows 2000, dir is not an executable command. Instead it's built into the command processor or shell. So in doing the substitution for ls, you need to say:
cmd /c dir
which means "invoke the command processor and execute a single command (dir) and then exit the command processor". On both UNIX and Windows systems, many commands are shell built-ins, and so this technique is required.
Let's look at another example to illustrate these ideas:
import java.io.IOException;
public class ExecDemo2 {
static void doExec1() throws IOException {
// use pipes and I/O redirection
// but without using a shell
Runtime runtime = Runtime.getRuntime();
runtime.exec("ls | wc >out1");
}
static void doExec2() throws IOException {
// invoke a shell and give command to it
Runtime runtime = Runtime.getRuntime();
String[] args =
new String[]{"sh", "-c", "ls | wc >out2"};
Process p = runtime.exec(args);
}
public static void main(String[] args) throws IOException {
doExec1();
doExec2();
}
}
|
In this ExecDemo2 program, the command to be executed is:
ls | wc >out1
This is UNIX shell syntax which says "run the ls command, direct its output to the wc command, and direct the output of the wc command to a file out1". The wc command counts the numbers of lines, words, and characters in its input.
When you run ExecDemo2, out1 is not created, and doExec1 fails. However doExec2 succeeds, and the contents of out2 are something like this:
13 13 168
The problem with doExec1 is that Runtime.exec invokes actual executable binary programs. Syntax such as | and > are part of a particular command processor, and are only understood by that processor. ls is executed, but the rest of the shell command is not. However, doExec2 succeeds because it invokes a command processor or shell (sh), and gives the shell the input containing the | and > characters. Note that Runtime.exec could have been invoked with a single string argument like this:
"sh -c 'ls | wc >out2'"
However StringTokenizer will split this string apart without observing the single inner quotes.
These examples serve to illustrate the type of issues you will encounter if you use Runtime.exec. It's a good idea to ask yourself some hard questions before you start using this feature. In particular, you need to determine whether there's a way to accomplish the same end using standard Java features only.
Runtime.exec makes the most sense when you're invoking a large, complex application such as a word processor or web browser, that is, an application that isn't feasible to implement using Java features.
For more information about using Runtime.exec, see section 18.2, Creating Processes, in "The Java Programming Language Third Edition" by Arnold, Gosling, and Holmes.
PROGRAMMING WITH FILE ATTRIBUTES
A file attribute is a characteristic of a file, for example its read and write permissions, its length, or whether it is a directory. The java.io.File class supports setting and querying file attributes. This tip looks at some of these attributes.
Java file attributes are a subset of what is available on a particular system like UNIX or Windows. There are some attributes that an underlying system such as UNIX supports that the Java system does not. At the same time, using the attributes made available in the File class means that your applications are more portable than if you use underlying facilities.
Another general point about Java file attributes concerns security. The techniques illustrated in this tip might be under the control of a security manager (assuming one is installed). This means that you might not be allowed to use the techniques. For example, in order to directly set the modification time on a file, your program must be able to pass the security checks of the security manager's checkWrite method.
The first type of attribute is one you might consider obvious: the name of a file. But there are a couple of interesting things to consider here. Let's look at an example:
import java.io.*;
public class AttrDemo1 {
public static void main(String[] args)
throws IOException {
File testfile = new File("." +
File.separatorChar + "testfile1");
testfile.createNewFile();
System.out.println(
"name = " + testfile.getName());
System.out.println(
"path = " + testfile.getPath());
System.out.println("absolute path = " +
testfile.getAbsolutePath());
System.out.println("canonical path = " +
testfile.getCanonicalPath());
}
}
|
The AttrDemo1 program creates a file testfile1 with a contrived pathname. The pathname has a ./ or .\ in front of the name, to refer to the current directory. A single dot (".") refers to the current directory on UNIX and Windows systems, but isn't necessarily supported on other systems. But this particular example assumes that a file pathname makes use of this convention.
If you run the program on a Windows systems, the output should look something like this:
name = testfile1
path = .\testfile1
absolute path = G:\JOB\work\.\testfile1
canonical path = G:\JOB\work\testfile1
The first line of output is the result of calling File.getName, and is the last name in a file's pathname. The second line comes from the File.getPath method, and is the pathname of the file. The pathname is a series of directories with separators like / or \ between them, ending with a final name. File.pathSeparatorChar specifies the separator character for a given system.
The third and fourth lines are absolute pathnames. These are typically determined by resolving against a current working directory. In this example, the current directory is G:\JOB\work.
Every pathname that specifies an existing directory or file has a unique canonical form. The canonical form is typically derived by eliminating . and .. in the pathname, resolving symbolic links, and converting drive letters (in Windows) to a standard case. In the AttrDemo1 example, the redundant \.\ in the pathname is converted to \.
Let's look at another kind of attribute: file modification times. Here's an example:
import java.io.*;
import java.util.*;
public class AttrDemo2 {
public static void main(String[] args)
throws IOException {
File testfile = new File("testfile2");
testfile.delete();
testfile.createNewFile();
long modtime = testfile.lastModified();
System.out.println(
"last modification time #1 = " +
new Date(modtime));
testfile.setLastModified(0);
modtime = testfile.lastModified();
System.out.println(
"last modification time #2 = " +
new Date(modtime));
}
}
|
The output looks something like this:
last modification time #1 = Wed Feb 12 14:05:34 MST 2003
last modification time #2 = Wed Dec 31 17:00:00 MST 1969
The lastModified method returns the time in standard Java format. This is the number of milliseconds since 1/1/1970 0000 GMT. The second line of output above shows the result of setting the time to 0. The result references the year 1969, because MST (Mountain Standard Time) has a -0700 correction to GMT.
The File.length method is used to find the length of a file. Normally you might not think of file length as an attribute you can set, but it is possible using RandomAccessFile. Here's some code that shows how to do this:
import java.io.*;
public class AttrDemo3 {
public static void main(String[] args)
throws IOException {
File testfile = new File("testfile3");
testfile.delete();
testfile.createNewFile();
System.out.println(
"length #1 = " + testfile.length());
RandomAccessFile raf =
new RandomAccessFile(
"testfile3", "rw");
raf.setLength(100);
raf.close();
System.out.println("length #2 = " +
testfile.length());
}
}
|
The output is:
length #1 = 0
length #2 = 100
This approach might be useful in a case where you want to preallocate a certain number of fixed-length records in a file.
Another area that's important is read and write permissions on a file. For example, you might want to mark a file as being read-only, so that it cannot be written. Here's an example that illustrates this point:
import java.io.*;
public class AttrDemo4 {
public static void main(String[] args)
throws IOException {
File testfile = new File("testfile4");
testfile.delete();
testfile.createNewFile();
if (testfile.canRead()) {
System.out.println(
"file can be read #1");
}
if (testfile.canWrite()) {
System.out.println(
"file can be written #1");
}
testfile.setReadOnly();
if (testfile.canRead()) {
System.out.println(
"file can be read #2");
}
if (testfile.canWrite()) {
System.out.println(
"file can be written #2");
}
}
}
|
The output is:
file can be read #1
file can be written #1
file can be read #2
The program creates a file. Initially it's possible to both read and write the file. Then the program makes the file read-only. The canRead method returns true, but canWrite returns false.
There is also a File.isHidden method, to check whether a file has its hidden attribute set. For UNIX systems, a file is considered hidden if its name starts with a dot ("."). On Windows systems, there is a specific Windows file system attribute that you can set to mark a file as hidden.
Let's look at a final example. A common programming task is to go through a file directory structure on disk and enumerate all the files found in that structure. One attribute that hasn't been discussed is the contents of a directory. If a File object refers to a directory, then that directory has entries in it, that is, names of other directories and files. It's possible to recursively go through these entries and make a list of all the pathnames that are found.
Here's what the code looks like:
import java.io.*;
import java.util.*;
class DirTreeWalker {
private List filelist = new ArrayList();
private void getFiles(File file)
throws IOException {
if (file.isDirectory()) {
String[] entries = file.list();
int maxlen = (entries ==
null ? 0 : entries.length);
for (int i = 0; i < maxlen; i++) {
getFiles(
new File(file, entries[i]));
}
}
else if (file.isFile()) {
filelist.add(
file.getCanonicalPath());
}
}
public DirTreeWalker(String filename)
throws IOException {
getFiles(new File(filename));
}
public List getList() {
return filelist;
}
}
public class AttrDemo5 {
public static void main(String[] args)
throws IOException {
if (args.length != 1) {
System.err.println(
"missing argument");
System.exit(1);
}
DirTreeWalker dtw =
new DirTreeWalker(args[0]);
List list = dtw.getList();
for (int i = 0, size = list.size();
i < size; i++) {
System.out.println(list.get(i));
}
}
}
|
A typical invocation of this program is:
java AttrDemo5 /techtips
for UNIX systems, and
java AttrDemo5 C:\
for Windows systems. The program displays all the file pathnames under the specified starting directory. Here are a few typical lines of output:
C:\arcldr.exe
C:\arcsetup.exe
C:\AUTOEXEC.BAT
C:\boot.ini
In this program, the getFiles method is called with a File object. If the File object refers to a plain file, its canonical path is added to the ArrayList list. If it's a directory, then File.list is called to get the contents of the directory. For each entry in the directory, a new File object is constructed based on the current path passed in to getFiles and the entry in the directory. Then getFiles is called recursively with this object.
You should not assume that if isDirectory is false, then isFile is true, and vice versa. If the File object references a nonexistent file, neither is true. Also, it's worth noting that the directory attribute of a file is not something you can directly set -- it's set for you by virtue of creating a directory using File.mkdir.
For more information about programming with file attributes, see section 15.6.3, The File Class, in "The Java Programming Language Third Edition" by Arnold, Gosling, and Holmes.
IMPORTANT: Please read our Terms of Use, Privacy, and Licensing
policies:
http://www.sun.com/share/text/termsofuse.html
http://www.sun.com/privacy/
http://developer.java.sun.com/berkeley_license.html
Comments? Send your feedback on the Core Java Technologies Tech Tips to: jdc-webmaster@sun.com
Subscribe to other Java developer Tech Tips:
- Enterprise Java Technologies Tech Tips. Get tips on using enterprise Java technologies and APIs, such as those in the Java 2 Platform, Enterprise Edition (J2EE).
- Wireless Developer Tech Tips. Get tips on using wireless Java technologies and APIs, such as those in the Java 2 Platform, Micro Edition (J2ME).
To subscribe to these and other JDC publications:
- Go to the JDC Newsletters and Publications page, choose the newsletters you want to subscribe to and click "Update".
- To unsubscribe, go to the subscriptions page, uncheck the appropriate checkbox, and click "Update".
ARCHIVES: You'll find the Core Java Technologies Tech Tips archives at:
http://java.sun.com/jdc/TechTips/index.html
Copyright 2003 Sun Microsystems, Inc. All rights reserved.
4150 Network Circle, Santa Clara, CA 95054 USA.
This document is protected by copyright. For more information, see:
http://java.sun.com/jdc/copyright.html
Sun, Sun Microsystems, Java, Java Developer Connection, J2SE, J2EE, and J2ME are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries.
UNIX is a registered trademark in the United States and other countries, exclusively licensed through X/Open Company, Ltd. UNIX est unemarque enregistree aux Etats-Unis et dans d'autres pays e licenciee exclusivement par X/Open Company Ltd.
|