Viewing Dependencies – Java Module System

Viewing Dependencies

The Java Dependency Analysis tool, jdeps, is a versatile command-line tool for static analysis of dependencies between Java artifacts like class files and JARs. It can analyze dependencies at all levels: module, package, and class. It allows the results to be filtered and aggregated in various ways, even generating various graphs to illustrate its findings. It is an indispensable tool when migrating non-modular code to make use of the module system.

In this section, we confine our discussion to modular code—that is, either an exploded module directory with the compiled module code (e.g., mods/main) or a modular JAR (e.g., mlib/main.jar).

In this section, the results from some of the jdeps commands have been edited to fit the width of the page without any loss of information, or elided to shorten repetitious outputs.

Viewing Package-Level Dependencies

The jdeps command with no options, shown below, illustrates the default behavior of the tool. Line numbers have been added for illustrative purposes.

When presented with the root module directory mods/model (1), the default behavior of jdeps is to print the name of the module (2), the path to its location (3), its module descriptor (4), followed by its module dependencies (5), and lastly the package-level dependencies (6). The package-level dependency at (6) shows that the package com.passion.model in the model module depends on the java.lang package in the proverbial java.base module.

Click here to view code image

(1) 
>jdeps mods/model

(2)  model
(3)  [file:…/adviceApp/mods/model/]
(4)     requires mandated java.base (@17.0.2)
(5)  model -> java.base
(6)     com.passion.model           -> java.lang           java.base

The following jdeps command if let loose on the model.jar archive will print the same information for the model module, barring the difference in the file location:

>
jdeps mlib/model.jar

However, the following jdeps command with the main.jar archive gives an error:

Click here to view code image

>jdeps mlib/main.jar

Exception in thread “main” java.lang.module.FindException: Module controller
not found, required by main
       at java.base/…

Dependency analysis cannot be performed on the main module by the jdeps tool because the modules the main module depends on cannot be found. In the case of the model module, which does not depend on any other user-defined module, the dependency analysis can readily be performed.

The two options –module-path (no short form) and –module (short form: -m) in the jdeps command below unambiguously specify the location of other modular JARs and the module to analyze, respectively. The format of the output is the same as before, and can be verified easily.

Click here to view code image

>jdeps –module-path mlib –module main
main
 [file:…/adviceApp/mlib/main.jar]
   requires controller
   requires mandated java.base (@17.0.2)
main -> controller
main -> java.base
   com.passion.main     -> com.passion.controller     controller
   com.passion.main     -> java.lang                  java.base

However, if one wishes to analyze all modules that the specified module depends on recursively, the –recursive option (short form: -R) can be specified. The output will be in the same format as before, showing the package-level dependencies for each module. The output from the jdeps command for each module is elided below, but has the same format we have seen previously.

Click here to view code image

>
jdeps –module-path mlib –module main –recursive

controller

main

model

view


Creating Path Objects with the Path.of() Method – Java I/O: Part II

Creating Path Objects with the Path.of() Method

The simplest way to create a Path is to use the static factory method Path.of(String first, String… more). It joins the first string with any strings in the variable arity parameter more to create a path string that is the basis of constructing a Path object. This method creates a Path object in accordance with the default file system.

For instance, the default file system can be queried for the platform-specific name separator for name elements in a path.

The three absolute paths below are equivalent on a Unix platform, as they create a Path object based on the same path string. The nameSeparator string is the platform-specific name separator obtained from the default file system. At (1) and (2) below, only the first string parameter is specified in the Path.of() method, and it is the basis for constructing a Path object. However, specifying the variable arity parameter where possible is recommended when a Path object is constructed as shown at (3), as joining of the name elements is implicitly done using the platform-specific name separator. The equals() methods simply checks for equality on the path string, not on any directory entry denoted by the Path objects.

Click here to view code image

FileSystem dfs = FileSystems.getDefault();     // Obtain the default file system.
String nameSeparator = dfs.getSeparator();     // The name separator for a path.

Path absPath1 = Path.of(“/a/b/c”);                         // (1) /a/b/c
Path absPath2 = Path.of(nameSeparator + “a” +              // (2) /a/b/c
                        nameSeparator + “b” +
                        nameSeparator + “c”);
Path absPath3 = Path.of(nameSeparator, “a”, “b”, “c”);     // (3) /a/b/c
System.out.println(absPath1.equals(absPath2) &&
                   absPath2.equals(absPath3));             // true

The two absolute paths below are equivalent on a Windows platform, as they create Path objects based on the same path string. Note that the backslash character must be escaped with a second backslash in a string. Otherwise, it will be interpreted as starting an escape sequence (§2.1, p. 38).

Click here to view code image

Path absPath4 = Path.of(“C:\\a\\b\\c”);                    // (4) C:\a\b\c
Path absPath5 = Path.of(“C:”, “a”, “b”, “c”);              // (5) C:\a\b\c

The Path created below is a relative path, as no root component is specified in the arguments.

Click here to view code image

