Chapter 2. Interacting with the Environment

2.0 Introduction

This chapter describes how your Java program can deal with its immediate surroundings with what we call the runtime environment. In one sense, everything you do in a Java program using almost any Java API involves the environment. Here we focus more narrowly on things that directly surround your program. Along the way we’ll be introduced to the System class, which knows a lot about your particular system.

Two other runtime classes deserve brief mention. The first, java.lang.Runtime, lies behind many of the methods in the System class. System.exit(), for example, just calls Runtime.exit(). Runtime is technically part of the environment, but the only time we use it directly is to run other programs, which is covered in Recipe 18.1.

2.1 Getting Environment Variables

Problem

You want to get the value of environment variables from within your Java program.

Solution

Use System.getenv().

Discussion

The seventh edition of Unix, released in 1979, had a new feature known as environment variables. Environment variables are in all modern Unix systems (including macOS) and in most later command-line systems, such as the DOS or Command Prompt in Windows, but they are not in some older platforms or other Java runtimes. Environment variables are commonly used for customizing an individual computer user’s runtime environment, hence the name. To take one familiar example, on Unix or DOS the environment variable PATH determines where the system looks for executable programs. So, of course people want to know how they access environment variables from their Java program.

The answer is that you can do this in all modern versions of Java, but you should exercise caution in depending on being able to specify environment variables because some rare operating systems may not provide them. That said, it’s unlikely you’ll run into such a system because all “standard” desktop systems provide them at present.

In some ancient versions of Java, System.getenv() was deprecated and/or just didn’t work. Nowadays the getenv() method is no longer deprecated, though it still carries the warning that System Properties (see Recipe 2.2) should be used instead. Even among systems that support environment variables, their names are case sensitive on some platforms and case insensitive on others. The code in Example 2-1 is a short program that uses the getenv() method.

Example 2-1. main/src/main/java/environ/GetEnv.java
public class GetEnv {
    public static void main(String[] argv) {
        System.out.println("System.getenv("PATH") = " + System.getenv("PATH"));
    }
}

Running this code will produce output similar to the following:

C:javasrc>java environ.GetEnv
System.getenv("PATH") = C:windowsin;c:jdk1.8in;c:documents
    and settingsianin
C:javasrc>

The no-argument form of the method System.getenv() returns all the environment variables in the form of an immutable String Map. You can iterate through this map and access all the user’s settings or retrieve multiple environment settings.

Both forms of getenv() require you to have permissions to access the environment, so they typically do not work in restricted environments such as applets.

2.2 Getting Information from System Properties

Problem

You need to get information from the system properties.

Solution

Use System.getProperty() or System.getProperties().

Discussion

What is a property anyway? A property is just a name and value pair stored in a java.util.Properties object, which we discuss more fully in Recipe 7.10.

The System.Properties object controls and describes the Java runtime. The System class has a static Properties member whose content is the merger of operating system specifics (os.name, for example), system and user tailoring (java.class.path), and properties defined on the command line (as we’ll see in a moment). Note that the use of periods in these names (like os.arch, os.version, java.class.path, and java.lang.version) makes it look as though there is a hierarchical relationship similar to that for package/class names. The Properties class, however, imposes no such relationships: each key is just a string, and dots are not special.

To view all the defined system properties, you can iterate through the output of calling System.getProperties() as in Example 2-2.

Example 2-2. jshell System.getProperties
jshell> System.getProperties().forEach((k,v) -> System.out.println(k + "->" +v))
awt.toolkit->sun.awt.X11.XToolkit
java.specification.version->11
sun.cpu.isalist->
sun.jnu.encoding->UTF-8
java.class.path->.
java.vm.vendor->Oracle Corporation
sun.arch.data.model->64
java.vendor.url->http://java.oracle.com/
user.timezone->
os.name->OpenBSD
java.vm.specification.version->11
... many more ...
jshell>

Remember that properties whose names begin with “sun” are unsupported and subject to change.

To retrieve one system-provided property, use System.getProperty(propName). If I just wanted to find out if the System Properties had a property named "pencil_color", I could say:

        String sysColor = System.getProperty("pencil_color");

But what does that return? Surely Java isn’t clever enough to know about everybody’s favorite pencil color? Right you are! But we can easily tell Java about our pencil color (or anything else we want to tell it) using the -D argument.

When starting a Java runtime, you can define a value in the system properties object using a -D option. Its argument must have a name, an equals sign, and a value, which are parsed the same way as in a properties file (see Recipe 7.10). You can have more than one -D definition between the java command and your class name on the command line. At the Unix or Windows command line, type:

java -D"pencil_color=Deep Sea Green" environ.SysPropDemo

When running this under an IDE, put the variable’s name and value in the appropriate dialog box, for example, in Eclipse’s “Run Configuration” dialog under “Program Arguments”. You can also set environment variables and system properties using the build tools (Maven, Gradle, etc.).

The SysPropDemo program has code to extract just one or a few properties, so you can run it like this:

$ java environ.SysPropDemo os.arch
os.arch = x86

If you invoke the SysPropDemo program with no arguments, it outputs the same information as the jshell fragment in Example 2-2.

Which reminds me—this is a good time to mention system-dependent code. Recipe 2.3 talks about OS-dependent code and release-dependent code.

See Also

Recipe 7.10 lists more details on using and naming your own Properties files. The javadoc page for java.util.Properties lists the exact rules used in the load() method, as well as other details.

2.3 Dealing with Code That Depends on the Java Version or the Operating System

Problem

You need to write code that adapts to the underlying operating system.

Solution

You can use System.Properties to find out the Java version and the operating system, various features in the File class to find out some platform-dependent features, and java.awt.TaskBar to see if you can use the system-dependent Taskbar or Dock.

Discussion

Some things depend on the version of Java you are running. Use System.getProperty() with an argument of java.specification.version.

Alternatively, and with greater generality, you may want to test for the presence or absence of particular classes. One way to do this is with Class.forName("class") (see Chapter 17), which throws an exception if the class cannot be loaded—a good indication that it’s not present in the runtime’s library. Example 2-3 shows code for this, from an application wanting to find out whether the common Swing UI components are available. The javadoc for the standard classes reports the version of the JDK in which this class first appeared, under the heading “Since.” If there is no such heading, it normally means that the class has been present since the beginnings of Java:

Example 2-3. main/src/main/java/starting/CheckForSwing.java
public class CheckForSwing {
    public static void main(String[] args) {
        try {
            Class.forName("javax.swing.JButton");
        } catch (ClassNotFoundException e) {
            String failure =
                "Sorry, but this version of MyApp needs 
" +
                "a Java Runtime with JFC/Swing components
" +
                "having the final names (javax.swing.*)";
            // Better to make something appear in the GUI. Either a
            // JOptionPane, or: myPanel.add(new Label(failure));
            System.err.println(failure);
        }
        // No need to print anything here - the GUI should work...
    }
}

It’s important to distinguish between testing this code at compile time and at runtime. In both cases, it must be compiled on a system that includes the classes you are testing for: JDK >= 1.1 and Swing, respectively. These tests are only attempts to help the poor backwater Java runtime user trying to run your up-to-date application. The goal is to provide this user with a message more meaningful than the simple “class not found” error that the runtime gives. It’s also important to note that this test becomes unreachable if you write it inside any code that depends on the code you are testing for. Put the test early in the main flow of your application, before any GUI objects are constructed. Otherwise the code just sits there wasting space on newer runtimes and never gets run on Java systems that don’t include Swing. Obviously this is a very early example, but you can use the same technique to test for any runtime feature added at any stage of Java’s evolution (see Appendix A for an outline of the features added in each release of Java). You can also use this technique to determine whether a needed third-party library has been successfully added to your classpath.

Also, although Java is designed to be portable, some things aren’t. These include such variables as the filename separator. Everybody on Unix knows that the filename separator is a slash character (/) and that a backward slash, or backslash (), is an escape character. Back in the late 1970s, a group at Microsoft was actually working on Unix—their version was called Xenix, later taken over by SCO—and the people working on DOS saw and liked the Unix filesystem model. The earliest versions of MS-DOS didn’t have directories; it just had user numbers like the system it was a clone of, Digital Research CP/M (itself a clone of various other systems). So the Microsoft developers set out to clone the Unix filesystem organization. Unfortunately, MS-DOS had already committed the slash character for use as an option delimiter, for which Unix had used a dash (-); and the PATH separator (:) was also used as a drive letter delimiter, as in C: or A:. So we now have commands like those shown in Table 2-1.

Table 2-1. Directory listing commands
System Directory list command Meaning Example PATH setting

Unix

ls -R /

Recursive listing of /, the top-level directory

PATH=/bin:/usr/bin

DOS

dir/s

Directory with subdirectories option (i.e., recursive) of , the top-level directory (but only of the current drive)

PATH=C:windows;D:mybin

