Team LiB
Previous Section Next Section

5.17. Miscellaneous Platform Features

The following sections detail important but miscellaneous features of the Java platform, including properties, preferences, processes, and management and instrumentation.

5.17.1. Properties

java.util.Properties is a subclass of java.util.Hashtable, a legacy collections class that predates the Collections API introduced in Java 1.2. A Properties object maintains a mapping between string keys and string values and defines methods that allow the mappings to be written to and read from a simple text file or (in Java 5.0) an XML file. This makes the Properties class ideal for configuration and user preference files. The Properties class is also used for the system properties returned by System.getProperty( ):

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

// Note: many of these system properties calls throw a security exception if
// called from untrusted code such as applets.
String homedir = System.getProperty("user.home"); // Get a system property
Properties sysprops = System.getProperties();     // Get all system properties

// Print the names of all defined system properties
for(Enumeration e = sysprops.propertyNames(); e.hasMoreElements();) 
  System.out.println(e.nextElement());

sysprops.list(System.out); // Here's an even easier way to list the properties

// Read properties from a configuration file
Properties options = new Properties();             // Empty properties list
File configfile = new File(homedir, ".config");    // The configuration file
try {
  options.load(new FileInputStream(configfile));   // Load props from the file
} catch (IOException e) { /* Handle exception here */ }

// Query a property ("color"), specifying a default ("gray") if undefined
String color = options.getProperty("color", "gray");  

// Set a property named "color" to the value "green"
options.setProperty("color", "green");  

// Store the contents of the Properties object back into a file
try {
  options.store(new FileOutputStream(configfile),  // Output stream
                "MyApp Config File");              // File header comment text
} catch (IOException e) { /* Handle exception */ }

// In Java 5.0 properties can be written to or read from XML files
try {
  options.storeToXML(new FileOutputStream(configfile),  // Output stream
                     "MyApp Config File");              // Comment text
  options.loadFromXML(new FileInputStream(configfile)); // Read it back in
}
catch(IOException e) { /* Handle exception */ }
catch(InvalidPropertiesFormatException e) { /* malformed input */ }

5.17.2. Preferences

Java 1.4 introduced the Preferences API, which is specifically tailored for working with user and systemwide preferences and is more useful than Properties for this purpose. The Preferences API is defined by the java.util.prefs package. The key class in that package is Preferences. You can obtain a Preferences object that contains user-specific preferences with the static method Preferences.userNodeForPackage() and obtain a Preferences object that contains systemwide preferences with Preferences.systemNodeForPackage(). Both methods take a java.lang.Class object as their sole argument and return a Preferences object shared by all classes in that package. (This means that the preference names you use must be unique within the package.) Once you have a Preferences object, use the get( ) method to query the string value of a named preference, or use other type-specific methods such as getInt( ), getBoolean(), and getByteArray(). Note that to query preference values, a default value must be passed for all methods. This default value is returned if no preference with the specified name has been registered or if the file or database that holds the preference data cannot be accessed. A typical use of Preferences is the following:

package com.davidflanagan.editor;
import java.util.prefs.Preferences;

public class TextEditor {
  // Fields to be initialized from preference values
  public int width;             // Screen width in columns
  public String dictionary;     // Dictionary name for spell checking

  public void initPrefs() {
    // Get Preferences objects for user and system preferences for this package
    Preferences userprefs = Preferences.userNodeForPackage(TextEditor.class);
    Preferences sysprefs = Preferences.systemNodeForPackage(TextEditor.class);

    // Look up preference values. Note that you always pass a default value.
    width = userprefs.getInt("width", 80);
    // Look up a user preference using a system preference as the default
    dictionary = userprefs.get("dictionary",
                               sysprefs.get("dictionary",
                                            "default_dictionary"));
  }
}

In addition to the get( ) methods for querying preference values, there are corresponding put() methods for setting the values of named preferences:

// User has indicated a new preference, so store it
userprefs.putBoolean("autosave", false);

If your application wants to be notified of user or system preference changes while the application is in progress, it may register a PreferenceChangeListener with addPreferenceChangeListener(). A Preferences object can export the names and values of its preferences as an XML file and can read preferences from such an XML file. (See importPreferences( ), exportNode(), and exportSubtree( ) in java.util.pref.Preferences in the reference section.) Preferences objects exist in a hierarchy that typically corresponds to the hierarchy of package names. Methods for navigating this hierarchy exist but are not typically used by ordinary applications.

5.17.3. Processes

Earlier in the chapter, we saw how easy it is to create and manipulate multiple threads of execution running within the same Java interpreter. Java also has a java.lang.Process class that represents an operating system process running externally to the interpreter. A Java program can communicate with an external process using streams in the same way that it might communicate with a server running on some other computer on the network. Using a Process is always platform-dependent and is rarely portable, but it is sometimes a useful thing to do:

// Maximize portability by looking up the name of the command to execute
// in a configuration file. 
java.util.Properties config;  
String cmd = config.getProperty("sysloadcmd");
if (cmd != null) {
  // Execute the command; Process p represents the running command
  Process p = Runtime.getRuntime().exec(cmd);         // Start the command
  InputStream pin = p.getInputStream();               // Read bytes from it
  InputStreamReader cin = new InputStreamReader(pin); // Convert them to chars
  BufferedReader in = new BufferedReader(cin);        // Read lines of chars
  String load = in.readLine();                        // Get the command output
  in.close();                                         // Close the stream
}