Path relPath1 = Path.of(“c”, “d”);                         //  c/d

Often we need to create a Path object to denote the current directory. This can be done via a system property named “user.dir” that can be looked up, as shown at (1) below, and its value used to construct a Path object, as shown at (2). The path string of the current directory can be used to create paths relative to the current directory, as shown at (3).

Click here to view code image

String pathOfCurrDir = System.getProperty(“user.dir”);  // (1)
Path currDir = Path.of(pathOfCurrDir);                  // (2)
Path relPath = Path.of(pathOfCurrDir, “d”);             // (3) <curr-dir-path>/d

Selected Options for the javac Tool – Java Module System

Selected Options for the javac Tool

The Java language compiler, javac, compiles Java source code into Java bytecode. The general form of the javac command is:

javac
[options] [sourcefiles]

Table 19.7 shows some selected options that can be used when compiling modules. The optional sourcefiles is an itemized list of source files, often omitted in favor of using module-related options.

Table 19.7 Selected Options for the javac Tool

OptionDescription
–module-source-path
   
moduleSourcePath

The moduleSourcePath specifies the source directory where the exploded modules with the source code files can be found.
–module-path
modulepath

 -p          
modulepath

The modulepath specifies where the modules needed by the application can be found. This can be a root directory of the exploded modules with the class files or a root directory where the modular JARs can be found. Multiple directories of modules can be specified, separated by a colon (:) on a Unix-based platform and semicolon (;) on the Windows platform.
–module
moduleName

 -m     
moduleName

Specifies the module(s) to be compiled. Can be a single module name or a comma-separated (,) list of module names. For each module specified, all modules it depends on are also compiled, according to the module dependencies.
-d
classesDirectory

Specifies the destination directory for the class files. Mandatory when compiling modules. For classes in a package, their class files are put in a directory hierarchy that reflects the package name, with directories being created as needed. Without the -d option, the class files are put in the same directory as their respective source files. Specifying the directory path is platform dependent: slash (/) on Unix-based platforms and backslash (\) on Windows platforms being used when specifying the directory path.
–add-modules
module,…

Specifies root modules to resolve in addition to the initial modules. These modules can be modular JAR files, JMOD files, or even exploded modules.

Selected Options for the java Tool

The java tool launches an application—that is, it creates an instance of the JVM in which to run the application. A typical command to launch a modular application is by specifying the location of its modules (path) and the entry point of the application (as module or module/mainclass):

Click here to view code image

java –module-path
path
 –module
module[/mainclass]

Table 19.8 shows some selected options that can be used for launching and exploring modular applications.

Table 19.8 Selected Options for the java Tool

OptionDescription
–module-path
modulepath…

 -p          
modulepath

The modulepath specifies the location where the modules needed to run the application can be found. This can be a root directory for the exploded modules with the class files or a directory where the modular JARs can be found. Multiple directories of modules can be specified, separated by a colon (:) on a Unix-based platform and semicolon (;) on Windows platforms.
–module
module
[/
mainclass
]
 -m     
module
[/
mainclass
]

When module/mainclass is specified, it explicitly states the module name and the fully qualified name of the class with the main() method, thereby overriding any other entry point in the application. Without the mainclass, the entry point of the application is given by the module which must necessarily contain the main-class.
–add-modules
module,…

Specifies root modules to resolve in addition to the initial modules.
–list-modules

Only lists the observable modules, and does not launch the application. That is, it lists the modules that the JVM can use when the application is run.
–describe-module
moduleName

 -d              
moduleName

Describes a specified module, in particular its module descriptor, but does not launch the application.
–validate-modules

Validates all modules on the module path to find conflicts and errors within modules.

Input and Output – Java I/O: Part I

20.1 Input and Output

Java provides I/O streams as a general mechanism for dealing with data input and output. I/O streams implement sequential processing of data. An input stream allows an application to read a sequence of data, and an output stream allows an application to write a sequence of data. An input stream acts as a source of data, and an output stream acts as a destination of data. The following entities can act as both input and output streams:

  • A file—which is the focus in this chapter
  • An array of bytes or characters
  • A network connection

There are two categories of I/O streams:

  • Byte streams that process bytes as a unit of data
  • Character streams that process characters as a unit of data

A low-level I/O stream operates directly on the data source (e.g., a file or an array of bytes), and processes the data primarily as bytes.

A high-level I/O stream is chained to an underlying stream, and provides additional capabilities for processing data managed by the underlying stream—for example, processing bytes from the underlying stream as Java primitive values or objects. In other words, a high-level I/O stream acts as a wrapper for the underlying stream.

In the rest of this chapter we primarily explore how to use I/O streams of the standard I/O API provided by the java.io package to read and write various kinds of data that is stored in files.

20.2 Byte Streams: Input Streams and Output Streams

The abstract classes InputStream and OutputStream in the java.io package are the root of the inheritance hierarchies for handling the reading and writing of data as bytes (Figure 20.1). Their subclasses, implementing different kinds of input and output (I/O) streams, override the following methods from the InputStream and OutputStream classes to customize the reading and writing of bytes, respectively:

