3.7. Reading Input (from Files or Otherwise)¶
3.7.1. The Scanner Class¶
Java has an excellent class for reading in text. The Scanner class is useful to quickly parse through a String. See the Scanner class API. So how do we properly read in a file? There are a number of ways. This page shows a simple demonstration of how to use the Scanner class to read in a command file. In this project we are given four commands that our program must handle: debug, search, add, and delete. Take a look at the input file.
debug - prints information about the tree in the program
search - searches a region based off coordinates given
add - adds a node at the coordinates given
delete - deletes a node at the specific point
*Every command except debug takes additional parameters*
Consider the following code snippet.
import java.io.File;
import java.util.Scanner;
public class Main {
/**
* @param args
*/
public static void main(String[] args) {
String filename = args[0];//Pass the function a full filepath
beginParsing(filename);//call the parsing function
}
public static void beginParsing(String filename) {
try {
Scanner sc = new Scanner(new File(filename));//Create our new scanner
while(sc.hasNext()) {//While the scanner has information to read
String cmd = sc.next();//Read the next term
double x; double y; double radius;
switch(cmd) {
case "debug" :
System.out.println("debug cmd activated");
break;
case "add" ://Found an add command
x = sc.nextDouble();
y = sc.nextDouble();
String name = sc.next();
System.out.println("Insert node at "+x+" "+y+" with name "+name);
break;
case "delete" ://Found a delete command
x = sc.nextDouble();
y = sc.nextDouble();
System.out.println("Remove node at "+x+" "+y);
break;
case "search" ://Found a search command
x = sc.nextDouble();
y = sc.nextDouble();
radius = sc.nextDouble();
System.out.println("Search for node near "+x+" "+y+" within radius of "+radius);
break;
default ://Found an unrecognized command
System.out.println("Unrecognized input "+cmd);
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
This code will parse through a command file, read in each command and each of their parameters (if the command has one). It is important to note, however, that this code is not necessarily safe. It assumes that the command file given is properly formatted, and as such if a user decides to give the program a malformed file the program will behave in a possibly unknown way.
Depending on the structure of your file you may not wish to simply do the token method. Another approach would be to read in an entire line and then work from there. Consider this input file. We now have 3 commands that we must support.
1. insert {artist-name}<SEP>{song-name} - inserts a song using the information provided in the fields
remove {artist|song} {name} - removes a song given a song name or artist name
3. print {artist|song|blocks} - depending on the parameter value, you will print out either a complete listing of the artists contained in the database, or the songs, or else the free block list for the memory manager
So this time we have less commands to support, but more options for each command , no worries! We simply need to change our code just a little bit. We see this time that the insert command has no spacing between artist/song tokens. Rather it uses the seperator <SEP>.
public static void beginParsingByLine(String filename) {
try {
Scanner sc = new Scanner(new File(filename));
Scanner scancmd;//Declare two scanners one to read the file and one to read the text pulled from the file
while(sc.hasNextLine()) {//While we have text to read
String line = sc.nextLine();//Get our next line
scancmd = new Scanner(line);//Create a scanner from this line
String cmd = scancmd.next();//Get the first word (the command) on each line
String type;
switch(cmd) {
case "insert"://In the case of insert change our delimiter from white space to <SEP>
scancmd.useDelimiter("<SEP>");
String artist = scancmd.next();//Get the artist since it is before <SEP>
String song = scancmd.next();//Get the song title that follows <SEP>
System.out.println("Insert "+artist+" \\ "+song);
break;
case "remove":
type = scancmd.next();//Get the mode of deletion artist/song
String token = scancmd.nextLine();
//Since both artist titles and song titles have spaces
//get the rest of the line for the song/artist name
switch(type) {
case "artist":
System.out.println("Artist Delete: "+token);
break;
case "song":
System.out.println("Song Delete: "+token);
break;
default ://Error bad token
System.out.println("Error bad remove type " + type);
break;
}
break;
case "print"://Print command
type = scancmd.next();//get the type of print command
switch(type) {
case "artist":
System.out.println("Print artist mode");
break;
case "song":
System.out.println("Print song mode");
break;
case "blocks":
System.out.println("Print block mode");
break;
default:
System.out.println("Error bad print type" + type);
break;
}
break;
default :
System.out.println("Unrecognized input");
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
Seperating artists and song name on the same line can prove to be rather difficult due to the fact that either name might include a space or other traditional deliminator. By seperating these fields using <SEP>, we drastically reduce the possibilty of a valid name containing the delimeter.