Where does this get us? If we are going to generate filenames in Java, we may need to know whether to put a / or a or some other character. Java has two solutions to this. First, when moving between Unix and Microsoft systems, at least, it is permissive: either / or can be used,1 and the code that deals with the operating system sorts it out. Second, and more generally, Java makes the platform-specific information available in a platform-independent way. For the file separator (and also the PATH separator), the java.io.File class makes available some static variables containing this information. Because the File class manages platform dependent information, it makes sense to anchor this information here. The variables are shown in Table 2-2.

Table 2-2. File properties
Name Type Meaning

separator

static String

The system-dependent filename separator character (e.g., / or )

separatorChar

static char

The system-dependent filename separator character (e.g., / or )

pathSeparator

static String

The system-dependent path separator character, represented as a string for convenience

pathSeparatorChar

static char

The system-dependent path separator character

Both filename and path separators are normally characters, but they are also available in String form for convenience.

A second, more general, mechanism is the system Properties object mentioned in Recipe 2.2. You can use this to determine the operating system you are running on. Here is code that simply lists the system properties; it can be informative to run this on several different implementations:

public class SysPropDemo {
    public static void main(String[] argv) throws IOException {
        if (argv.length == 0)
            // tag::sysprops[]
            System.getProperties().list(System.out);
            // end::sysprops[]
        else {
            for (String s : argv) {
                System.out.println(s + " = " +
                    System.getProperty(s));
            }
        }
    }
}

Some OSes, for example, provide a mechanism called the null device that can be used to discard output (typically used for timing purposes). Here is code that asks the system properties for the os.name and uses it to make up a name that can be used for discarding data (if no null device is known for the given platform, we return the name jnk, which means that on such platforms, we’ll occasionally create, well, junk files; I just remove these files when I stumble across them):

package com.darwinsys.lang;

import java.io.File;

/** Some things that are system-dependent.
 * All methods are static.
 * @author Ian Darwin
 */
public class SysDep {

    final static String UNIX_NULL_DEV = "/dev/null";
    final static String WINDOWS_NULL_DEV = "NUL:";
    final static String FAKE_NULL_DEV = "jnk";

    /** Return the name of the null device on platforms which support it,
     * or "jnk" (to create an obviously well-named temp file) otherwise.
     * @return The name to use for output.
     */
    public static String getDevNull() {

        if (new File(UNIX_NULL_DEV).exists()) {     1
            return UNIX_NULL_DEV;
        }

        String sys = System.getProperty("os.name"); 2
        if (sys==null) {                            3
            return FAKE_NULL_DEV;
        }
        if (sys.startsWith("Windows")) {            4
            return WINDOWS_NULL_DEV;
        }
        return FAKE_NULL_DEV;                       5
    }
}
1

If /dev/null exists, use it.

2

If not, ask System.properties if it knows the OS name.

3

Nope, so give up, return jnk.

4

We know it’s Microsoft Windows, so use NUL:.

5

All else fails, go with jnk.

Although Java’s Swing GUI aims to be portable, Apple’s implementation for macOS does not automatically do the right thing for everyone. For example, a JMenuBar menu container appears by default at the top of the application window. This is the norm on Windows and on most Unix platforms, but Mac users expect the menu bar for the active application to appear at the top of the screen. To enable normal behavior, you have to set the System property apple.laf.useScreenMenuBar to the value true before the Swing GUI starts up. You might want to set some other properties too, such as a short name for your application to appear in the menu bar (the default is the full class name of your main application class).

There is an example of this in the book’s source code, at src/main/java/gui/MacOsUiHints.java.

There is probably no point in setting these properties unless you are, in fact, being run under macOS. How do you tell? Apple’s recommended way is to check for the system property mrj.runtime and, if so, assume you are on macOS:

boolean isMacOS = System.getProperty("mrj.version") != null;
if (isMacOS) {
  System.setProperty("apple.laf.useScreenMenuBar",  "true");
  System.setProperty("com.apple.mrj.application.apple.menu.about.name",
  "My Super App");
}

On the other hand, these properties are likely harmless on non-Mac systems, so you could just skip the test and set the two properties unconditionally.

Finally, the Mac’s Dock or the TaskBar on most other systems can be accessed using the java.awt.Taskbar class that was added in Java 9. This is not discussed here, but there is an example TaskbarDemo in the gui subdirectory.

2.4 Using Extensions or Other Packaged APIs

Problem

You have a JAR file of classes you want to use.

Solution

Simply add the JAR file to your CLASSPATH.

Discussion

As you build more sophisticated applications, you will need to use more and more third-party libraries. You can add these to your CLASSPATH.