Figure 20.1 Partial Byte Stream Inheritance Hierarchies in the java.io Package

From the InputStream abstract class:

Click here to view code image

int read() throws IOException
int read(byte[] b) throws IOException
int read(byte[] b, int off, int len) throws IOException

Note that the first read() method reads a byte, but returns an int value. The byte read resides in the eight least significant bits of the int value, while the remaining bits in the int value are zeroed out. The read() methods return the value –1 when the end of the input stream is reached.

Click here to view code image

long transferTo(OutputStream out) throws IOException

Reads bytes from this input stream and writes to the specified output stream in the order they are read. The I/O streams are not closed after the operation (see below).

From the OutputStream abstract class:

Click here to view code image

void write(int b) throws IOException
void write(byte[] b) throws IOException
void write(byte[] b, int off, int len) throws IOException

The first write() method takes an int as an argument, but truncates it to the eight least significant bits before writing it out as a byte to the output stream.

Click here to view code image

void close() throws IOException       
Both
 InputStream
and
 OutputStream
void flush() throws IOException       
Only for
 OutputStream

A I/O stream should be closed when no longer needed, to free system resources. Closing an output stream automatically flushes the output stream, meaning that any data in its internal buffer is written out.

Since byte streams also implement the AutoCloseable interface, they can be declared in a try-with-resources statement (§7.7, p. 407) that will ensure they are properly closed after use at runtime.

An output stream can also be manually flushed by calling the second method.

Read and write operations on streams are blocking operations—that is, a call to a read or write method does not return before a byte has been read or written.

Many methods in the classes contained in the java.io package throw the checked IOException. A calling method must therefore either catch the exception explicitly, or specify it in a throws clause.

Table 20.1 and Table 20.2 give an overview of selected byte streams. Usually an output stream has a corresponding input stream of the same type.

Table 20.1 Selected Input Streams

FileInputStreamData is read as bytes from a file. The file acting as the input stream can be specified by a File object, a FileDescriptor, or a String file name.
FilterInputStreamThe superclass of all input filter streams. An input filter stream must be chained to an underlying input stream.
DataInputStreamA filter stream that allows the binary representation of Java primitive values to be read from an underlying input stream. The underlying input stream must be specified.
ObjectInputStreamA filter stream that allows binary representations of Java objects and Java primitive values to be read from a specified input stream.

Table 20.2 Selected Output Streams

FileOutputStreamData is written as bytes to a file. The file acting as the output stream can be specified by a File object, a FileDescriptor, or a String file name.
FilterOutputStreamThe superclass of all output filter streams. An output filter stream must be chained to an underlying output stream.
PrintStreamA filter output stream that converts a text representation of Java objects and Java primitive values to bytes before writing them to an underlying output stream, which must be specified. This is the type of System.out and System.err (p. 1255). However, the PrintWriter class is recommended when writing characters rather than bytes (p. 1247).
DataOutputStreamA filter stream that allows the binary representation of Java primitive values to be written to an underlying output stream. The underlying output stream must be specified.
ObjectOutputStreamA filter stream that allows the binary representation of Java objects and Java primitive values to be written to a specified underlying output stream.

Writing Binary Values to a File – Java I/O: Part I

Writing Binary Values to a File

To write the binary representation of Java primitive values to a binary file, the following procedure can be used, which is also depicted in Figure 20.2.

Figure 20.2 Stream Chaining for Reading and Writing Binary Values to a File

Use a try-with-resources statement for declaring and creating the necessary streams, which guarantees closing of the filter stream and any underlying stream.

Create a FileOutputStream:

Click here to view code image

FileOutputStream outputFile = new FileOutputStream(“primitives.data”);

Create a DataOutputStream which is chained to the FileOutputStream:

Click here to view code image

DataOutputStream outputStream = new DataOutputStream(outputFile);

Write Java primitive values using relevant writeX() methods:

Note that in the case of char, byte, and short data types, the int argument to the writeX() method is converted to the corresponding type, before it is written (see Table 20.3).

See also the numbered lines in Example 20.2 corresponding to the steps above.

Reading Binary Values from a File

To read the binary representation of Java primitive values from a binary file, the following procedure can be used, which is also depicted in Figure 20.2.

Use a try-with-resources statement for declaring and creating the necessary streams, which guarantees closing of the filter stream and any underlying stream.

Create a FileInputStream:

Click here to view code image

FileInputStream inputFile = new FileInputStream(“primitives.data”);

Create a DataInputStream which is chained to the FileInputStream:

Click here to view code image

DataInputStream inputStream = new DataInputStream(inputFile);

Read the (exact number of) Java primitive values in the same order they were written out to the file, using relevant readX() methods. Not doing so will unleash the wrath of the IOException.

See also the numbered lines in Example 20.2 corresponding to the steps above. Example 20.2 uses both procedures described above: first to write and then to read some Java primitive values to and from a file. It also checks to see if the end of the stream has been reached, signaled by an EOFException. The values are also written to the standard output stream.