In Java 5.0 the java.lang.ProcessBuilder class provides a more flexible way to launch new processes than the Runtime.exec() method. ProcessBuilder allows control of environment variables through a Map and makes it simple to set the working directory. It also has an option to automatically redirect the standard error stream of the processes it launches to the standard output stream, which makes it much easier to read all output of a Process.

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

public class JavaShell {
    public static void main(String[] args) {
        // We use this to start commands
        ProcessBuilder launcher = new ProcessBuilder();
        // Our inherited environment vars.  We may modify these below
        Map<String,String> environment = launcher.environment();
        // Our processes will merge error stream with standard output stream
        launcher.redirectErrorStream(true);
        // Where we read the user's input from
        BufferedReader console =
            new BufferedReader(new InputStreamReader(System.in));

        while(true) {
            try {
                System.out.print("> ");               // display prompt
                System.out.flush();                   // force it to show
                String command = console.readLine();  // Read input

                if (command.equals("exit")) return;   // Exit command

                else if (command.startsWith("cd ")) { // change directory
                    launcher.directory(new File(command.substring(3)));
                }

                else if (command.startsWith("set ")) {// set environment var
                    command = command.substring(4);
                    int pos = command.indexOf('=');
                    String name = command.substring(0,pos).trim();
                    String var = command.substring(pos+1).trim();
                    environment.put(name, var);
                }
                    
                else { // Otherwise it is a process to launch
                    // Break command into individual tokens
                    String[] words = command.split(" ");
                    launcher.command(words);      // Set the command
                    Process p = launcher.start(); // And launch a new process

                    // Now read and display output from the process 
                    // until there is no more output to read
                    BufferedReader output = new BufferedReader(
                             new InputStreamReader(p.getInputStream()));
                    String line;
                    while((line = output.readLine()) != null) 
                        System.out.println(line);
                                     
                    // The process should be done now, but wait to be sure.
                    p.waitFor();
                }
            }
            catch(Exception e) {
                System.out.println(e);
            }
        }
    }
}

5.17.4. Management and Instrumentation

Java 5.0 includes the powerful JMX API for remote monitoring and management of running applications. The full javax.management API is beyond the scope of this book. The reference section does cover the java.lang.management package, however: this package is an application of JMX for the monitoring and management of the Java virtual machine itself. java.lang.instrument is another Java 5.0 package: it allows the definition of "agents" that can be used to instrument the running JVM. In VMs that support it, java.lang.instrument can be used to redefine class files as they are loaded to add profiling or coverage testing code, for example. Class redefinition is beyond the scope of this chapter, but the following code uses the new instrumentation and management features of Java 5.0 to determine resource usages of a Java program. The example also demonstrates the Runtime.addShutdownHook() method, which registers code to be run when the VM starts shutting down.

import java.lang.instrument.*;
import java.lang.management.*;
import java.util.List;
import java.io.*;

public class ResourceUsageAgent {
    // A Java agent class defines a premain() method to run before main()
    public static void premain(final String args, final Instrumentation inst) {
       // This agent simply registers a shutdown hook to run when the VM exits
        Runtime.getRuntime().addShutdownHook(new Thread() {
                public void run() {
                    // This code runs when the VM exits
                    try {
                        // Decide where to send our output
                        PrintWriter out;
                        if (args != null && args.length() > 0) 
                            out = new PrintWriter(new FileWriter(args));
                        else
                            out = new PrintWriter(System.err);

                        // Use java.lang.management to query peak thread usage
                        ThreadMXBean tb = ManagementFactory.getThreadMXBean();
                        out.printf("Current thread count: %d%n",
                                   tb.getThreadCount());
                        out.printf("Peak thread count: %d%n",
                                   tb.getPeakThreadCount());

                        // Use java.lang.management to query peak memory usage
                        List<MemoryPoolMXBean> pools = 
                            ManagementFactory.getMemoryPoolMXBeans();
                        for(MemoryPoolMXBean pool: pools) {
                            MemoryUsage peak = pool.getPeakUsage();
                            out.printf("Peak %s memory used: %,d%n",
                                       pool.getName(), peak.getUsed());
                            out.printf("Peak %s memory reserved: %,d%n",
                                       pool.getName(), peak.getCommitted());
                        }

                        // Use the Instrumentation object passed to premain()
                        // to get a list of all classes that have been loaded
                        Class[] loaded = inst.getAllLoadedClasses();
                        out.println("Loaded classes:");
                        for(Class c : loaded) out.println(c.getName());

                        out.close();  // close and flush the output stream
                    }
                    catch(Throwable t) {
                        // Exceptions in shutdown hooks are ignored so
                        // we've got to print this out explicitly
                        System.err.println("Exception in agent: " + t);
                    }
                }
            });
    }
}

To monitor the resource usage of a Java program with this agent, you first must compile the class normally. You then store the generated class files in a JAR file with a manifest that specifies the class that contains the premain() method. Create a manifest file that contains this line:

Premain-Class: ResourceUsageAgent

Create the JAR file with a command like this:

% jar cmf manifest agent.jar ResourceUsageAgent*.class

Finally, to use the agent, specify the JAR file and the agent arguments with the -javaagent flag to the Java interpreter:

% java -javaagent:agent.jar=/tmp/usage.info my.java.Program

    Team LiB
    Previous Section Next Section