It used to be recommended that you drop these JAR files into the Java Extensions mechanism directory, typically something like jdk1.xjrelibext., instead of listing each JAR file in your CLASSPATH variable. However, this is no longer generally recommended and is no longer available in the latest JDKs. Instead, you may wish to use build tools like Maven (see Recipe 1.7) or Gradle, as well as IDEs, to automate the addition of JAR files to your classpath.

One reason I’ve never been fond of using the extensions directory is that it requires modifying the installed JDK or JRE, which can lead to maintenance issues and problems when a new JDK or JRE is installed.

Java 9 introduced a major change to Java, the Java 9 Modules System for program modularization, which we discuss in Recipe 2.5.

2.5 Using the Java Modules System

Problem

You are using Java 9 or later, and need to deal with the Modules mechanism.

Solution

Read on.

Discussion

Java’s Modules System, formerly known as Project Jigsaw, was designed to handle the need to build large applications out of many small pieces. To an extent this problem had been solved by tools like Maven and Gradle, but the Modules system solves a slightly different problem than those tools. Maven or Gradle will find dependencies, download them, install them on your development and test runtimes, and package them into runnable JAR files. The Modules system is more concerned with the visbility of classes from one chunk of application code to another, typically provided by different developers who may not know or trust each other. As such, it is an admission that Java’s original set of access modifiers—such as public, private, protected, and default visibility—was not sufficient for building large-scale applications.

What follows is a brief discussion of using JPMS, the Java Platform Module System, to import modules into your application. There is an introduction to creating your own modules in Chapter 15. For a more detailed presentation, you should refer to a book-length treatment such as Java 9 Modularity: Patterns and Practices for Developing Maintainable Applications by Sander Mak and Paul Bakker (O’Reilly).

Java has always been a language for large-scale development. Object orientation is one of the keys: classes and objects group methods, and access modifiers can be applied so that public and private methods are clearly separated. When developing large applications, having just a single flat namespace of classes is still not enough. Enter packages: they gather classes into logical groups within their own namespace. Access control can be applied at the package level as well so that some classes are only accessible inside a package. Modules are the next logical step up. A module groups some number of related packages, has a distinct name, and can restrict access to some packages while exposing other packages to different modules as public API.

One thing to understand at the outset: JPMS is not a replacement for your existing build tool. Whether you use Maven, Gradle, Ant, or just dump all needed JAR files into a lib directory, you still need to do that. Also, don’t confuse Maven’s modules with JPMS modules; the former is the physical structuring of a project into subprojects, and the latter is something the Java platform (compiler, runtime) understands. Usually when working with Java modules, each Java module will equate to a single Maven module.

When you’re dealing with a tiny, self-contained program, you don’t need to be concerned with modules. Just put all the necessary JAR files on your classpath at compile time and runtime, and all will be well. Probably.

You may see warning messages like this along the way:

Illegal reflective access by com.foo.Bar
    (file:/Users/ian/.m2/repository/com/foo/1.3.1/foo-1.3.1.jar)
    to field java.util.Properties.defaults
Please consider reporting this to the maintainers of com.foo.Bar
Use --illegal-access=warn to enable warnings of further illegal reflective access operations
All illegal access operations will be denied in a future release

The warning message comes about as a result of JPMS doing its job, checking that no types are accessed in encapsulated packages within a module. Such messages will go away over time as all public Java libraries and all apps being developed get modularized.

Why will all be well only “probably”? If you are using certain classes that were deprecated over the last few releases, things won’t compile. For that, you must make the requisite modules available. In the unsafe subdirectory (also a Maven module) under javasrc, there is a class called LoadAverage. The load average is a feature of Unix/Linux systems that gives a rough measure of system load or busyness, by reporting the number of processes that are waiting to be run. There are almost always more processes running than CPU cores to run them on, so some always have to wait. Higher numbers mean a busier system with slower response.

Sun’s unsupported Unsafe class has a method for obtaining the load average, on systems that support it. The code has to use the Reflection API (see Chapter 17) to obtain the Unsafe object; if you try to instantiate Unsafe directly you will get a SecurityException (this was the case before the Modules system). Once the instance is obtained and casted to Unsafe, you can invoke methods such as loadAverage() (Example 2-4).

Example 2-4. unsafe/src/main/java/unsafe/LoadAverage.java (use of Unsafe.java)
public class LoadAverage {
    public static void main(String[] args) throws Exception {
        Field f = Unsafe.class.getDeclaredField("theUnsafe");
        f.setAccessible(true);
        Unsafe unsafe = (Unsafe) f.get(null);
        int nelem = 3;
        double loadAvg[] = new double[nelem];
        unsafe.getLoadAverage(loadAvg, nelem);
        for (double d : loadAvg) {
            System.out.printf("%4.2f ", d);
        }
        System.out.println();
    }
}