Example 20.2 Reading and Writing Binary Values

Click here to view code image

import java.io.*;
public class BinaryValuesIO {
  public static void main(String[] args) throws IOException {
    // Write binary values to a file:
    try(                                                                   // (1)
        // Create a FileOutputStream.                                         (2)
        FileOutputStream outputFile = new FileOutputStream(“primitives.data”);
        // Create a DataOutputStream which is chained to the FileOutputStream.(3)
        DataOutputStream outputStream = new DataOutputStream(outputFile)) {
      // Write Java primitive values in binary representation:                (4)
      outputStream.writeBoolean(true);
      outputStream.writeChar(‘A’);               // int written as Unicode char
      outputStream.writeByte(Byte.MAX_VALUE);    // int written as 8-bits byte
      outputStream.writeShort(Short.MIN_VALUE);  // int written as 16-bits short
      outputStream.writeInt(Integer.MAX_VALUE);
      outputStream.writeLong(Long.MIN_VALUE);
      outputStream.writeFloat(Float.MAX_VALUE);
      outputStream.writeDouble(Math.PI);
    }
    // Read binary values from a file:
    try (                                                                  // (1)
        // Create a FileInputStream.                                          (2)
        FileInputStream inputFile = new FileInputStream(“primitives.data”);
        // Create a DataInputStream which is chained to the FileInputStream.  (3)
        DataInputStream inputStream = new DataInputStream(inputFile)) {
      // Read the binary representation of Java primitive values
      // in the same order they were written out:                             (4)
      System.out.println(inputStream.readBoolean());
      System.out.println(inputStream.readChar());
      System.out.println(inputStream.readByte());
      System.out.println(inputStream.readShort());
      System.out.println(inputStream.readInt());
      System.out.println(inputStream.readLong());
      System.out.println(inputStream.readFloat());
      System.out.println(inputStream.readDouble());
      // Check for end of stream:
      int value = inputStream.readByte();
      System.out.println(“More input: ” + value);
    } catch (FileNotFoundException fnf) {
      System.out.println(“File not found.”);
    } catch (EOFException eof) {
      System.out.println(“End of input stream.”);
    }
  }
}

Output from the program:

true
A
127
-32768
2147483647
-9223372036854775808
3.4028235E38
3.141592653589793
End of input stream.

Character Streams: Readers and Writers – Java I/O: Part I

20.3 Character Streams: Readers and Writers

A character encoding is a scheme for representing characters. Java programs represent values of the char type internally in the 16-bit Unicode character encoding, but the host platform might use another character encoding to represent and store characters externally. For example, the ASCII (American Standard Code for Information Interchange) character encoding is widely used to represent characters on many platforms. However, it is only one small subset of the Unicode standard.

The abstract classes Reader and Writer are the roots of the inheritance hierarchies for streams that read and write Unicode characters using a specific character encoding (Figure 20.3). A reader is an input character stream that implements the Readable interface and reads a sequence of Unicode characters, and a writer is an output character stream that implements the Writer interface and writes a sequence of Unicode characters. Character encodings (usually called charsets) are used by readers and writers to convert between external bytes and internal Unicode characters. The same character encoding that was used to write the characters must be used to read those characters. The java.nio.charset.Charset class represents charsets. Kindly refer to the Charset class API documentation for more details.

Figure 20.3 Selected Character Streams in the java.io Package

Click here to view code image

static Charset forName(String charsetName)

Returns a charset object for the named charset. Selected common charset names are “UTF-8”, “UTF-16”, “US-ASCII”, and “ISO-8859-1”.

Click here to view code image

static Charset defaultCharset()

Returns the default charset of this Java virtual machine.

Table 20.4 and Table 20.5 give an overview of some selected character streams found in the java.io package.

Table 20.4 Selected Readers

ReaderDescription
BufferedReaderA reader is a high-level input stream that buffers the characters read from an underlying stream. The underlying stream must be specified and an optional buffer size can be given.
InputStreamReaderCharacters are read from a byte input stream which must be specified. The default character encoding is used if no character encoding is explicitly specified in the constructor. This class provides the bridge from byte streams to character streams.
FileReaderCharacters are read from a file, using the default character encoding, unless an encoding is explicitly specified in the constructor. The file can be specified by a String file name. It automatically creates a FileInputStream that is associated with the file.

Table 20.5 Selected Writers

WritersDescription
BufferedWriterA writer is a high-level output stream that buffers the characters before writing them to an underlying stream. The underlying stream must be specified, and an optional buffer size can be specified.
OutputStreamWriterCharacters are written to a byte output stream that must be specified. The default character encoding is used if no explicit character encoding is specified in the constructor. This class provides the bridge from character streams to byte streams.
FileWriterCharacters are written to a file, using the default character encoding, unless an encoding is explicitly specified in the constructor. The file can be specified by a String file name. It automatically creates a FileOutputStream that is associated with the file. A boolean parameter can be specified to indicate whether the file should be overwritten or appended with new content.
PrintWriterA print writer is a high-level output stream that allows text representation of Java objects and Java primitive values to be written to an underlying output stream or writer. The underlying output stream or writer must be specified. An explicit encoding can be specified in the constructor, and also whether the print writer should do automatic line flushing.

