Solution for Programming Exercise 11.5
This page contains a sample solution to one of the exercises from Introduction to Programming Using Java.
Exercise 11.5:
The sample program PhoneDirectoryFileDemo.java, from Subsection 11.3.2, stores name/number pairs for a simple phone book in a text file in the user's home directory. Modify that program so that it uses an XML format for the data. The only significant changes that you will have to make are to the parts of the program that read and write the data file. Use the DOM to read the data, as discussed in Subsection 11.5.3. You can use the XML format illustrated in the following sample phone directory file:
<?xml version="1.0"?> <phone_directory> <entry name='barney' number='890-1203'/> <entry name='fred' number='555-9923'/> </phone_directory>
(This is just an easy exercise in simple XML processing; as before, the program in this exercise is not meant to be a useful phone directory program.)
The data for the phone book is stored in a TreeMap in which the entries are name/number pairs. The original program uses a two-line for-each loop to output the data to a PrintWriter named out:
for ( Map.Entry<String,String> entry : phoneBook.entrySet() ) out.println(entry.getKey() + "%" + entry.getValue() );
The data file is written at the end of the program. Writing out the data in the XML format shown in the exercise is only a little harder. The same values are output, but we have to add in a lot of extra text:
out.println("<?xml version=\"1.0\"?>"); out.println("<phone_directory>"); for ( Map.Entry<String,String> entry : phoneBook.entrySet() ) { out.print(" <entry name='"); out.print(entry.getKey()); out.print("' number='"); out.print(entry.getValue()); out.println("'/>"); } out.println("</phone_directory>");
The data file is read at the beginning of the program. To use the Document Object Model for this, we have to create a DocumentBuilder and use it to parse the data file:
DocumentBuilder docReader = DocumentBuilderFactory.newInstance().newDocumentBuilder(); Document xmldoc = docReader.parse(dataFile);
If this succeeds, we know that the file contained a well-formed XML document. We can then call xmldoc.getDocumentElement() to get the root element of the document. This element should have tag name "phone_directory." The phone directory entries are stored in elements that are directly nested inside the root element. We can get a list of the children of the root element by calling root.getChildNodes() and we can traverse that list to get the individual directory entries. The name and number for an entry are stored as values of attributes named "name" and "number", and we can retrieve them using the getAttribute() method. Once we have a name and number, we can add it to the TreeMap in the usual way. Here is the code that gets the data from the DOM representation of the XML data file:
Element root = xmldoc.getDocumentElement(); if (! root.getTagName().equals("phone_directory")) throw new Exception(); NodeList nodes = root.getChildNodes(); for (int i = 0; i < nodes.getLength(); i++) { if ( nodes.item(i) instanceof Element ) { Element entry = (Element)nodes.item(i); if (! entry.getTagName().equals("entry")) throw new Exception(); String entryName = entry.getAttribute("name"); String entryNumber = entry.getAttribute("number"); if (entryName.length() == 0 || entryNumber.length() == 0) throw new Exception(); phoneBook.put(entryName,entryNumber); } }
A complete solution is given below, with changes from PhoneDirectoryFileDemo.java shown in red.
import java.io.*; import java.util.Map; import java.util.TreeMap; import java.util.Scanner; import javax.xml.parsers.*; import org.w3c.dom.*; /** * This program lets the user keep a persistent "phone book" that * contains names and phone numbers. The data for the phone book * is stored in a file in the user's home directory. * * The program is meant only as a demonstration of files and XML. * The data file uses XML to represent the phone book data. */ public class PhoneDirectoryXMLDemo { /** * The name of the file in which the phone book data is kept. The * file is stored in the user's home directory. The "." at the * beginning of the file name means that the file will be a * "hidden" file on Unix-based computers, including Linux and * Mac OS X. */ private static String DATA_FILE_NAME = ".phone_book_xml_demo"; public static void main(String[] args) { String name, number; // Name and number of an entry in the directory // (used at various places in the program). TreeMap<String,String> phoneBook; // Phone directory data structure. // Entries are name/number pairs. phoneBook = new TreeMap<String,String>(); /* Create a dataFile variable of type File to represent the * data file that is stored in the user's home directory. */ File userHomeDirectory = new File( System.getProperty("user.home") ); File dataFile = new File( userHomeDirectory, DATA_FILE_NAME ); /* If the data file already exists, then the data in the file is * read and is used to initialize the phone directory. */ if ( ! dataFile.exists() ) { System.out.println("No phone book data file found."); System.out.println("A new one will be created."); System.out.println("File name: " + dataFile.getAbsolutePath()); } else { System.out.println("Reading phone book data..."); try { DocumentBuilder docReader = DocumentBuilderFactory.newInstance().newDocumentBuilder(); Document xmldoc = docReader.parse(dataFile); Element root = xmldoc.getDocumentElement(); if (! root.getTagName().equals("phone_directory")) throw new Exception(); NodeList nodes = root.getChildNodes(); for (int i = 0; i < nodes.getLength(); i++) { if ( nodes.item(i) instanceof Element ) { Element entry = (Element)nodes.item(i); if (! entry.getTagName().equals("entry")) throw new Exception(); String entryName = entry.getAttribute("name"); String entryNumber = entry.getAttribute("number"); if (entryName.length() == 0 || entryNumber.length() == 0) throw new Exception(); phoneBook.put(entryName,entryNumber); } } } catch (Exception e) { System.out.println("Error in read phone book data file."); System.out.println("File name: " + dataFile.getAbsolutePath()); System.out.println("This program cannot continue."); System.exit(1); } } /* Read commands from the user and carry them out, until the * user gives the "Exit from program" command. */ Scanner in = new Scanner( System.in ); boolean changed = false; // Have any changes been made to the directory? mainLoop: while (true) { System.out.println("\nSelect the action that you want to perform:"); System.out.println(" 1. Look up a phone number."); System.out.println(" 2. Add or change a phone number."); System.out.println(" 3. Remove an entry from your phone directory."); System.out.println(" 4. List the entire phone directory."); System.out.println(" 5. Exit from the program."); System.out.println("Enter action number (1-5): "); int command; if ( in.hasNextInt() ) { command = in.nextInt(); in.nextLine(); } else { System.out.println("\nILLEGAL RESPONSE. YOU MUST ENTER A NUMBER."); in.nextLine(); continue; } switch(command) { case 1: System.out.print("\nEnter the name whose number you want to look up: "); name = in.nextLine().trim().toLowerCase(); number = phoneBook.get(name); if (number == null) System.out.println("\nSORRY, NO NUMBER FOUND FOR " + name); else System.out.println("\nNUMBER FOR " + name + ": " + number); break; case 2: System.out.print("\nEnter the name: "); name = in.nextLine().trim().toLowerCase(); if (name.length() == 0) System.out.println("\nNAME CANNOT BE BLANK."); else if (name.indexOf('%') >= 0) System.out.println("\nNAME CANNOT CONTAIN THE CHARACTER \"%\"."); else { System.out.print("Enter phone number: "); number = in.nextLine().trim(); if (number.length() == 0) System.out.println("\nPHONE NUMBER CANNOT BE BLANK."); else { phoneBook.put(name,number); changed = true; } } break; case 3: System.out.print("\nEnter the name whose entry you want to remove: "); name = in.nextLine().trim().toLowerCase(); number = phoneBook.get(name); if (number == null) System.out.println("\nSORRY, THERE IS NO ENTRY FOR " + name); else { phoneBook.remove(name); changed = true; System.out.println("\nDIRECTORY ENTRY REMOVED FOR " + name); } break; case 4: System.out.println("\nLIST OF ENTRIES IN YOUR PHONE BOOK:\n"); for ( Map.Entry<String,String> entry : phoneBook.entrySet() ) System.out.println(" " + entry.getKey() + ": " + entry.getValue() ); break; case 5: System.out.println("\nExiting program."); break mainLoop; default: System.out.println("\nILLEGAL ACTION NUMBER."); } } /* Before ending the program, write the current contents of the * phone directory, but only if some changes have been made to * the directory. */ if (changed) { System.out.println("Saving phone directory changes to file " + dataFile.getAbsolutePath() + " ..."); PrintWriter out; try { out = new PrintWriter( new FileWriter(dataFile) ); } catch (IOException e) { System.out.println("ERROR: Can't open data file for output."); return; } out.println("<?xml version=\"1.0\"?>"); out.println("<phone_directory>"); for ( Map.Entry<String,String> entry : phoneBook.entrySet() ) { out.print(" <entry name='"); out.print(entry.getKey()); out.print("' number='"); out.print(entry.getValue()); out.println("'/>"); } out.println("</phone_directory>"); out.close(); if (out.checkError()) System.out.println("ERROR: Some error occurred while writing data file."); else System.out.println("Done."); } } }