Chapter 16 – Files, Buffers, and Channels Using Files for Input and Output A Simple Text-File Example – HTML File Generator A Website Reader Object I/O Output with: ObjectOutputStream, FileOutputStream Input with: ObjectInputStream, FileInputStream Character Sets and File-Access Options Buffered Text File I/O Primitive Buffers with Random Access Channel I/O and Memory-Mapped Files The File Class Walking a Directory Tree With a “glob” Pattern Matcher 1 Using Files for Input and Output So far, most input has come from the keyboard and most output has been to the console window. Keyboard and console input/output (I/O) is temporary, not permanent. For permanent I/O, use files with extensions that suggests how the data is formatted and the type of program that understands that format. Benefit of reading input from a file: Allows input to be reused without having to re-enter the input via the keyboard. Benefits of saving output to a file: Allows output to be re-viewed without having to rerun the program. Allows program chaining where the output of one program is used as the input for another program. 2 HTML File Generator Example input file, historyChannel.txt: When Chihuahuas Ruled the World Around 8000 B.C., the great Chihuahua Dynasty ruled the world. What happened to this ancient civilization? Join us for an extraordinary journey into the history of these noble beasts. Resulting output file, historyChannel.html: <!doctype html> <html> <head> <title>When Chihuahuas Ruled the World</title> </head> <body> <h1>When Chihuahuas Ruled the World</h1> <p> Around 8000 B.C., the great Chihuahua Dynasty ruled the world. What happened to this ancient civilization? <p> Join us for an extraordinary journey into the history of these noble beasts. </body> </html> 3 HTML File Generator import java.util.Scanner; import java.io.PrintWriter; import java.nio.file.Paths; public class HTMLGenerator { public static void main(String[] args) { Scanner stdIn = new Scanner(System.in); String filenameIn; // original file's name int dotIndex; // position of dot in filename String filenameOut; // HTML file's name String line; // a line from the input file System.out.print("Enter file's name: "); filenameIn = stdIn.nextLine(); // Compose the new filename dotIndex = filenameIn.lastIndexOf("."); if (dotIndex == -1) // no dot found filenameOut = filenameIn + ".html"; else // dot found filenameOut = filenameIn.substring(0, dotIndex) + ".html"; 4 HTML File Generator try ( Scanner fileIn = new Scanner(Paths.get(filenameIn)); PrintWriter fileOut = new PrintWriter(filenameOut)) { // First line used for title and header elements line = fileIn.nextLine(); if (line == null) { System.out.println(filenameIn + " is empty."); } else { // Write the top of the HTML page. fileOut.println("<!doctype html>"); fileOut.println("<html>"); fileOut.println("<head>"); fileOut.println("<title>" + line + "</title>"); fileOut.println("</head>"); fileOut.println("<body>"); fileOut.println("<h1>" + line + "</h1>"); 5 HTML File Generator while (fileIn.hasNextLine()) { line = fileIn.nextLine(); // Blank lines generate p tags. if (line.isEmpty()) fileOut.println("<p>"); else fileOut.println(line); } // end while // Write ending HTML code. fileOut.println("</body>"); fileOut.println("</html>"); } // end else } // end try and close fileOut and fileIn automatically catch (Exception e) { System.out.println(e.getClass()); System.out.println(e.getMessage()); } // end catch } // end main } // end class HTMLGenerator 6 A Website Reader 7 Reading text from a remote website is like reading text from a file – in both cases, the text comes in as a stream of bytes. import java.util.Scanner; import java.net.*; // URL, URLConnection import java.io.InputStream; public class WebPageReader { public static void main(String[] args) { Scanner stdIn = new Scanner(System.in); Scanner webIn; URL url; URLConnection connection; InputStream inStream; // stream of bytes int i = 0, maxI; // line number and max line number A website reader needs a try block because the URL constructor can throw a MalformedURLException, the openConnection method call can throw an IOException, and the getInputStream method call can throw an IOException. A Website Reader try { System.out.print("Enter a full URL address: "); url = new URL(stdIn.nextLine()); connection = url.openConnection(); inStream = connection.getInputStream(); webIn = new Scanner(inStream); System.out.print("Enter number of lines: "); maxI = stdIn.nextInt(); while (i < maxI && webIn.hasNext()) { System.out.println(webIn.nextLine()); i++; } inStream.close(); } // end try catch (Exception e) { System.out.println(e.getClass()); System.out.println(e.getMessage()); } } // end main } // end WebPageReader 8 Object File I/O 9 Problem: In OOP, most data is in object format, and the structure of objects is user-specified. So there is no universal format for storing objects in files, and each object’s variables must be stored separately using a format that is appropriate for that type of object. This makes writing or reading an object’s data to or from a file very tedious. Solution: Automate this process with Java’s “serialization” service. To get this service, append the following to the heading of any class you want to use this service: implements Serializable The JVM handles all the details. 10 Object File I/O Output: ObjectOutputStream fileOut; try { fileOut = new ObjectOutputStream( new FileOutputStream(filename)); fileOut.writeObject(testObject); fileOut.close(); } open file Input: ObjectInputStream fileIn; try { fileIn = new ObjectInputStream( open file new FileInputStream(filename)); testObject = (TestObject) fileIn.readObject(); fileIn.close(); } Adding an Updated Version of a Previously Written Object 11 If you ask ObjectOutputStream's writeObject method to output a modified version of the same object again while the file is still open, the serializing software recognizes the repetition and outputs just a reference to the previously output object. To make Java append the latest state of an object instead of just a reference to the originally output object, invoke ObjectOutputStream's reset method before you output an updated version of a previously output object: fileOut.writeObject(testObject); testObject.set(<some-attribute>); fileOut.reset(); fileOut.writeObject(testObject); File Access Options 12 Up until now, the types of file-access options we have employed have been the default options built into the particular file-handling constructors we used. But sometimes you need another option. You can display the names of all of Java’s standard open options by executing this code: for (StandardOpenOption opt : StandardOpenOption.values()) { System.out.println(opt); } Here are some Examples: APPEND, CREATE, READ, TRUNCATE_EXISTING, WRITE. For example, to open an existing file to add some more objects to it, you might use this: ObjectOutputStream fileOut = new ObjectOutputStream( Files.newOutputStream(path, StandardOpenOption.APPEND))); Character Sets 13 All text representation employs a certain set of characters. You can determine the name of the default character set used on your computer by importing java.nio.charset.Charset and calling Charset.defaultCharset(). You can determine the names of all the particular character sets your own computer can identify, read, and write by executing this code fragment: for (String s : Charset.availableCharsets().keySet()) { System.out.println(s); } To modify the previous chapter’s ReadFromFile program to read data that was written in the particular character set having the name, “US-ASCII”, instead of using the one-parameter Scanner constructor, use this two-parameter Scanner constructor: fileIn = new Scanner(Paths.get(filename), "US-ASCII"); Buffered Text File I/O For large and/or remote file I/O, you should always employ an intermediate buffer. A buffer is a sequential storage structure that acts like a first-in first-out (FIFO) queue, or “waiting line.” Since a buffer resides in high-speed memory, during program execution the program can transfer data into or out of the buffer much more quickly than into or out of a file in persistent storage. The following BufferedWriteToFile program shows how to write text to a file through a buffer. The Files class’s class method, public static BufferedWriter newBufferedWriter(Path path, Charset cs, OpenOption... options) throws IOException returns a BufferedWriter object, configured for a particular character set and particular open options. The OpenOption... notation is a varargs, which means we may supply any number of arguments of the specified type, including none. 14 BufferedWriteToFile Program 15 This program writes a string through a buffer to a text file. The user specifies whether it is the only string in the file or is appended to previous string(s) in the file. import import import import java.util.Scanner; java.io.BufferedWriter; java.nio.file.*; // Paths, Files, StandardOpenOption java.nio.charset.Charset; public class BufferedWriteToFile { public static void main(String[] args) { Scanner stdIn = new Scanner(System.in); String fileName, openOption; System.out.print("Enter filename: "); fileName = stdIn.nextLine(); System.out.print("Enter TRUNCATE_EXISTING or APPEND: "); openOption = stdIn.nextLine(); BufferedWriteToFile Program try (BufferedWriter fileOut = Files.newBufferedWriter( Paths.get(fileName), Charset.defaultCharset(), StandardOpenOption.CREATE, StandardOpenOption.valueOf(openOption))) { System.out.println("Enter a line of text:"); fileOut.write(stdIn.nextLine() + "\n"); } // end try catch (Exception e) { System.out.println(e.getClass()); System.out.println(e.getMessage()); } } // end main } // end BufferedWriteToFile class 16 BufferedWriteToFile Program This shows how to write two lines of text into a file. Sample session 1: Enter filename: Ecclesiastes Enter TRUNCATE_EXISTING or APPEND: TRUNCATE_EXISTING Enter a line of text: Do not be over-virtuous Sample session 2: Enter filename: Ecclesiastes Enter TRUNCATE_EXISTING or APPEND: APPEND Enter a line of text: nor play too much the sage; 17 BufferedReadFromFile Program 18 The following program shows how to read text from a file through a buffer. File’s newBufferedReader method also includes specification of the character set. It does not accept any openoption specification – it just assumes we want the READ option only. The imports and local variables are the same as those in the BufferedWriteToFile program. Assuming the file created by the inputs in the previous slide, this is what the user gets by running the BufferedReadFromFile program with input equal to the name of the file created in the BufferedWriteToFile program: Sample session: Enter filename: Ecclesiastes Do not be over-virtuous nor play too much the sage; BufferedReadFromFile Program import import import import java.util.Scanner; java.io.BufferedReader; java.nio.file.*; // Paths, Files java.nio.charset.Charset; public class BufferedReadFromFile { public static void main(String[] args) { Scanner stdIn = new Scanner(System.in); String fileName; System.out.print("Enter filename: "); fileName = stdIn.nextLine(); 19 BufferedReadFromFile Program try (BufferedReader fileIn = Files.newBufferedReader( Paths.get(fileName), Charset.defaultCharset())) { while (fileIn.ready()) { System.out.println(fileIn.readLine()); } } // end try catch (Exception e) { System.out.println(e.getClass()); System.out.println(e.getMessage()); } } // end main } // end BufferedReadFromFile class 20 Web Reading With Buffer You can also use Java’s BufferedReader to read from the Web. To do this, create a URL object. Then create a URLConnection. Then create an InputStream. Then use that InputStream to create the InputStreamReader. Then use that InputStreamReader to create a BufferedReader. Here is the sequence of operations: URL url = new URL(webAddress); URLConnection connection = url.openConnection(); InputStream in = connection.getInputStream(); BufferedReader reader = new BufferedReader(new InputStreamReader(in)); 21 The URL and URLConnection are in the java.net package. The InputStream, InputStreamReader, and BufferedReader classes are in the java.io package.If you need a character set different from your computer’s default character set, you can specify the character set with a second argument supplied to an alternate InputStreamReader constructor. Primitive Buffers With Random Access 22 We can also buffer primitive data, like byte, char, short, int, float, long, and double. To facilitate this, the java.nio package provides the abstract class, Buffer, and its abstract descendants: ByteBuffer, CharBuffer, DoubleBuffer, FloatBuffer, IntBuffer, LongBuffer, and ShortBuffer. Use <buffer-class>.allocate(<elements>) to create a new buffer and establish its capacity (maximum number of elements). Since computer systems universally transmit and store data as bytes, the ByteBuffer class plays a central role. It provides single-variable put and get methods that make conversions between other primitive types and the byte type. With help from these methods, the data in the array underlying a ByteBuffer can represent any primitive type, any combination of primitive types, or any combination of primitive types and Object types. Buffer Methods for Random Access Buffer methods storing element information: 23 int capacity() returns the total number of elements. int limit() returns the total number of accessible elements. int position() returns the index of the next element. boolean hasRemaining() says there are more accessible elements. int remaining() returns the accessible elements after position. Buffer methods manipulating element information: Buffer clear() clears the buffer. Buffer limit(int newLimit) sets number of accessible elements. Buffer mark() sets the mark at the current position. Buffer position(int newPosition) sets element position index. Buffer rewind() changes the current position to zero. Buffer reset() changes the current position to the mark. Buffer flip() sets the limit to the current position and then sets the position to zero. Elementary ByteBuffer Methods 24 There are get and put methods for each type of primitive variable: byte, char, short, int, long, float, double. Zero-parameter get methods like getChar() are relative. They return the primitive at the calling buffer’s current position and then increment that position to the next element. One-parameter get methods like getChar(int index) are absolute. The index parameter specifies the primitive’s position. Absolute get methods do not change the buffer’s current position. One-parameter put methods like putChar(char value) are relative. They write the primitive at the calling buffer’s current position and then they increment that position to the next element. Two-parameter put methods like putChar(int index, char value) are absolute. The index parameter specifies the destination position, but these absolute put methods do not change the buffer’s current position. ByteBuffer Methods 25 The method, public ByteBuffer put(ByteBuffer source) copies all remaining source bytes to the calling buffer, starting at the calling buffer’s current position. Since the two buffers’ limit and position values may be different, you can use this method to copy an arbitrary subset of the data in the first buffer to an arbitrary position in the second buffer. The next slide contains a simple program that uses ByteBuffer’s relative putInt method to put a single int value at the beginning of a ByteBuffer. Then it uses ByteBuffer’s absolute putDouble method to put a single double value into that same ByteBuffer, with seven blank spaces between the end of the four-byte int and the start of the eight-byte double. ByteBufferAccess Program import java.nio.ByteBuffer; public class ByteBufferAccess { public static void main(String[] args) { int bufLength = 4 + 7 + 8; // int + empty spaces + double ByteBuffer buffer1 = ByteBuffer.allocate(bufLength); ByteBuffer buffer2 = ByteBuffer.allocate(bufLength); // populate output buffer buffer1.putInt(2); System.out.println("afterIntPos= " + buffer1.position()); buffer1.putDouble(11, 2.0); System.out.println("afterDblPos= " + buffer1.position()); // Transfer everything to input buffer buffer1.rewind(); buffer2.put(buffer1); // display transferred data buffer2.flip(); System.out.println(buffer2.getInt()); System.out.println(buffer2.getDouble(11)); } // end main } // end ByteBufferAccess class 26 ByteBuffer Array Methods 27 The class method, public static ByteBuffer wrap(byte[] byteArray) creates, populates, and returns a ByteBuffer filled with the parameter’s elements. The relative one-parameter method, put(byte[] source), copies all bytes in the source array into the calling buffer, starting at the calling buffer’s current position. The absolute three-parameter method, put(byte[] source, int offset, int length) copies length bytes from the source array, starting at offset array index, into the calling buffer, starting at its current position. The method, byte[] array() returns a view of the array that underlies the calling buffer. The additional view methods, asCharBuffer, asShortBuffer, asIntBuffer, asLongBuffer, asFloatBuffer, and asDoubleBuffer, expose a ByteBuffer’s underlying array as other primitive types. Other Buffer Array Methods 28 Other classes in the java.nio package − CharBuffer, ShortBuffer, IntBuffer, LongBuffer, FloatBuff, and DoubleBuffer – define methods like these: get(char[] destination) get(char[] destination, int offset, int length), put(char[] source) put(char[] source, int offset, int length) With these methods, it’s relatively easy to copy an array of primitives that are not bytes to or from a ByteBuffer, using statements like these: buffer.asDoubleBuffer().put(doubles) buffer.asDoubleBuffer().get(doubles) A powerful feature of the combination of a ByteBuffer and the DoubleBuffer obtained from ByteBuffer’s asDoubleBuffer method is the independence of the two buffers’ position variables. ByteBufferArrayAccess Program import java.util.Arrays; import java.nio.ByteBuffer; public class ByteBufferArrayAccess { public static void main(String[] args) { int[] ints = new int[]{1, 1, 2, 3, 5, 8}; String str = "The purpose of computing is insight, not numbers."; double[] doubles = new double[]{1.0, 2.0, 1.5, 1.67, 1.6}; byte[] strBytes = str.getBytes(); ByteBuffer buffer = ByteBuffer.allocate( 4 * ints.length + strBytes.length + 8 * doubles.length); // put to buffer buffer.asIntBuffer().put(ints); buffer.position(4 * ints.length); buffer.put(strBytes).asDoubleBuffer().put(doubles); 29 ByteBufferArrayAccess Program // fill working arrays with zeros and rewind buffer Arrays.fill(ints, 0); Arrays.fill(strBytes, (byte) 0); Arrays.fill(doubles, 0.0); str = ""; buffer.rewind(); // get from buffer buffer.asIntBuffer().get(ints); buffer.position(4 * ints.length); buffer.get(strBytes).asDoubleBuffer().get(doubles); str = new String(strBytes); // display transferred data System.out.println(Arrays.toString(ints)); System.out.println(str); System.out.println(Arrays.toString(doubles)); } // end main } // end ByteBufferArrayAccess class Sample session: [1, 1, 2, 3, 5, 8] The purpose of computing is insight, not numbers. [1.0, 2.0, 1.5, 1.67, 1.6] 30 Character Sets Revisited 31 In the ByteBufferArrayAccess program, the getBytes method call that converts the specified String to a byte array in the declarations and the later String constructor with the byte[] argument both use the default character set. For a different character set, use the getBytes method and the String constructor that accept character-set specification. Specifically, include this additional import: import java.nio.charset.Charset; To convert the specified String to a byte array, replace the strBytes declaration with something like this: byte[] strBytes = str.getBytes(Charset.forName("US-ASCII")); To convert the byte array back into a String, use something like this: str = new String(strBytes, Charset.forName("US-ASCII")); The choice of “US-ASCII” here is arbitrary. In practice, you might use one of the other character sets. Channel I/O 32 A channel is a large-scale view of what’s in a file. To put heterogeneous data into a file, first put primitives into buffers, and then put those buffers into channels. Conversely, to get heterogeneous data from a file, first extract the buffers from the channels, and then extract the primitives from the buffers. Channel position and size are measured in bytes. If you don’t explicitly alter the channel’s position, as you write bytes, position automatically increments and size automatically expands as required. If you specify a starting place, you could go back and over-write any sequence of bytes. Then you could use channel’s size method to restore its position to just after the last contained byte and continue on from there. After writing from any combination of buffers, you can read with a different combination of buffers. Channel I/O To work with file channels, include these imports: import java.nio.channels.FileChannel; import java.nio.file.*; To specify the file you want, create a new Path, using something like this: System.out.print("Enter filename: "); Path path = Paths.get(stdIn.nextLine()); To open the file for either reading or writing (over any pre-existing data), in a try-with-resources header, create a channel like this: try(FileChannel channel = FileChannel.open( path, StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.READ)) { // ... } // end try Because the file opens in a try-with resources header, it closes automatically at the end of the try block. 33 General FileChannel Methods static FileChannel open( Path path, OpenOption... options) long position() Returns this channel’s current position in the file. FileChannel position(long newPosition) Sets this channel’s current position in the file. public long size() Returns the current size of this channel’s file. MappedByteBuffer map( FileChannel.MapMode mode, long position, long size) Creates a persistent view of size bytes of this channel’s file, starting at this channel’s position, with modes READ_ONLY, READ_WRITE (mutable view), or PRIVATE (copy). 34 FileChannel Write Methods 35 int write(ByteBuffer source) Starting at this channel ‘s position or at the end if opened for APPEND, writes the remaining bytes from the source buffer. long write(ByteBuffer[] sources) Starting at this channel ‘s position or at the end if opened for APPEND, writes the remaining bytes from the sources buffers. long write( ByteBuffer[] sources, int offset, int length) Starting at this channel’s position or at the end if opened for APPEND, writes the remaining bytes from length buffers starting at offset in the sources array. int write(ByteBuffer source, long start) Starting at start in this channel, writes the remaining bytes in the source buffer. FileChannel Read Methods 36 int read(ByteBuffer destination) Reads the remaining bytes from this channel into the remaining positions in the destination buffer. long read(ByteBuffer[] destinations) Reads the remaining bytes from this channel into the remaining positions in the buffers in the destinations array. long read( ByteBuffer[] destinations, int offset, int length) Reads the remaining bytes from this channel into the remaining positions in length buffers, starting at offset in the destinations array. int read(ByteBuffer destination, long start) Reads bytes after start in this channel into the remaining positions in destination buffer. Does not change this channel’s position. ChanneledFileAccess Program import import import import import java.nio.channels.FileChannel; java.io.IOException; java.util.*; // Arrays, Scanner java.nio.*; // ByteBuffer, MappedByteBuffer java.nio.file.*; // Path, Paths, StandardOpenOption public class ChanneledFileAccess { public final static int TEXT = 12; public final static int RECORD = 4 + TEXT + 8; // This adds one buffered record to a file channel. public void writeRecord(FileChannel channel, int id, String string, double value) throws IOException { byte[] strBytes = Arrays.copyOfRange(string.getBytes(), 0, TEXT); ByteBuffer buffer = ByteBuffer.allocate(RECORD); buffer.putInt(id).put(strBytes).putDouble(value); buffer.rewind(); channel.write(buffer); } // end writeRecord 37 ChanneledFileAccess Program public void readRecord(FileChannel channel, int recordIndex) throws IOException { ByteBuffer buffer = ByteBuffer.allocate(RECORD); channel.read(buffer, recordIndex * RECORD); buffer.rewind(); displayRecord(buffer); } // end readRecord private static void displayRecord(ByteBuffer buffer) { int id; byte[] strBytes = new byte[TEXT]; double value; id = buffer.getInt(); buffer.get(strBytes); value = buffer.getDouble(); System.out.printf("%4d %10s %6.1f\n", id, new String(strBytes), value); } // end displayRecord 38 ChanneledFileAccess Program public static void main(String[] args) { Scanner stdIn = new Scanner(System.in); ChanneledFileAccess cio = new ChanneledFileAccess(); ByteBuffer mappedBuffer = ByteBuffer.allocate(3 * RECORD); System.out.print("Enter filename: "); Path path = Paths.get(stdIn.nextLine()); try(FileChannel channel = FileChannel.open( path, StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.READ)) { cio.writeRecord(channel, 1, "first", 1.0); cio.writeRecord(channel, 2, "second", 2.0); cio.writeRecord(channel, 3, "third", 3.0); System.out.print("Enter file's record index (0,1,2): "); cio.readRecord(channel, stdIn.nextInt()); mappedBuffer = channel.map( FileChannel.MapMode.READ_WRITE, 0, channel.size()); } 39 ChanneledFileAccess Program catch(IOException e) { System.out.println(e.getClass()); System.out.println(e.getMessage()); } // Now, channel is gone, but mappedBuffer still exists. System.out.print("Enter map's record index (0,1,2): "); mappedBuffer.position(stdIn.nextInt() * RECORD); displayRecord(mappedBuffer); } // end main } // end class ChanneledFileAccess Sample Enter Enter 3 Enter 2 session: filename: Records file's record index (0,1,2): 2 third 3.0 map's record index (0,1,2): 1 second 2.0 40 Defining and Manipulating Paths 41 The best way to specify a file is to describe the path leading to it using the Files.get() method, like this: Path path = Files.get("<path-to-directory-or-file>"); An absolute path starts at the directory-tree’s root (a leading forward slash). The relative path to the current directory is a single dot. The relative path to a file in the current directory is the name of that file. If the current directory contains a sisterSonia subdirectory, the relative path to a nieceNedra file in that subdirectory is sisterSonia/nieceNedra. The relative path to the directory above the current directory is a pair of dots (..). If the parent directory contains another directory called auntAgnes, the relative path to a cousinCora file in that other directory is ../auntAgnes/cousinCora. The absolute path, pathA, corresponding to the relative path, pathR, is: Path pathA = pathR.toAbsolutePath(); The relative path from absolute path pathA1 to absolute path pathA2 is: Path path1_to_path2 = pathA1.relativize(pathA2); The combination of path1 followed by path2 is: Path pathComb = path1.resolve(path2); Creating, Moving, Copying, and Deleting Files 42 The method call, Files.exists(path) returns true if the file identified by path already exists and you have permission to access that file. To create a new directory, use Path.createDirectory(pathToDir). Use Files.isDirectory(path) to see if path leads to a directory. Use Files.isRegularFile to see if path leads to a regular file. To move or copy an existing directory or file from path1 to path2, in a try block use: Files.move(path1, path2, <option(s)>); Files.copy(path1, path2, <option(s)>); If there is no existing file at path2, omit the option(s) argument. To replace an existing path2 file, include the option: StandardCopyOption.REPLACE_EXISTING To delete the file, path, in a try block use Files.delete(path); Before you can delete a directory, you must first delete or remove all the files it contains. DirectoryDescription Program import java.nio.file.*; // Path, Paths, DirectoryStream, Files public class DirectoryDescription { public static void main(String[] args) { Path pathToDirectory = Paths.get("."); try (DirectoryStream<Path> paths = Files.newDirectoryStream(pathToDirectory)) { for (Path path : paths) { System.out.printf("%-30s%6d bytes\n", path.getFileName(), Files.size(path)); } } 43 DirectoryDescription Program catch (Exception e) { System.out.println(e.getClass()); System.out.println(e.getMessage()); } } // end main } // end DirectoryDescription class Sample session: DirectoryDescription.class DirectoryDescription.java FileSizesGUI.class FileSizesGUI.java 1793 772 1813 1975 bytes bytes bytes bytes 44 Walking a Directory Tree − FindFiles Program 45 Instead of listing all files in a particular directory, let’s locate filenames containing a specified substring anywhere in or below a specified directory. A substring specification called a “glob” can contain any number of singlecharacter wildcards, ?, and the substring wildcards, *. import java.nio.file.*; import java.util.Scanner; import java.io.IOException; // Path, Paths, Files public class FindFiles { public static void main(String[] args) { Path startDir; Scanner stdIn = new Scanner(System.in); String pattern; // ? is wild char; * is wild substring FileVisitor visitor; Walking a Directory Tree With “glob” Pattern Matcher System.out.println( "Enter absolute path to starting directory:"); startDir = Paths.get(stdIn.nextLine()); System.out.print("Enter filename search pattern: "); pattern = stdIn.nextLine(); visitor = new FileVisitor("glob:" + pattern); try { Files.walkFileTree(startDir, visitor); } catch (IOException e) { System.out.println(e.getClass()); System.out.println(e.getMessage()); } } // end main } // end FindFiles class 46 Walking a Directory Tree – FileVisitor Class // for // import import import SimpleFileVisitor, Path, PathMatcher, FileSystem, FileSystems, FileVisitResult, and Files: java.nio.file.*; java.nio.file.attribute.BasicFileAttributes; java.io.IOException; public class FileVisitor extends SimpleFileVisitor<Path> { private PathMatcher matcher; private int tab = 0; // depth in tree //***************************************************** public FileVisitor(String syntaxAndPattern) { FileSystem system = FileSystems.getDefault(); this.matcher = system.getPathMatcher(syntaxAndPattern); } // end constructor 47 Walking a Directory Tree – FileVisitor Class public FileVisitResult preVisitDirectory(Path path, BasicFileAttributes attributes) throws IOException { for (int i=0; i<tab; i++) { System.out.print(" "); } System.out.println(path.getFileName()); // directory tab++; return FileVisitResult.CONTINUE; } // end preVisitDirectory //***************************************************** public FileVisitResult postVisitDirectory(Path path, IOException exc) { tab--; return FileVisitResult.CONTINUE; } // end postVisitDirectory 48 Walking a Directory Tree – FileVisitor Class public FileVisitResult visitFile(Path path, BasicFileAttributes attributes) throws IOException { Path name = path.getFileName(); if (name !=null && matcher.matches(name)) { for (int i=0; i<tab; i++) { System.out.print(" "); } System.out.printf("%-25s%6d bytes\n", name, Files.size(path)); // ordinary file } return FileVisitResult.CONTINUE; } // end visitFile } // end FileVisitor class 49 Walking a Directory Tree − Output Sample session: Enter absolute path to starting directory: /Users/raydean/Documents/John/ITPJSourceCode/pgmsInChapBodies Enter filename search pattern: *2.java pgmsInChapBodies chap01 chap03 chap04 chap05 chap06 Mouse2.java 1264 bytes MouseDriver2.java 687 bytes chap07 Car2.java 950 bytes Employee2.java 448 bytes chap08 chap09 chap10 SpeedDialList2.java 1459 bytes chap11 50 Walking a Directory Tree − Output chap12 Dealership2.java DealershipDriver2.java Manager2.java SalesPerson2.java chap13 Car2.java Employee2.java Hourly2.java Pets2.java Salaried2.java chap14 GetIntFromUser2.java PrintLineFromFile2.java StudentList2.java chap15 WriteObject2.java WriteTextFile2.java chap16 FactorialButton2.java chap17 1240 817 297 413 bytes bytes bytes bytes 984 588 832 713 621 bytes bytes bytes bytes bytes 978 bytes 1210 bytes 1056 bytes 999 bytes 804 bytes 2980 bytes 51 Walking a Directory Tree − Alternatives Suppose all you want to see is the directory structure, not any of the files in that structure. For this result, run the program with nothing but a simple carriage return (Enter) for the pattern specification. Suppose what you want to see is a listing of matching files only, with each file identified as the total path from the directory’s root to that file. For this result, do not override the preVisitDirectory and postVisitDirectory methods. That is, delete or comment out these two methods in the FileVisitor class. Also dlete the tab variable and replace the for loop in the visitFile method with this statement: name = name.toAbsolutePath(); Suppose you want to truncate or terminate the search. In the FileVisitor class, with appropriate code adjustments, use of these: return FileVisitResult.SKIP_SIBLINGS return FileVisitResult.SKIP_SUBTREE return FileVisitResult.TERMINATE 52