Readers use the following methods for reading Unicode characters:

Click here to view code image

int read() throws IOException
int read(char cbuf[]) throws IOException
int read(char cbuf[], int off, int len) throws IOException

Note that the read() methods read the character as an int in the range 0 to 65,535 (0x0000–0xFFFF).

The first method returns the character as an int value. The last two methods store the characters in the specified array and return the number of characters read. The value -1 is returned if the end of the stream has been reached.

Click here to view code image

long skip(long n) throws IOException

A reader can skip over characters using the skip() method.

Click here to view code image

void close() throws IOException

Like byte streams, a character stream should be closed when no longer needed in order to free system resources.

Click here to view code image

boolean ready() throws IOException

When called, this method returns true if the next read operation is guaranteed not to block. Returning false does not guarantee that the next read operation will block.

Click here to view code image

long transferTo(Writer out) throws IOException

Reads all characters from this reader and writes the characters to the specified writer in the order they are read. The I/O streams are not closed after the operation.

Writers use the following methods for writing Unicode characters:

Click here to view code image

void write(int c) throws IOException

The write() method takes an int as an argument, but writes only the least significant 16 bits.

Click here to view code image

void write(char[] cbuf) throws IOException
void write(String str) throws IOException
void write(char[] cbuf, int off, int length) throws IOException
void write(String str, int off, int length) throws IOException

Write the characters from an array of characters or a string.

Click here to view code image

void close() throws IOException
void flush() throws IOException

Like byte streams, a character stream should be closed when no longer needed in order to free system resources. Closing a character output stream automatically flushes the stream. A character output stream can also be manually flushed.

Like byte streams, many methods of the character stream classes throw a checked IOException that a calling method must either catch explicitly or specify in a throws clause. They also implement the AutoCloseable interface, and can thus be declared in a try-with-resources statement (§7.7, p. 407) that will ensure they are automatically closed after use at runtime.

Analogous to Example 20.1 that demonstrates usage of a byte buffer for writing and reading bytes to and from file streams, Example 20.3 demonstrates using a character buffer for writing and reading characters to and from file streams. Later in this section, we will use buffered readers (p. 1251) and buffered writers (p. 1250) for reading and writing characters from files, respectively.

Example 20.3 Copying a File Using a Character Buffer

Click here to view code image

/* Copy a file using a character buffer.
   Command syntax: java CopyCharacterFile <from_file> <to_file> */
import java.io.*;
class CopyCharacterFile {
  public static void main(String[] args) {
    try (// Assign the files:
        FileReader fromFile = new FileReader(args[0]);                 // (1)
        FileWriter toFile = new FileWriter(args[1]))  {                // (2)
      // Copy characters using buffer:                                 // (3a)
      char[] buffer = new char[1024];
      int length = 0;
      while((length = fromFile.read(buffer)) != -1) {
        toFile.write(buffer, 0, length);
      }
      // Transfer characters:
//    fromFile.transferTo(toFile);                                     // (3b)
    } catch(ArrayIndexOutOfBoundsException e) {
      System.err.println(“Usage: java CopyCharacterFile <from_file> <to_file>”);
    } catch(FileNotFoundException e) {
      System.err.println(“File could not be copied: ” + e);
    } catch(IOException e) {
      System.err.println(“I/O error.”);
    }
  }
}

Reading Text Files – Java I/O: Part I

Reading Text Files

When reading characters from a file using the default character encoding, the following two procedures for setting up an InputStreamReader can be used.

Setting up an InputStreamReader which is chained to a FileInputStream (Figure 20.5(a)):

Figure 20.5 Setting Up Readers to Read Characters

Create a FileInputStream:

Click here to view code image

FileInputStream inputFile = new FileInputStream(“info.txt”);

Create an InputStreamReader which is chained to the FileInputStream:

Click here to view code image

InputStreamReader reader = new InputStreamReader(inputFile);

The InputStreamReader uses the default character encoding for reading the characters from the file.

Setting up a FileReader which is a subclass of InputStreamReader (Figure 20.5(b)):

Create a FileReader:

Click here to view code image

FileReader fileReader = new FileReader(“info.txt”);

This is equivalent to having an InputStreamReader chained to a FileInputStream for reading the characters from the file, using the default character encoding.

If a specific character encoding is desired for the reader, the first procedure can be used (Figure 20.5(a)), with the encoding being specified for the InputStreamReader:

Click here to view code image

Charset utf8 = Charset.forName(“UTF-8”);
FileInputStream inputFile = new FileInputStream(“info.txt”);
InputStreamReader reader = new InputStreamReader(inputFile, utf8);

This reader will use the UTF-8 character encoding to read the characters from the file. Alternatively, we can use one of the FileReader constructors that accept a character encoding:

Click here to view code image

Charset utf8 = Charset.forName(“UTF-8”);
FileReader reader = new FileReader(“info.txt”, utf8);

A BufferedReader can also be used to improve the efficiency of reading characters from the underlying stream, as explained later in this section (p. 1251).

Using Buffered Writers

A BufferedWriter can be chained to the underlying writer by using one of the following constructors:

Click here to view code image

BufferedWriter(Writer out)
BufferedWriter(Writer out, int size)

The default buffer size is used, unless the buffer size is explicitly specified.

Characters, strings, and arrays of characters can be written using the methods for a Writer, but these now use buffering to provide efficient writing of characters. In addition, the BufferedWriter class provides the method newLine() for writing the platform-dependent line separator.

The following code creates a PrintWriter whose output is buffered, and the characters are written using the UTF-8 character encoding (Figure 20.6(a)):

Figure 20.6 Buffered Writers

Click here to view code image

Charset utf8 = Charset.forName(“UTF-8”);
FileOutputStream   outputFile      = new FileOutputStream(“info.txt”);
OutputStreamWriter outputStream    = new OutputStreamWriter(outputFile, utf8);
BufferedWriter     bufferedWriter1 = new BufferedWriter(outputStream);
PrintWriter        printWriter1    = new PrintWriter(bufferedWriter1, true);

The following code creates a PrintWriter whose output is buffered, and the characters are written using the default character encoding (Figure 20.6(b)):

Click here to view code image

FileWriter     fileWriter      = new FileWriter(“info.txt”);
BufferedWriter bufferedWriter2 = new BufferedWriter(fileWriter);
PrintWriter    printWriter2    = new PrintWriter(bufferedWriter2, true);

Note that in both cases, the PrintWriter is used to write the characters. The Buffered-Writer is sandwiched between the PrintWriter and the underlying OutputStreamWriter (which is the superclass of the FileWriter class).

The Standard Input, Output, and Error Streams – Java I/O: Part I

The Standard Input, Output, and Error Streams

The standard output stream (usually the display) is represented by the PrintStream object System.out. The standard input stream (usually the keyboard) is represented by the InputStream object System.in. In other words, it is a byte input stream. The standard error stream (also usually the display) is represented by System.err, which is another object of the PrintStream class. The PrintStream class offers print() methods that act as corresponding print() methods from the PrintWriter class. The print() methods can be used to write output to System.out and System.err. In other words, both System.out and System.err act like PrintWriter, but in addition they have write() methods for writing bytes.

The System class provides the methods setIn(InputStream), setOut(PrintStream), and setErr(PrintStream) that can be passed an I/O stream to reassign the standard streams.

In order to read characters typed by the user, the Console class is recommended (p. 1256).

Comparison of Byte Streams and Character Streams

It is instructive to see which byte streams correspond to which character streams. Table 20.7 shows the correspondence between byte and character streams. Note that not all classes have a corresponding counterpart.

Table 20.7 Correspondence between Selected Byte and Character Streams

Byte streamsCharacter streams
OutputStreamWriter
InputStreamReader
No counterpartOutputStreamWriter
No counterpartInputStreamReader
FileOutputStreamFileWriter
FileInputStreamFileReader
BufferedOutputStreamBufferedWriter
BufferedInputStreamBufferedReader
PrintStreamPrintWriter
DataOutputStreamNo counterpart
DataInputStreamNo counterpart
ObjectOutputStreamNo counterpart
ObjectInputStreamNo counterpart

20.4 The Console Class

A console is a unique character-based device associated with a JVM. Whether a JVM has a console depends on the platform, and also on the manner in which the JVM is invoked. When the JVM is started from a command line, and the standard input and output streams have not been redirected, the console will normally correspond to the keyboard and the display (Figure 20.8). In any case, the console will be represented by an instance of the class java.io.Console. This Console instance is a singleton, and can only be obtained by calling the static method console() of the System class. If there is no console associated with the JVM, the null value is returned by this method.

Figure 20.8 Keyboard and Display as Console

Click here to view code image

// Obtaining the console:
Console console = System.console();
if (console == null) {
  System.err.println(“No console available.”);
  return;
}
// Continue …

For creating dialogue for console-based applications, the Console class provides the following functionality:

  • Prompt and read a line of character-based response.

Click here to view code image

String username = console.readLine(“Enter the username (%d chars): “, 4);

The readLine() method first prints the formatted prompt on the console, and then returns the characters typed at the console when the line is terminated by the ENTER key.

  • Prompt and read passwords without echoing the characters on the console.

Click here to view code image

char[] password;
do {
    password = console.readPassword(“Enter password (min. %d chars): “, 6);
} while (password.length < 6);

The readPassword() method first prints the formatted prompt, and returns the password characters typed by the user in an array of char when the line is terminated by the ENTER key. The password characters are not echoed on the display.

Since a password is sensitive data, one recommended practice is to have it stored in memory for only as long as it is necessary and to zero-fill the char array as soon as possible in order to overwrite the password characters.

  • Print formatted values to the console.