This code, which used to compile, gives warnings. If we are using Java Modules, we must modify our module-info.java file to tell the compiler and VM that we require use of the module with the semi-obvious name jdk.unsupported.

module javasrc.unsafe {
    requires jdk.unsupported;
	// others...
}

We’ll say more about the module file format in Recipe 15.9).

Now that we have the code in place and the module file in the top level of the source folder, we can build the project, run the program, and compare its output against the system-level tool for displaying the load average, uptime. We’ll still get the “internal proprietary API” warnings, but it works:

$ java -version
openjdk version "14-ea" 2020-03-17
OpenJDK Runtime Environment (build 14-ea+27-1339)
OpenJDK 64-Bit Server VM (build 14-ea+27-1339, mixed mode, sharing)
$ mvn clean package
[INFO] Scanning for projects...
[INFO]
[INFO] --------------------< com.darwinsys:javasrc-unsafe >--------------------
[INFO] Building javasrc - Unsafe 1.0.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ javasrc-unsafe ---
[INFO] Deleting /Users/ian/workspace/javasrc/unsafe/target
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ javasrc-unsafe ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory /Users/ian/workspace/javasrc/unsafe/src/main/resources
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ javasrc-unsafe ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 2 source files to /Users/ian/workspace/javasrc/unsafe/target/classes
[WARNING] /Users/ian/workspace/javasrc/unsafe/src/main/java/unsafe/LoadAverage.java:[3,16] sun.misc.Unsafe is internal proprietary API and may be removed in a future release
[WARNING] /Users/ian/workspace/javasrc/unsafe/src/main/java/unsafe/LoadAverage.java:[12,27] sun.misc.Unsafe is internal proprietary API and may be removed in a future release
[WARNING] /Users/ian/workspace/javasrc/unsafe/src/main/java/unsafe/LoadAverage.java:[14,17] sun.misc.Unsafe is internal proprietary API and may be removed in a future release
[WARNING] /Users/ian/workspace/javasrc/unsafe/src/main/java/unsafe/LoadAverage.java:[14,34] sun.misc.Unsafe is internal proprietary API and may be removed in a future release
[INFO]
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ javasrc-unsafe ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory /Users/ian/workspace/javasrc/unsafe/src/test/resources
[INFO]
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ javasrc-unsafe ---
[INFO] No sources to compile
[INFO]
[INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ javasrc-unsafe ---
[INFO] No tests to run.
[INFO]
[INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ javasrc-unsafe ---
[INFO] Building jar: /Users/ian/workspace/javasrc/unsafe/target/javasrc-unsafe-1.0.0-SNAPSHOT.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  4.668 s
[INFO] Finished at: 2020-01-05T14:53:55-05:00
[INFO] ------------------------------------------------------------------------
$
$ java -cp target/classes unsafe/LoadAverage
3.54 1.94 1.62
$ uptime
14:54  up 1 day, 21:50, 5 users, load averages: 3.54 1.94 1.62
$

Thankfully, it works and gives the same numbers as the standard Unix uptime command. At least, it works on Java 11. As the warnings imply, it may (i.e., probably will) be removed in a later release.

If you are building a more complex app, you will probably need to put together a more complete module-info.java file. But at this stage it’s primarily a matter of requiring the modules you need. The standard Java API is divided into several modules, which you can list using the java command:

$ java --list-modules
java.base
java.compiler
java.datatransfer
java.desktop
java.instrument
java.logging
java.management
java.management.rmi
java.naming
java.net.http
java.prefs
java.rmi
java.scripting
java.se
java.security.jgss
java.security.sasl
java.smartcardio
java.sql
java.sql.rowset
java.transaction.xa
java.xml
java.xml.crypto
... plus a bunch of JDK modules ...

Of these, java.base is always available and doesn’t need to be listed in your module file, java.desktop adds AWT and Swing for graphics, and java.se includes basically all of what used to be public API in the Java SDK. If our Load Average program wanted to display the result in a Swing window, for example, it would need to add this into its module file:

requires java.desktop;

When your application is big enough to be divided into tiers or layers, you will probably want to describe these modules using JPMS. Since that topic comes under the heading of packaging, it is described in Recipe 15.9.

1 When compiling strings for use on Windows, remember to double them because is an escape character in most places other than the MS-DOS command line: String rootDir = "C:\";.

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

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