Similar to the PrintWriter and the PrintStream classes, the Console class also provides the format() and the printf() methods for printing formatted values, but its methods do not allow a locale to be specified.

Note that the console only returns character-based input. For reading other types of values from the standard input stream, the java.util.Scanner class can be considered.

The Console class provides methods for formatted prompting and reading from the console, and obtaining the reader associated with it.

Click here to view code image

String readLine()
String readLine(String format, Object… args)

The first method reads a single line of text from the console. The second method prints a formatted prompt first, then reads a single line of text from the console. The prompt is constructed by formatting the specified args according to the specified format.

Click here to view code image

char[] readPassword()
char[] readPassword(String format, Object… args)

The first method reads a password or a password phrase from the console with echoing disabled. The second method does the same, but first prints a formatted prompt.

Reader reader()

This retrieves the unique Reader object associated with this console.

The Console class provides the following methods for writing formatted strings to the console, and obtaining the writer associated with it:

Click here to view code image

Console format(String format, Object… args)
Console printf(String format, Object… args)

Write a formatted string to this console’s output stream using the specified format string and arguments, according to the default locale. See the PrintWriter class with analogous methods (p. 1245).

PrintWriter writer()

Retrieves the unique PrintWriter object associated with this console.

void flush()

Flushes the console and forces any buffered output to be written immediately.

Example 20.5 illustrates using the Console class to change a password. The example illustrates the capability of the Console class, and in no way should be construed to provide the ultimate secure implementation to change a password.

The console is obtained at (1). The code at (2) implements the procedure for changing the password. The user is asked to submit the new password, and then asked to confirm it. Note that the password characters are not echoed. The respective char arrays returned with this input are compared for equality by the static method equals() in the java.util.Arrays class, which compares two arrays.

Example 20.5 Changing Passwords

Click here to view code image

import java.io.Console;
import java.io.IOException;
import java.util.Arrays;
/** Class to change the password of a user */
public class ChangePassword {
  public static void main (String[] args) throws IOException  {
    // Obtain the console:                                           (1)
    Console console = System.console();
    if (console == null) {
      System.err.println(“No console available.”);
      return;
    }
    // Changing the password:                                        (2)
    boolean noMatch = false;
    do {
      // Read the new password and its confirmation:
      char[] newPasswordSelected
          = console.readPassword(“Enter your new password: “);
      char[] newPasswordConfirmed
          = console.readPassword(“Confirm your new password: “);
      // Compare the supplied passwords:
      noMatch = newPasswordSelected.length == 0  ||
                newPasswordConfirmed.length == 0 ||
                !Arrays.equals(newPasswordSelected, newPasswordConfirmed);
      if (noMatch) {
        console.format(“Passwords don’t match. Please try again.%n”);
      } else {
        // Necessary code to change the password.
        console.format(“Password changed.”);
      }
    } while (noMatch);
  }
}

Running the program:

>
java ChangePassword
Enter your new password:
Confirm your new password:
Password changed.

Object Serialization – Java I/O: Part I

20.5 Object Serialization

Object serialization allows the state of an object to be transformed into a sequence of bytes that can be converted back into a copy of the object (called deserialization). After deserialization, the object has the same state as it had when it was serialized, barring any data members that were not serializable. This mechanism is generally known as persistence—the serialized result of an object can be stored in a repository from which it can be later retrieved.

Java provides the object serialization facility through the ObjectInput and Object-Output interfaces, which allow the writing and reading of objects to and from I/O streams. These two interfaces extend the DataInput and DataOutput interfaces, respectively (Figure 20.1, p. 1235).

The ObjectOutputStream class and the ObjectInputStream class implement the Object-Output interface and the ObjectInput interface, respectively, providing methods to write and read binary representation of both objects as well as Java primitive values. Figure 20.9 gives an overview of how these classes can be chained to underlying streams and some selected methods they provide. The figure does not show the methods inherited from the abstract OutputStream and InputStream superclasses.

Figure 20.9 Object Stream Chaining

The read and write methods in the two classes can throw an IOException, and the read methods will throw an EOFException if an attempt is made to read past the end of the stream.

The ObjectOutputStream Class

The class ObjectOutputStream can write objects to any stream that is a subclass of the OutputStream—for example, to a file or a network connection (socket). An Object-OutputStream must be chained to an OutputStream using the following constructor:

Click here to view code image

ObjectOutputStream(OutputStream out) throws IOException

For example, in order to store objects in a file and thus provide persistent storage for objects, an ObjectOutputStream can be chained to a FileOutputStream:

Click here to view code image

FileOutputStream outputFile = new FileOutputStream(“obj-storage.dat”);
ObjectOutputStream outputStream = new ObjectOutputStream(outputFile);

Objects can be written to the stream using the writeObject() method of the Object-OutputStream class:

Click here to view code image

final void writeObject(Object obj) throws IOException

The writeObject() method can be used to write any object to a stream, including strings and arrays, as long as the object implements the java.io.Serializable interface, which is a marker interface with no methods. The String class, the primitive wrapper classes, and all array types implement the Serializable interface. A serializable object can be any compound object containing references to other objects, and all constituent objects that are serializable are serialized recursively when the compound object is written out. This is true even if there are cyclic references between the objects. Each object is written out only once during serialization. The following information is included when an object is serialized:

  • The class information needed to reconstruct the object
  • The values of all serializable non-transient and non-static members, including those that are inherited

A checked exception of the type java.io.NotSerializableException is thrown if a non-serializable object is encountered during the serialization process. Note also that objects of subclasses that extend a serializable class are always serializable.

Writing Formatted Values – Java I/O: Part I

Writing Formatted Values

Although formatting of values is covered extensively in Chapter 18, p. 1095, here we mention the support for formatting values provided by I/O streams. The PrintWriter class provides the format() methods and the printf() convenient methods to write formatted values. The printf() methods are functionally equivalent to the format() methods. As the methods return a PrintWriter, calls to these methods can be chained.

The printf() and the format() methods for printing formatted values are also provided by the PrintStream and the Console classes (p. 1256). The format() method is also provided by the String class (§8.4, p. 457). We assume familiarity with printing formatted values on the standard output stream by calling the printf() method on the System.out field which is an object of the PrintStream class (§1.9, p. 24).

Click here to view code image

PrintWriter format(String format, Object… args)
PrintWriter format(Locale loc, String format, Object… args)
PrintWriter printf(String format, Object… args)
PrintWriter printf(Locale loc, String format, Object… args)

The String parameter format specifies how formatting will be done. It contains format specifiers that determine how each subsequent value in the variable arity parameter args will be formatted and printed. The resulting string from the formatting will be written to the current writer.

If the locale is specified, it is taken into consideration to format the args.

Any error in the format string will result in a runtime exception.

Writing Text Files

When writing text representation of values to a file using the default character encoding, any one of the following four procedures for setting up a PrintWriter can be used.

Setting up a PrintWriter based on an OutputStreamWriter which is chained to a FileOutputStream (Figure 20.4(a)):

Figure 20.4 Setting Up a PrintWriter to Write to a File

Create a FileOutputStream:

Click here to view code image

FileOutputStream outputFile = new FileOutputStream(“info.txt”);

Create an OutputStreamWriter which is chained to the FileOutputStream:

Click here to view code image

OutputStreamWriter outputStream = new OutputStreamWriter(outputFile);

The OutputStreamWriter uses the default character encoding for writing the characters to the file.

Create a PrintWriter which is chained to the OutputStreamWriter:

Click here to view code image

PrintWriter printWriter1 = new PrintWriter(outputStream, true);

The value true for the second parameter in the constructor will result in the output buffer being flushed by the println() and printf() methods.

Setting up a PrintWriter based on a FileOutputStream (Figure 20.4(b)):

Create a FileOutputStream:

Click here to view code image

FileOutputStream outputFile = new FileOutputStream(“info.txt”);

Create a PrintWriter which is chained to the FileOutputStream:

Click here to view code image

PrintWriter printWriter2 = new PrintWriter(outputFile, true);

The intermediate OutputStreamWriter to convert the characters using the default encoding is automatically supplied. The output buffer will also perform automatic line flushing.

Setting up a PrintWriter based on a FileWriter (Figure 20.4(c)):

Create a FileWriter which is a subclass of OutputStreamWriter:

Click here to view code image

FileWriter fileWriter = new FileWriter(“info.txt”);

This is equivalent to having an OutputStreamWriter chained to a FileOutputStream for writing the characters to the file, as shown in Figure 20.4(a).

Create a PrintWriter which is chained to the FileWriter:

Click here to view code image

PrintWriter printWriter3 = new PrintWriter(fileWriter, true);

The output buffer will be flushed by the println() and printf() methods.

Setting up a PrintWriter, given the file name (Figure 20.4(d)):

Create a PrintWriter, supplying the file name:

Click here to view code image

PrintWriter printWriter4 = new PrintWriter(“info.txt”);

The underlying OutputStreamWriter is created to write the characters to the file in the default encoding, as shown in Figure 20.4(d). In this case, there is no automatic flushing by the println() and printf() methods.

If a specific character encoding is desired for the writer, the third procedure (Figure 20.4(c)) can be used, with the encoding being specified for the FileWriter:

Click here to view code image

Charset utf8 = Charset.forName(“UTF-8”);
FileWriter fileWriter = new FileWriter(“info.txt”, utf8);
PrintWriter printWriter5 = new PrintWriter(fileWriter, true);

This writer will use the UTF-8 character encoding to write the characters to the file. Alternatively, we can use a PrintWriter constructor that accepts a character encoding:

Click here to view code image

Charset utf8 = Charset.forName(“UTF-8”);
PrintWriter printWriter6 = new PrintWriter(“info.txt”, utf8);

A BufferedWriter can also be used to improve the efficiency of writing characters to the underlying stream, as explained later in this section.