Web server on xp - 930 Files and Streams Chapter 16 33 //
930 Files and Streams Chapter 16 33 // ensure that name is proper length 34 private String padName( RandomAccessFile file ) 35 throws IOException 36 { 37 char name[] = new char[ 15 ], temp; 38 39 for ( int count = 0; count < name.length; count++ ) { 40 temp = file.readChar(); 41 name[ count ] = temp; 42 } 43 44 return new String( name ).replace( ' ', ' ' ); 45 } 46 47 // write a record to specified RandomAccessFile 48 public void write( RandomAccessFile file ) throws IOException 49 { 50 file.writeInt( getAccount() ); 51 writeName( file, getFirstName() ); 52 writeName( file, getLastName() ); 53 file.writeDouble( getBalance() ); 54 } 55 56 // write a name to file; maximum of 15 characters 57 private void writeName( RandomAccessFile file, String name ) 58 throws IOException 59 { 60 StringBuffer buffer = null; 61 62 if ( name != null ) 63 buffer = new StringBuffer( name ); 64 else 65 buffer = new StringBuffer( 15 ); 66 67 buffer.setLength( 15 ); 68 file.writeChars( buffer.toString() ); 69 } 70 71 // NOTE: This method contains a hard coded value for the 72 // size of a record of information. 73 public static int size() 74 { 75 return 72; 76 } 77 78 } // end class RandomAccessAccountRecord Fig. 16.11 RandomAccessAccountRecordclass used in the random-access file programs (part 2 of 2). Class RandomAccessAccountRecord inherits AccountRecord s (Fig. 16.5) implementation, which includes private instance variables account, lastName, firstNameand balance as well as their publicset and get methods. #BREAK# Chapter 16 Files and Streams 931 Method read(lines 25 31) reads one record from the RandomAccessFileobject passed as an argument. Methods readInt(line 27) and readDouble(line 30) read the accountand balance, respectively. Method readcalls privatemethod padName (lines 34 45) twice to obtain the first and last names. Method padNamereads fifteen characters from the RandomAccessFileand returns a String. If a name is shorter than 15 characters, the program fills each extra character with a null byte (' '). Swing components, such as JTextFields, cannot display null byte characters (which are displayed instead as rectangles). Line 44 solves this problem by replacing null bytes with spaces. Method write (48 54) outputs one record to the RandomAccessFile object passed in as an argument. This method uses method writeInt to output the integer account, method writeChars(called from utility method writeName) to output the firstName and lastNamecharacter arrays and method writeDoubleto output the double balance. [Note: In order to ensure that all records in the RandomAccess- Filehave the same size, we write exactly 15 characters for the first name and exactly 15 characters for the last name.] Method writeName(lines 57 69) performs the write operations for the first and last name. Figure 16.12 illustrates opening a random-access file and writing data to the disk. This program writes 100 RandomAccessAccountRecords, using method write (Fig. 16.11). Each RandomAccessAccountRecord object contains 0 for the account number, nullfor the last name, nullfor the first name and 0.0for the balance. The file is initialized to create the proper amount of empty space in which the account data will be stored and to enable us to determine in subsequent programs whether each record is empty or contains data. 1 // Fig. 16.12: CreateRandomFile.java 2 // This program creates a random access file sequentially 3 // by writing 100 empty records to disk. 4 5 // Java core packages 6 import java.io.*; 7 8 // Java extension packages 9 import javax.swing.*; 10 11 // Deitel packages 12 import com.deitel.jhtp4.ch16.RandomAccessAccountRecord; 13 14 public class CreateRandomFile { 15 16 // enable user to select file to open 17 private void createFile() 18 { 19 // display dialog so user can choose file 20 JFileChooser fileChooser = new JFileChooser(); 21 fileChooser.setFileSelectionMode( 22 JFileChooser.FILES_ONLY ); 23 24 int result = fileChooser.showSaveDialog( null ); Fig. 16.12 Fig. 16.12 Creating a random-access file sequentially (part 1 of 3). #BREAK# 932 Files and Streams Chapter 16 25 26 // if user clicked Cancel button on dialog, return 27 if ( result == JFileChooser.CANCEL_OPTION ) 28 return; 29 30 // obtain selected file 31 File fileName = fileChooser.getSelectedFile(); 32 33 // display error if file name invalid 34 if ( fileName == null || 35 fileName.getName().equals( "" ) ) 36 JOptionPane.showMessageDialog( null, 37 "Invalid File Name", "Invalid File Name", 38 JOptionPane.ERROR_MESSAGE ); 39 40 else { 41 42 // open file 43 try { 44 RandomAccessFile file = 45 new RandomAccessFile( fileName, "rw" ); 46 47 RandomAccessAccountRecord blankRecord = 48 new RandomAccessAccountRecord(); 49 50 // write 100 blank records 51 for ( int count = 0; count < 100; count++ ) 52 blankRecord.write( file ); 53 54 // close file 55 file.close(); 56 57 // display message that file was created 58 JOptionPane.showMessageDialog( null, 59 "Created file " + fileName, "Status", 60 JOptionPane.INFORMATION_MESSAGE ); 61 62 System.exit( 0 ); // terminate program 63 } 64 65 // process exceptions during open, write or 66 // close file operations 67 catch ( IOException ioException ) { 68 JOptionPane.showMessageDialog( null, 69 "Error processing file", "Error processing file", 70 JOptionPane.ERROR_MESSAGE ); 71 72 System.exit( 1 ); 73 } 74 } 75 76 } // end method openFile 77 Fig. 16.12 Fig. 16.12 Creating a random-access file sequentially (part 2 of 3). #BREAK# Chapter 16 Files and Streams 933 78 // execute application to create file user specifies 79 public static void main( String args[] ) 80 { 81 CreateRandomFile application = new CreateRandomFile(); 82 83 application.createFile(); 84 } 85 86 } // end class CreateRandomFile Fig. 16.12 Fig. 16.12 Creating a random-access file sequentially (part 3 of 3). Lines 44 45 attempt to open a RandomAccessFile for use in this program. The RandomAccessFile constructor receives two arguments the file name and the file open mode. The file open mode for a RandomAccessFileis either "r"to open the file for reading or "rw"to open the file for reading and writing. If an IOExceptionoccurs during the open process, the program displays a message dialog and terminates. If the file opens properly, the program uses a for structure (lines 51 52) to invoke RandomAccessAccountRecord method write 100 times. This statement causes the data members of object blankRecordto be written to the file associated with RandomAccessFileobject file. 16.9 Writing Data Randomly to a Random-Access File Figure 16.13 writes data to a file that is opened with the "rw" mode for reading and writing. It uses the RandomAccessFilemethod seekto determine the exact location in the file at which a record of information is stored. Method seeksets the file-position pointer to a specific position in the file relative to the beginning of the file, and the Random- AccessAccountRecordclass method writeoutputs the data. This program assumes #BREAK# 934 Files and Streams Chapter 16 the user does not enter duplicate account numbers and that the user enters appropriate data in each JTextField. 1 // Fig. 16.13: WriteRandomFile.java 2 // This program uses textfields to get information from the 3 // user at the keyboard and writes the information to a 4 // random-access file. 5 6 // Java core packages 7 import java.awt.*; 8 import java.awt.event.*; 9 import java.io.*; 10 11 // Java extension packages 12 import javax.swing.*; 13 14 // Deitel packages 15 import com.deitel.jhtp4.ch16.*; 16 17 public class WriteRandomFile extends JFrame { 18 private RandomAccessFile output; 19 private BankUI userInterface; 20 private JButton enterButton, openButton; 21 22 // set up GUI 23 public WriteRandomFile() 24 { 25 super( "Write to random access file" ); 26 27 // create instance of reusable user interface BankUI 28 userInterface = new BankUI( 4 ); // four textfields 29 getContentPane().add( userInterface, 30 BorderLayout.CENTER ); 31 32 // get reference to generic task button doTask1 in BankUI 33 openButton = userInterface.getDoTask1Button(); 34 openButton.setText( "Open..." ); 35 36 // register listener to call openFile when button pressed 37 openButton.addActionListener( 38 39 // anonymous inner class to handle openButton event 40 new ActionListener() { 41 42 // allow user to select file to open 43 public void actionPerformed( ActionEvent event ) 44 { 45 openFile(); 46 } 47 48 } // end anonymous inner class 49 50 ); // end call to addActionListener Fig. 16.13 Fig. 16.13 Writing data randomly to a random-access file (part 1 of 5). #BREAK# Chapter 16 Files and Streams 935 51 52 // register window listener for window closing event 53 addWindowListener( 54 55 // anonymous inner class to handle windowClosing event 56 new WindowAdapter() { 57 58 // add record in GUI, then close file 59 public void windowClosing( WindowEvent event ) 60 { 61 if ( output != null ) 62 addRecord(); 63 64 closeFile(); 65 } 66 67 } // end anonymous inner class 68 69 ); // end call to addWindowListener 70 71 // get reference to generic task button doTask2 in BankUI 72 enterButton = userInterface.getDoTask2Button(); 73 enterButton.setText( "Enter" ); 74 enterButton.setEnabled( false ); 75 76 // register listener to call addRecord when button pressed 77 enterButton.addActionListener( 78 79 // anonymous inner class to handle enterButton event 80 new ActionListener() { 81 82 // add record to file 83 public void actionPerformed( ActionEvent event ) 84 { 85 addRecord(); 86 } 87 88 } // end anonymous inner class 89 90 ); // end call to addActionListener 91 92 setSize( 300, 150 ); 93 show(); 94 } 95 96 // enable user to choose file to open 97 private void openFile() 98 { 99 // display file dialog so user can select file 100 JFileChooser fileChooser = new JFileChooser(); 101 fileChooser.setFileSelectionMode( 102 JFileChooser.FILES_ONLY ); 103 Fig. 16.13 Fig. 16.13 Writing data randomly to a random-access file (part 2 of 5). #BREAK# 936 Files and Streams Chapter 16 104 int result = fileChooser.showOpenDialog( this ); 105 106 // if user clicked Cancel button on dialog, return 107 if ( result == JFileChooser.CANCEL_OPTION ) 108 return; 109 110 // obtain selected file 111 File fileName = fileChooser.getSelectedFile(); 112 113 // display error if file name invalid 114 if ( fileName == null || 115 fileName.getName().equals( "" ) ) 116 JOptionPane.showMessageDialog( this, 117 "Invalid File Name", "Invalid File Name", 118 JOptionPane.ERROR_MESSAGE ); 119 120 else { 121 122 // open file 123 try { 124 output = new RandomAccessFile( fileName, "rw" ); 125 enterButton.setEnabled( true ); 126 openButton.setEnabled( false ); 127 } 128 129 // process exception while opening file 130 catch ( IOException ioException ) { 131 JOptionPane.showMessageDialog( this, 132 "File does not exist", 133 "Invalid File Name", 134 JOptionPane.ERROR_MESSAGE ); 135 } 136 } 137 138 } // end method openFile 139 140 // close file and terminate application 141 private void closeFile() 142 { 143 // close file and exit 144 try { 145 if ( output != null ) 146 output.close(); 147 148 System.exit( 0 ); 149 } 150 151 // process exception while closing file 152 catch( IOException ioException ) { 153 JOptionPane.showMessageDialog( this, 154 "Error closing file", 155 "Error", JOptionPane.ERROR_MESSAGE ); 156 Fig. 16.13 Fig. 16.13 Writing data randomly to a random-access file (part 3 of 5). #BREAK# Chapter 16 Files and Streams 937 157 System.exit( 1 ); 158 } 159 } 160 161 // add one record to file 162 public void addRecord() 163 { 164 int accountNumber = 0; 165 String fields[] = userInterface.getFieldValues(); 166 RandomAccessAccountRecord record = 167 new RandomAccessAccountRecord(); 168 169 // ensure account field has a value 170 if ( ! fields[ BankUI.ACCOUNT ].equals( "" ) ) { 171 172 // output values to file 173 try { 174 accountNumber = 175 Integer.parseInt( fields[ BankUI.ACCOUNT ] ); 176 177 if ( accountNumber > 0 && accountNumber <= 100 ) { 178 record.setAccount( accountNumber ); 179 180 record.setFirstName( fields[ BankUI.FIRSTNAME ] ); 181 record.setLastName( fields[ BankUI.LASTNAME ] ); 182 record.setBalance( Double.parseDouble( 183 fields[ BankUI.BALANCE ] ) ); 184 185 output.seek( ( accountNumber -1 ) * 186 RandomAccessAccountRecord.size() ); 187 record.write( output ); 188 } 189 190 userInterface.clearFields(); // clear TextFields 191 } 192 193 // process improper account number or balance format 194 catch ( NumberFormatException formatException ) { 195 JOptionPane.showMessageDialog( this, 196 "Bad account number or balance", 197 "Invalid Number Format", 198 JOptionPane.ERROR_MESSAGE ); 199 } 200 201 // process exceptions while writing to file 202 catch ( IOException ioException ) { 203 closeFile(); 204 } 205 } 206 207 } // end method addRecord 208 Fig. 16.13 Fig. 16.13 Writing data randomly to a random-access file (part 4 of 5). #BREAK# 938 Files and Streams Chapter 16 209 // execute application 210 public static void main( String args[] ) 211 { 212 new WriteRandomFile(); 213 } 215 } // end class WriteRandomFile Fig. 16.13 Fig. 16.13 Writing data randomly to a random-access file (part 5 of 5). The user enters values for the account number, first name, last name and balance. When the user clicks the Enter button, the program calls method addRecord(162 207) of class WriteRandomFileto retrieve the data from the BankAccountUI s JText- Fields, store the data in RandomAccessAccountRecordclass object recordand call the writemethod of class RandomAccessAccountRecordto output the data. Lines 185 186 call RandomAccessFilemethod seekto position the file-position pointer for object outputto the byte location calculated by (accountNumber-1)* RandomAccessAccountRecord.size(). Account numbers in this program should be between 1 and 100. We subtract 1 from the account number when calculating the byte location of the record. Thus, for record 1, the file-position pointer is set to byte 0 of the file. #BREAK# Chapter 16 Files and Streams 939 When the user closes the window, the program attempts to add the last record to the file (if there is one in the GUI waiting to be output), closes the file and terminates. 16.10 Reading Data Sequentially from a Random-Access File In the previous sections, we created a random-access file and wrote data to that file. In this section, we develop a program (Fig. 16.14) that opens a RandomAccessFilefor reading with the "r"file open mode, reads through the file sequentially and displays only those records containing data. This program produces an additional benefit. See whether you can determine what it is; we will reveal it at the end of this section. Good Programming Practice 16.1 Open a file with the "r"file open mode for input if the contents of the file should not be modified. This prevents unintentional modification of the file s contents. This is another example of the principle of least privilege. 1 // Fig. 16.14: ReadRandomFile.java 2 // This program reads a random-access file sequentially and 3 // displays the contents one record at a time in text fields. 4 5 // Java core packages 6 import java.awt.*; 7 import java.awt.event.*; 8 import java.io.*; 9 import java.text.DecimalFormat; 10 11 // Java extension packages 12 import javax.swing.*; 13 14 // Deitel packages 15 import com.deitel.jhtp4.ch16.*; 16 17 public class ReadRandomFile extends JFrame { 18 private BankUI userInterface; 19 private RandomAccessFile input; 20 private JButton nextButton, openButton; 21 22 // set up GUI 23 public ReadRandomFile() 24 { 25 super( "Read Client File" ); 26 27 // create reusable user interface instance 28 userInterface = new BankUI( 4 ); // four textfields 29 getContentPane().add( userInterface ); 30 31 // configure generic doTask1 button from BankUI 32 openButton = userInterface.getDoTask1Button(); 33 openButton.setText( "Open File for Reading..." ); 34 Fig. 16.14 Fig. 16.14 Reading a random-access file sequentially (part 1 of 5). #BREAK# 940 Files and Streams Chapter 16 35 // register listener to call openFile when button pressed 36 openButton.addActionListener( 37 38 // anonymous inner class to handle openButton event 39 new ActionListener() { 40 41 // enable user to select file to open 42 public void actionPerformed( ActionEvent event ) 43 { 44 openFile(); 45 } 46 47 } // end anonymous inner class 48 49 ); // end call to addActionListener 50 51 // configure generic doTask2 button from BankUI 52 nextButton = userInterface.getDoTask2Button(); 53 nextButton.setText( "Next" ); 54 nextButton.setEnabled( false ); 55 56 // register listener to call readRecord when button pressed 57 nextButton.addActionListener( 58 59 // anonymous inner class to handle nextButton event 60 new ActionListener() { 61 62 // read a record when user clicks nextButton 63 public void actionPerformed( ActionEvent event ) 64 { 65 readRecord(); 66 } 67 68 } // end anonymous inner class 69 70 ); // end call to addActionListener 71 72 // register listener for window closing event 73 addWindowListener( 74 75 // anonymous inner class to handle windowClosing event 76 new WindowAdapter() { 77 78 // close file and terminate application 79 public void windowClosing( WindowEvent event ) 80 { 81 closeFile(); 82 } 83 84 } // end anonymous inner class 85 86 ); // end call to addWindowListener 87 Fig. 16.14 Fig. 16.14 Reading a random-access file sequentially (part 2 of 5). #BREAK# Chapter 16 Files and Streams 941 88 setSize( 300, 150 ); 89 show(); 90 } 91 92 // enable user to select file to open 93 private void openFile() 94 { 95 // display file dialog so user can select file 96 JFileChooser fileChooser = new JFileChooser(); 97 fileChooser.setFileSelectionMode( 98 JFileChooser.FILES_ONLY ); 99 100 int result = fileChooser.showOpenDialog( this ); 101 102 // if user clicked Cancel button on dialog, return 103 if ( result == JFileChooser.CANCEL_OPTION ) 104 return; 105 106 // obtain selected file 107 File fileName = fileChooser.getSelectedFile(); 108 109 // display error is file name invalid 110 if ( fileName == null || 111 fileName.getName().equals( "" ) ) 112 JOptionPane.showMessageDialog( this, 113 "Invalid File Name", "Invalid File Name", 114 JOptionPane.ERROR_MESSAGE ); 115 116 else { 117 118 // open file 119 try { 120 input = new RandomAccessFile( fileName, "r" ); 121 nextButton.setEnabled( true ); 122 openButton.setEnabled( false ); 123 } 124 125 // catch exception while opening file 126 catch ( IOException ioException ) { 127 JOptionPane.showMessageDialog( this, 128 "File does not exist", "Invalid File Name", 129 JOptionPane.ERROR_MESSAGE ); 130 } 131 } 132 133 } // end method openFile 134 135 // read one record 136 public void readRecord() 137 { 138 DecimalFormat twoDigits = new DecimalFormat( "0.00" ); 139 RandomAccessAccountRecord record = 140 new RandomAccessAccountRecord(); Fig. 16.14 Fig. 16.14 Reading a random-access file sequentially (part 3 of 5). #BREAK# 942 Files and Streams Chapter 16 141 142 // read a record and display 143 try { 144 145 do { 146 record.read( input ); 147 } while ( record.getAccount() == 0 ); 148 149 String values[] = { 150 String.valueOf( record.getAccount() ), 151 record.getFirstName(), 152 record.getLastName(), 153 String.valueOf( record.getBalance() ) }; 154 userInterface.setFieldValues( values ); 155 } 156 157 // close file when end-of-file reached 158 catch ( EOFException eofException ) { 159 JOptionPane.showMessageDialog( this, "No more records", 160 "End-of-file reached", 161 JOptionPane.INFORMATION_MESSAGE ); 162 closeFile(); 163 } 164 165 // process exceptions from problem with file 166 catch ( IOException ioException ) { 167 JOptionPane.showMessageDialog( this, 168 "Error Reading File", "Error", 169 JOptionPane.ERROR_MESSAGE ); 170 171 System.exit( 1 ); 172 } 173 174 } // end method readRecord 175 176 // close file and terminate application 177 private void closeFile() 178 { 179 // close file and exit 180 try { 18 if ( input != null ) 18 input.close(); 18 18 System.exit( 0 ); 18 } 18 18 // process exception closing file 18 catch( IOException ioException ) { 18 JOptionPane.showMessageDialog( this, 190 "Error closing file", 191 "Error", JOptionPane.ERROR_MESSAGE ); 192 193 System.exit( 1 ); Fig. 16.14 Fig. 16.14 Reading a random-access file sequentially (part 4 of 5). #BREAK# Chapter 16 Files and Streams 943 194 } 195 } 196 197 // execute application 198 public static void main( String args[] ) 199 { 200 new ReadRandomFile(); 201 } 202 203 } // end class ReadRandomFile Fig. 16.14 Fig. 16.14 Reading a random-access file sequentially (part 5 of 5). When the user clicks the Next button to read the next record in the file, the program invokes class ReadRandomFile s readRecordmethod (lines 136 174). This method invokes class RandomAccessAccountRecord s read method (line 146) to read the data into RandomAccessAccountRecord class object record. Method readRecord reads from the file until it encounters a record with a nonzero account number (zero is the initial value for the account). When readRecordencounters a valid account number (i.e., a nonzero value), the loop terminates, and readRecorddisplays the record data in the text fields. When the user clicks the Done button or when the end-of-file #BREAK# 944 Files and Streams Chapter 16 marker is encountered while reading, method closeFile is invoked to close the file and terminate the program. What about that additional benefit we promised? If you examine the GUI as the program executes, you will notice that the records are displayed in sorted order (by account number)! This is a simple consequence of the way we stored these records in the file, using direct-access techniques. Compared to the bubble sort we have seen (Chapter 7), sorting with direct-access techniques is blazingly fast. The speed is achieved by making the file large enough to hold every possible record that might be created, which enables the program to insert a record between other records without having to reorganize the file. This, of course, means that the file could be sparsely occupied most of the time, a waste of storage. So this is another example of the space/time trade-off. By using large amounts of space, we are able to develop a much faster sorting algorithm. 16.11 Example: A Transaction-Processing Program We now present a substantial transaction-processing program (Fig. 16.20), using a random- access file to achieve instant access processing. The program maintains a bank s account information. The program updates existing accounts, adds new accounts and deletes accounts. We assume that the program of Fig. 16.12 has been executed to create a file and that the program of Fig. 16.13 has been executed to insert initial data. The techniques used in this example have been presented in the earlier RandomAccessFile examples. This program GUI consists of a window with a menu bar containing a File menu and internal frames that enable the user to perform insert, update and delete record operations on the file. The internal frames are subclasses of JInternalFrame that are managed by a JDesktopPane (as discussed in Chapter 13). The File menu has five menu items to select various tasks, as shown in Fig. 16.15. When the user selects Update Record from the File menu, the Update Record internal frame (Fig. 16.16) allows the user to update an existing account. The code that implements the Update Record internal frame is in class UpdateDialog (lines 238 458 of Fig. 16.20). In the first screen capture of Fig. 16.16, the user inputs an account number and presses Enter to invoke the account textfield s actionPerformed method (lines 345 360 of Fig. 16.20). This reads the account from the file with method getRecord (lines 371 418 of Fig. 16.20), which validates the account number, then reads the record with RandomAccessAccountRecord method read. Next, getRecord compares the account number with zero (i.e., no record) to determine whether the record contains information. If not, getRecord displays a message stating that the record does not exist; otherwise, it returns the record. Lines 350 357 of the account textfield s actionPerformed method extract the account information from the record and display the account information in the internal frame (as shown in the second screen capture of Fig. 16.16). The Transaction amount textfield initially contains the string charge (+) or payment ( ). The user should select this text and type the transaction amount (a positive value for a charge or a negative value for a payment), then press Enter to invoke the transaction textfield s actionPerformed method (lines 295 330 of Fig. 16.20). Method addRecord (lines 421 456 of Fig. 16.20) takes the transaction amount, adds it to the current balance and calls RandomAccessAccountRecord method setBalance to update the display. Clicking Save Changes writes the updated record to disk; clicking Cancel closes the internal frame without writing the record to disk. The windows in Fig. 16.17 show a sample of a transaction being input. #BREAK# Chapter 16 Files and Streams 945 When the user selects New Record from the File menu, the New Record internal frame in Fig. 16.18 allows the user to add a new record. The code that implements the New Record internal frame is in class NewDialog (lines 461 615 of Fig. 16.20). The user enters data in the JTextFields and clicks Save Changes to write the record to disk. If the account number already exists, the program displays an error message and does not attempt to write the record. Clicking Cancel closes the internal frame without attempting to write the record. Selecting Delete Record from the File menu displays the Delete Record internal frame in Fig. 16.19, which allows the user to delete a record from the file. The code that implements the Delete Record internal frame is in class DeleteDialog (lines 618 774 of Fig. 16.20). The user enters the account number in the JTextField and presses Enter. Only an existing record can be deleted, so, if the specified account is empty, the program displays an error message. This enables the user to check whether the record exists before deleting the record. Clicking the Delete Record button in the internal frame sets the record s account number to 0 (which this application considers to be an empty record). Clicking Cancel closes the internal frame without deleting the record. Fig. 16.15 The initial Transaction Processor window. Type account number and press the Enter key to load record. Fig. 16.16 Loading a record into the Update Record internal frame. #BREAK# 946 Files and Streams Chapter 16 Type transaction amount and press the Enter key to update balance in dialog. Press Save Changes to store new balance in file. Fig. 16.17Inputting a transaction in the Update Record internal frame. 16.17 Fig. 16.18 New Record internal frame. Fig. 16.19 Delete Record internal frame. #BREAK# Chapter 16 Files and Streams 947 The TransactionProcessorprogram appears in Fig. 16.20. The program opens the file with file open mode "rw"(reading and writing). 1 // Transaction processing program using RandomAccessFiles. 2 // This program reads a random-access file sequentially, 3 // updates records already written to the file, creates new 4 // records to be placed in the file and deletes data 5 // already in the file. 6 7 // Java core packages 8 import java.awt.*; 9 import java.awt.event.*; 10 import java.io.*; 11 import java.text.DecimalFormat; 12 13 // Java extension packages 14 import javax.swing.*; 15 16 // Deitel packages 17 import com.deitel.jhtp4.ch16.*; 18 19 public class TransactionProcessor extends JFrame { 20 private UpdateDialog updateDialog; 21 private NewDialog newDialog; 22 private DeleteDialog deleteDialog; 23 private JMenuItem newItem, updateItem, deleteItem, 24 openItem, exitItem; 25 private JDesktopPane desktop; 26 private RandomAccessFile file; 27 private RandomAccessAccountRecord record; 28 29 // set up GUI 30 public TransactionProcessor() 31 { 32 super( "Transaction Processor" ); 33 34 // set up desktop, menu bar and File menu 35 desktop = new JDesktopPane(); 36 getContentPane().add( desktop ); 37 38 JMenuBar menuBar = new JMenuBar(); 39 setJMenuBar( menuBar ); 40 41 JMenu fileMenu = new JMenu( "File" ); 42 menuBar.add( fileMenu ); 43 44 // set up menu item for adding a record 45 newItem = new JMenuItem( "New Record" ); 46 newItem.setEnabled( false ); 47 Fig. 16.20 Fig. 16.20 Transaction-processing program (part 1 of 15). #BREAK# 948 Files and Streams Chapter 16 48 // display new record dialog when user selects New Record 49 newItem.addActionListener( 50 51 new ActionListener() { 52 53 public void actionPerformed( ActionEvent event ) 54 { 55 newDialog.setVisible( true ); 56 } 57 } 58 ); 59 60 // set up menu item for updating a record 61 updateItem = new JMenuItem( "Update Record" ); 62 updateItem.setEnabled( false ); 63 64 // display update dialog when user selects Update Record 65 updateItem.addActionListener( 66 67 new ActionListener() { 68 69 public void actionPerformed( ActionEvent event ) 70 { 71 updateDialog.setVisible( true ); 72 } 73 } 74 ); 75 76 // set up menu item for deleting a record 77 deleteItem = new JMenuItem( "Delete Record" ); 78 deleteItem.setEnabled( false ); 79 80 // display delete dialog when user selects Delete Record 81 deleteItem.addActionListener( 82 83 new ActionListener() { 84 85 public void actionPerformed( ActionEvent event ) 86 { 87 deleteDialog.setVisible( true ); 88 } 89 } 90 ); 91 92 // set up button for opening file 93 openItem = new JMenuItem( "New/Open File" ); 94 95 // enable user to select file to open, then set up 96 // dialog boxes 97 openItem.addActionListener( 98 99 new ActionListener() { 100 Fig. 16.20 Fig. 16.20 Transaction-processing program (part 2 of 15). #BREAK# Chapter 16 Files and Streams 949 101 public void actionPerformed( ActionEvent event ) 102 { 103 boolean opened = openFile(); 104 105 if ( !opened ) 106 return; 107 108 openItem.setEnabled( false ); 109 110 // set up internal frames for record processing 111 updateDialog = new UpdateDialog( file ); 112 desktop.add( updateDialog ); 113 114 deleteDialog = new DeleteDialog( file ); 115 desktop.add ( deleteDialog ); 116 117 newDialog = new NewDialog( file ); 118 desktop.add( newDialog ); 119 } 120 121 } // end anonymous inner class 122 123 ); // end call to addActionListener 124 125 // set up menu item for exiting program 126 exitItem = new JMenuItem( "Exit" ); 127 exitItem.setEnabled( true ); 128 129 // teminate application 130 exitItem.addActionListener( 131 132 new ActionListener() { 133 134 public void actionPerformed( ActionEvent event ) 135 { 136 closeFile(); 137 } 138 } 139 ); 140 14 // attach menu items to File menu 14 fileMenu.add( openItem ); 14 fileMenu.add( newItem ); 14 fileMenu.add( updateItem ); 14 fileMenu.add( deleteItem ); 14 fileMenu.addSeparator(); 14 fileMenu.add( exitItem ); 14 14 // configure window 150 setDefaultCloseOperation( 151 WindowConstants.DO_NOTHING_ON_CLOSE ); 152 153 setSize( 400, 250 ); Fig. 16.20 Fig. 16.20 Transaction-processing program (part 3 of 15). #BREAK# 950 Files and Streams Chapter 16 154 setVisible( true ); 155 156 } // end TransactionProcessor constructor 157 158 // enable user to select file to open 159 private boolean openFile() 160 { 161 // display dialog so user can select file 162 JFileChooser fileChooser = new JFileChooser(); 163 fileChooser.setFileSelectionMode( 164 JFileChooser.FILES_ONLY ); 165 166 int result = fileChooser.showOpenDialog( this ); 167 168 // if user clicked Cancel button on dialog, return 169 if ( result == JFileChooser.CANCEL_OPTION ) 170 return false; 171 172 // obtain selected file 173 File fileName = fileChooser.getSelectedFile(); 174 175 // display error if file name invalid 176 if ( fileName == null || 177 fileName.getName().equals( "" ) ) { 178 JOptionPane.showMessageDialog( this, 179 "Invalid File Name", "Invalid File Name", 180 JOptionPane.ERROR_MESSAGE ); 181 182 return false; 183 } 184 185 else { 186 187 // open file 188 try { 189 file = new RandomAccessFile( fileName, "rw" ); 190 openItem.setEnabled( false ); 191 newItem.setEnabled( true ); 192 updateItem.setEnabled( true ); 193 deleteItem.setEnabled( true ); 194 } 195 196 // process problems opening file 197 catch ( IOException ioException ) { 198 JOptionPane.showMessageDialog( this, 199 "File does not exist", "Invalid File Name", 200 JOptionPane.ERROR_MESSAGE ); 201 202 return false; 203 } 204 } 205 206 return true; // file opened Fig. 16.20 Fig. 16.20 Transaction-processing program (part 4 of 15). #BREAK# Chapter 16 Files and Streams 951 207 } 208 209 // close file and terminate application 210 private void closeFile() 211 { 212 // close file and exit 213 try { 214 if( file != null ) 215 file.close(); 216 217 System.exit( 0 ); 218 } 219 220 // process exceptions closing file 221 catch( IOException ioException ) { 222 JOptionPane.showMessageDialog( this, 223 "Error closing file", 224 "Error", JOptionPane.ERROR_MESSAGE ); 225 System.exit( 1 ); 226 } 227 } 228 229 // execute application 230 public static void main( String args[] ) 231 { 232 new TransactionProcessor(); 233 } 234 235 } // end class TransactionProcessor 236 237 // class for udpating records 238 class UpdateDialog extends JInternalFrame { 239 private RandomAccessFile file; 240 private BankUI userInterface; 241 242 // set up GUI 243 public UpdateDialog( RandomAccessFile updateFile ) 244 { 245 super( "Update Record" ); 246 247 file = updateFile; 248 249 // set up GUI components 250 userInterface = new BankUI( 5 ); 251 getContentPane().add( userInterface, 252 BorderLayout.CENTER ); 253 254 // set up Save Changes button and register listener 255 JButton saveButton = userInterface.getDoTask1Button(); 256 saveButton.setText( "Save Changes" ); 257 258 saveButton.addActionListener( 259 Fig. 16.20 Fig. 16.20 Transaction-processing program (part 5 of 15). #BREAK# 952 Files and Streams Chapter 16 260 new ActionListener() { 261 262 public void actionPerformed( ActionEvent event ) 263 { 264 addRecord( getRecord() ); 265 setVisible( false ); 266 userInterface.clearFields(); 267 } 268 } 269 ); 270 271 // set up Cancel button and register listener 272 JButton cancelButton = userInterface.getDoTask2Button(); 273 cancelButton.setText( "Cancel" ); 274 275 cancelButton.addActionListener( 276 277 new ActionListener() { 278 279 public void actionPerformed( ActionEvent event ) 280 { 281 setVisible( false ); 282 userInterface.clearFields(); 283 } 284 } 285 ); 286 287 // set up listener for transaction textfield 288 JTextField transactionField = 289 userInterface.getFields()[ BankUI.TRANSACTION ]; 290 291 transactionField.addActionListener( 292 293 new ActionListener() { 294 295 public void actionPerformed( ActionEvent event ) 296 { 297 // add transaction amount to balance 298 try { 299 RandomAccessAccountRecord record = getRecord(); 300 30 // get textfield values from userInterface 30 String fieldValues[] = 30 userInterface.getFieldValues(); 30 30 // get transaction amount 30 double change = Double.parseDouble( 30 fieldValues[ BankUI.TRANSACTION ] ); 30 30 // specify Strings to display in GUI 310 String[] values = { 311 String.valueOf( record.getAccount() ), 312 record.getFirstName(), Fig. 16.20 Fig. 16.20 Transaction-processing program (part 6 of 15). #BREAK# Chapter 16 Files and Streams 953 313 record.getLastName(), 314 String.valueOf( record.getBalance() 315 + change ), 316 "Charge(+) or payment (-)" }; 317 318 // display Strings in GUI 319 userInterface.setFieldValues( values ); 320 } 321 322 // process invalid number in transaction field 323 catch ( NumberFormatException numberFormat ) { 324 JOptionPane.showMessageDialog( null, 325 "Invalid Transaction", 326 "Invalid Number Format", 327 JOptionPane.ERROR_MESSAGE ); 328 } 329 330 } // end method actionPerformed 331 332 } // end anonymous inner class 333 334 ); // end call to addActionListener 335 336 // set up listener for account text field 337 JTextField accountField = 338 userInterface.getFields()[ BankUI.ACCOUNT ]; 339 340 accountField.addActionListener( 341 342 new ActionListener() { 343 344 // get record and display contents in GUI 345 public void actionPerformed( ActionEvent event ) 346 { 347 RandomAccessAccountRecord record = getRecord(); 348 349 if ( record.getAccount() != 0 ) { 350 String values[] = { 351 String.valueOf( record.getAccount() ), 352 record.getFirstName(), 353 record.getLastName(), 354 String.valueOf( record.getBalance() ), 355 "Charge(+) or payment (-)" }; 356 357 userInterface.setFieldValues( values ); 358 } 359 360 } // end method actionPerformed 361 362 } // end anonymous inner class 363 364 ); // end call to addActionListener 365 Fig. 16.20 Fig. 16.20 Transaction-processing program (part 7 of 15). #BREAK# 954 Files and Streams Chapter 16 366 setSize( 300, 175 ); 367 setVisible( false ); 368 } 369 370 // get record from file 371 private RandomAccessAccountRecord getRecord() 372 { 373 RandomAccessAccountRecord record = 374 new RandomAccessAccountRecord(); 375 376 // get record from file 377 try { 378 JTextField accountField = 379 userInterface.getFields()[ BankUI.ACCOUNT ]; 380 381 int accountNumber = 382 Integer.parseInt( accountField.getText() ); 383 384 if ( accountNumber < 1 || accountNumber > 100 ) { 385 JOptionPane.showMessageDialog( this, 386 “Account Does Not Exist”, 387 “Error”, JOptionPane.ERROR_MESSAGE ); 388 return record; 389 } 390 391 // seek to appropriate record location in file 392 file.seek( ( accountNumber -1 ) * 393 RandomAccessAccountRecord.size() ); 394 record.read( file ); 395 396 if ( record.getAccount() == 0 ) 397 JOptionPane.showMessageDialog( this, 398 “Account Does Not Exist”, 399 “Error”, JOptionPane.ERROR_MESSAGE ); 400 } 401 402 // process invalid account number format 403 catch ( NumberFormatException numberFormat ) { 404 JOptionPane.showMessageDialog( this, 405 “Invalid Account”, “Invalid Number Format”, 406 JOptionPane.ERROR_MESSAGE ); 407 } 408 409 // process file processing problems 410 catch ( IOException ioException ) { 411 JOptionPane.showMessageDialog( this, 412 “Error Reading File”, 413 “Error”, JOptionPane.ERROR_MESSAGE ); 414 } 415 416 return record; 417 418 } // end method getRecord Fig. 16.20 Fig. 16.20 Transaction-processing program (part 8 of 15). #BREAK# Chapter 16 Files and Streams 955 419 420 // add record to file 421 public void addRecord( RandomAccessAccountRecord record ) 422 { 423 // update record in file 424 try { 425 int accountNumber = record.getAccount(); 426 427 file.seek( ( accountNumber -1 ) * 428 RandomAccessAccountRecord.size() ); 429 430 String[] values = userInterface.getFieldValues(); 431 432 // set firstName, lastName and balance in record 433 record.setFirstName( values[ BankUI.FIRSTNAME ] ); 434 record.setLastName( values[ BankUI.LASTNAME ] ); 435 record.setBalance( 436 Double.parseDouble( values[ BankUI.BALANCE ] ) ); 437 438 // rewrite record to file 439 record.write( file ); 440 } 441 442 // process file processing problems 443 catch ( IOException ioException ) { 444 JOptionPane.showMessageDialog( this, 445 “Error Writing To File”, 446 “Error”, JOptionPane.ERROR_MESSAGE ); 447 } 448 449 // process invalid balance value 450 catch ( NumberFormatException numberFormat ) { 451 JOptionPane.showMessageDialog( this, 452 “Bad Balance”, “Invalid Number Format”, 453 JOptionPane.ERROR_MESSAGE ); 454 } 455 456 } // end method addRecord 457 458 } // end class UpdateDialog 459 460 // class for creating new records 461 class NewDialog extends JInternalFrame { 462 private RandomAccessFile file; 463 private BankUI userInterface; 464 465 // set up GUI 466 public NewDialog( RandomAccessFile newFile ) 467 { 468 super( “New Record” ); 469 470 file = newFile; 471 Fig. 16.20 Fig. 16.20 Transaction-processing program (part 9 of 15). #BREAK# 956 Files and Streams Chapter 16 472 // attach user interface to dialog 473 userInterface = new BankUI( 4 ); 474 getContentPane().add( userInterface, 475 BorderLayout.CENTER ); 476 477 // set up Save Changes button and register listener 478 JButton saveButton = userInterface.getDoTask1Button(); 479 saveButton.setText( “Save Changes” ); 480 481 saveButton.addActionListener( 482 483 new ActionListener() { 484 485 // add new record to file 486 public void actionPerformed( ActionEvent event ) 487 { 488 addRecord( getRecord() ); 489 setVisible( false ); 490 userInterface.clearFields(); 491 } 492 493 } // end anonymous inner class 494 495 ); // end call to addActionListener 496 497 JButton cancelButton = userInterface.getDoTask2Button(); 498 cancelButton.setText( “Cancel” ); 499 500 cancelButton.addActionListener( 501 502 new ActionListener() { 503 504 // dismiss dialog without storing new record 505 public void actionPerformed( ActionEvent event ) 506 { 507 setVisible( false ); 508 userInterface.clearFields(); 509 } 510 511 } // end anonymous inner class 512 513 ); // end call to addActionListener 514 515 setSize( 300, 150 ); 516 setVisible( false ); 517 518 } // end constructor 519 520 // get record from file 521 private RandomAccessAccountRecord getRecord() 522 { 523 RandomAccessAccountRecord record = 524 new RandomAccessAccountRecord(); Fig. 16.20 Fig. 16.20 Transaction-processing program (part 10 of 15). #BREAK# Chapter 16 Files and Streams 957 525 526 // get record from file 527 try { 528 JTextField accountField = 529 userInterface.getFields()[ BankUI.ACCOUNT ]; 530 531 int accountNumber = 532 Integer.parseInt( accountField.getText() ); 533 534 if ( accountNumber < 1 || accountNumber > 100 ) { 535 JOptionPane.showMessageDialog( this, 536 “Account Does Not Exist”, 537 “Error”, JOptionPane.ERROR_MESSAGE ); 538 return record; 539 } 540 541 // seek to record location 542 file.seek( ( accountNumber -1 ) * 543 RandomAccessAccountRecord.size() ); 544 545 // read record from file 546 record.read( file ); 547 } 548 549 // process invalid account number format 550 catch ( NumberFormatException numberFormat ) { 551 JOptionPane.showMessageDialog( this, 552 “Account Does Not Exist”, “Invalid Number Format”, 553 JOptionPane.ERROR_MESSAGE ); 554 } 555 556 // process file processing problems 557 catch ( IOException ioException ) { 558 JOptionPane.showMessageDialog( this, 559 “Error Reading File”, 560 “Error”, JOptionPane.ERROR_MESSAGE ); 561 } 562 563 return record; 564 565 } // end method getRecord 566 567 // add record to file 568 public void addRecord( RandomAccessAccountRecord record ) 569 { 570 String[] fields = userInterface.getFieldValues(); 571 572 if ( record.getAccount() != 0 ) { 573 JOptionPane.showMessageDialog( this, 574 “Record Already Exists”, 575 “Error”, JOptionPane.ERROR_MESSAGE ); 576 return; 577 } Fig. 16.20 Fig. 16.20 Transaction-processing program (part 11 of 15). #BREAK# 958 Files and Streams Chapter 16 578 579 // output the values to the file 580 try { 581 582 // set account, first name, last name and balance 583 // for record 584 record.setAccount( Integer.parseInt( 585 fields[ BankUI.ACCOUNT ] ) ); 586 record.setFirstName( fields[ BankUI.FIRSTNAME ] ); 587 record.setLastName( fields[ BankUI.LASTNAME ] ); 588 record.setBalance( Double.parseDouble( 589 fields[ BankUI.BALANCE ] ) ); 590 591 // seek to record location 592 file.seek( ( record.getAccount() -1 ) * 593 RandomAccessAccountRecord.size() ); 594 595 // write record 596 record.write( file ); 597 } 598 599 // process invalid account or balance format 600 catch ( NumberFormatException numberFormat ) { 601 JOptionPane.showMessageDialog( this, 602 “Invalid Balance”, “Invalid Number Format”, 603 JOptionPane.ERROR_MESSAGE ); 604 } 605 606 // process file processing problems 607 catch ( IOException ioException ) { 608 JOptionPane.showMessageDialog( this, 609 “Error Writing To File”, 610 “Error”, JOptionPane.ERROR_MESSAGE ); 611 } 612 613 } // end method addRecord 614 615 } // end class NewDialog 616 617 // class for deleting records 618 class DeleteDialog extends JInternalFrame { 619 private RandomAccessFile file; // file for output 620 private BankUI userInterface; 621 622 // set up GUI 623 public DeleteDialog( RandomAccessFile deleteFile ) 624 { 625 super( “Delete Record” ); 626 627 file = deleteFile; 628 629 // create BankUI with only account field 630 userInterface = new BankUI( 1 ); Fig. 16.20 Fig. 16.20 Transaction-processing program (part 12 of 15). #BREAK# Chapter 16 Files and Streams 959 631 632 getContentPane().add( userInterface, 633 BorderLayout.CENTER ); 634 635 // set up Delete Record button and register listener 636 JButton deleteButton = userInterface.getDoTask1Button(); 637 deleteButton.setText( “Delete Record” ); 638 639 deleteButton.addActionListener( 640 641 new ActionListener() { 642 643 // overwrite existing record 644 public void actionPerformed( ActionEvent event ) 645 { 646 addRecord( getRecord() ); 647 setVisible( false ); 648 userInterface.clearFields(); 649 } 650 651 } // end anonymous inner class 652 653 ); // end call to addActionListener 654 655 // set up Cancel button and register listener 656 JButton cancelButton = userInterface.getDoTask2Button(); 657 cancelButton.setText( “Cancel” ); 658 659 cancelButton.addActionListener( 660 661 new ActionListener() { 662 663 // cancel delete operation by hiding dialog 664 public void actionPerformed( ActionEvent event ) 665 { 666 setVisible( false ); 667 } 668 669 } // end anonymous inner class 670 671 ); // end call to addActionListener 672 673 // set up listener for account text field 674 JTextField accountField = 675 userInterface.getFields()[ BankUI.ACCOUNT ]; 676 677 accountField.addActionListener( 678 679 new ActionListener() { 680 681 public void actionPerformed( ActionEvent event ) 682 { 683 RandomAccessAccountRecord record = getRecord(); Fig. 16.20 Fig. 16.20 Transaction-processing program (part 13 of 15). #BREAK# 960 Files and Streams Chapter 16 684 } 685 686 } // end anonymous inner class 687 688 ); // end call to addActionListener 689 690 setSize( 300, 100 ); 691 setVisible( false ); 692 693 } // end constructor 694 695 // get record from file 696 private RandomAccessAccountRecord getRecord() 697 { 698 RandomAccessAccountRecord record = 699 new RandomAccessAccountRecord(); 700 701 // get record from file 702 try { 703 JTextField accountField = 704 userInterface.getFields()[ BankUI.ACCOUNT ]; 705 706 int accountNumber = 707 Integer.parseInt( accountField.getText() ); 708 709 if ( accountNumber < 1 || accountNumber > 100 ) { 710 JOptionPane.showMessageDialog( this, 711 “Account Does Not Exist”, 712 “Error”, JOptionPane.ERROR_MESSAGE ); 713 return( record ); 714 } 715 716 // seek to record location and read record 717 file.seek( ( accountNumber -1 ) * 718 RandomAccessAccountRecord.size() ); 719 record.read( file ); 720 721 if ( record.getAccount() == 0 ) 722 JOptionPane.showMessageDialog( this, 723 “Account Does Not Exist”, 724 “Error”, JOptionPane.ERROR_MESSAGE ); 725 } 726 727 // process invalid account number format 728 catch ( NumberFormatException numberFormat ) { 729 JOptionPane.showMessageDialog( this, 730 “Account Does Not Exist”, 731 “Invalid Number Format”, 732 JOptionPane.ERROR_MESSAGE ); 733 } 734 735 // process file processing problems 736 catch ( IOException ioException ) { Fig. 16.20 Fig. 16.20 Transaction-processing program (part 14 of 15). #BREAK# Chapter 16 Files and Streams 961 737 JOptionPane.showMessageDialog( this, 738 “Error Reading File”, 739 “Error”, JOptionPane.ERROR_MESSAGE ); 740 } 741 742 return record; 743 744 } // end method getRecord 745 746 // add record to file 747 public void addRecord( RandomAccessAccountRecord record ) 748 { 749 if ( record.getAccount() == 0 ) 750 return; 751 752 // delete record by setting account number to 0 753 try { 754 int accountNumber = record.getAccount(); 755 756 // seek to record position 757 file.seek( ( accountNumber -1 ) * 758 RandomAccessAccountRecord.size() ); 759 760 // set account to 0 and overwrite record 761 record.setAccount( 0 ); 762 record.write( file ); 763 } 764 765 // process file processing problems 766 catch ( IOException ioException ) { 767 JOptionPane.showMessageDialog( this, 768 “Error Writing To File”, 769 “Error”, JOptionPane.ERROR_MESSAGE ); 770 } 771 772 } // end method addRecord 773 774 } // end class DeleteDialog Fig. 16.20 Fig. 16.20 Transaction-processing program (part 15 of 15). 16.12 Class File As we stated at the beginning of this chapter, the java.iopackage contains an abundance of classes for processing input and output. We concentrated on the classes for processing sequential files (FileInputStreamand FileOutputStream), for processing object streams (ObjectInputStreamand ObjectOutputStream) and for processing random-access files (RandomAccessFile). In this section, we discuss class File, which is particularly useful for retrieving information about a file or a directory from a disk. Objects of class Filedo not open files or provide any file-processing capabilities. One application of a File object is to determine whether a file exists before attempting to open the file. In Common Programming Error 16.1, we warned that opening an existing file for output by using a FileOutputStream object discards the contents #BREAK# 962 Files and Streams Chapter 16 of that file without warning. If a program using a Filedetermines that a file already exists, the program can warn that the user is about to discard the original file s contents. Good Programming Practice 16.2 Use a Fileobject to determine whether a file exists before opening the file with a File- OutputStreamobject. Class Fileprovides three constructors. The constructor public File( String name ) stores the Stringargument namein the object. The namecan contain path information as well as a file or directory name. A file or directory s path specifies the location of the file or directory on disk. The path includes some or all of the directories leading to the file or directory. An absolute path contains all the directories, starting with the root directory that lead to a specific file or directory. Every file or directory on a particular disk drive has the same root directory in its path. A relative path contains a subset of the directories leading to a specific file or directory. Relative paths normally start from the directory in which the application began executing. The constructor public File( String pathToName, String name ) uses argument pathToName (an absolute or relative path) to locate the file or directory specified by name. The constructor public File( File directory, String name ) uses an existing File object directory (an absolute or relative path) to locate the file or directory specified by name. Figure 16.21 discusses some common File methods. See the Java API for other Filemethods. Method Description boolean canRead() Returns trueif a file is readable; falseotherwise. boolean canWrite() Returns trueif a file is writable; falseotherwise. boolean exists() Returns trueif the name specified as the argument to the Fileconstructor is a file or directory in the specified path; falseotherwise. boolean isFile() Returns trueif the name specified as the argument to the Fileconstructor is a file; falseotherwise. boolean isDirectory() Returns trueif the name specified as the argument to the Fileconstructor is a directory; falseotherwise. boolean isAbsolute() Returns trueif the arguments specified to the Fileconstructor indicate an absolute path to a file or directory; falseotherwise. Fig. 16.21 g. 16.21 Some commonly used Filemethods (part 1 of 2). #BREAK# Chapter 16 Files and Streams 963 Method Description String getAbsolutePath() Returns a Stringwith the absolute path of the file or direc tory. String getName() Returns a Stringwith the name of the file or directory. String getPath() Returns a Stringwith the path of the file or directory. String getParent() Returns a Stringwith the parent directory of the file or directory that is, the directory in which the file or directory can be found. long length() Returns the length of the file in bytes. If the Fileobject represents a directory, 0is returned. long lastModified() Returns a platform-dependent representation of the time at which the file or directory was last modified. The value returned is only useful for comparison with other values returned by this method. String[] list() Returns an array of Strings representing the contents of a directory. Fig. 16.21 g. 16.21 Some commonly used Filemethods (part 2 of 2). Good Programming Practice 16.3 Use File method isFileto determine that a Fileobject represents a file (not a directory) before attempting to open a file. Good Programming Practice 16.4 Before attempting to open a file for reading, use File method canRead to determine whether the file is readable. Good Programming Practice 16.5 Before attempting to open a file for writing, use File method canWrite to determine whether the file is writable. Figure 16.22 demonstrates class File. The FileTest application creates a GUI containing a JTextFieldfor entering a file name or directory name and a JTextArea for displaying information about the file name or directory name input. 1 // Fig. 16.22: FileTest.java 2 // Demonstrating the File class. 3 4 // Java core packages 5 import java.awt.*; 6 import java.awt.event.*; 7 import java.io.*; 8 Fig. 16.22 Demonstrating class File(part 1 of 4). #BREAK# 964 Files and Streams Chapter 16 9 // Java extension packages import javax.swing.*; 11 12 public class FileTest extends JFrame 13 implements ActionListener { 14 15 private JTextField enterField; 16 private JTextArea outputArea; 17 18 // set up GUI 19 public FileTest() { 21 super( “Testing class File” ); 22 23 enterField = new JTextField( 24 “Enter file or directory name here” ); 25 enterField.addActionListener( this ); 26 outputArea = new JTextArea(); 27 28 ScrollPane scrollPane = new ScrollPane(); 29 scrollPane.add( outputArea ); 31 Container container = getContentPane(); 32 container.add( enterField, BorderLayout.NORTH ); 33 container.add( scrollPane, BorderLayout.CENTER ); 34 35 setSize( 400, 400 ); 36 show(); 37 } 38 39 // display information about file user specifies public void actionPerformed( ActionEvent actionEvent ) 41 { 42 File name = new File( actionEvent.getActionCommand() ); 43 44 // if name exists, output information about it 45 if ( name.exists() ) { 46 outputArea.setText( 47 name.getName() + ” existsn” + 48 ( name.isFile() ? 49 “is a filen” : “is not a filen” ) + ( name.isDirectory() ? 51 “is a directoryn” : “is not a directoryn” ) + 52 ( name.isAbsolute() ? “is absolute pathn” : 53 “is not absolute pathn” ) + 54 “Last modified: ” + name.lastModified() + 55 “nLength: ” + name.length() + 56 “nPath: ” + name.getPath() + 57 “nAbsolute path: ” + name.getAbsolutePath() + 58 “nParent: ” + name.getParent() ); 59 // output information if name is a file 61 if ( name.isFile() ) { Fig. 16.22 Demonstrating class File(part 2 of 4). #BREAK# Chapter 16 Files and Streams 965 62 63 // append contents of file to outputArea 64 try { 65 BufferedReader input = new BufferedReader( 66 new FileReader( name ) ); 67 StringBuffer buffer = new StringBuffer(); 68 String text; 69 outputArea.append( “nn” ); 70 71 while ( ( text = input.readLine() ) != null ) 72 buffer.append( text + “n” ); 73 74 outputArea.append( buffer.toString() ); 75 } 76 77 // process file processing problems 78 catch( IOException ioException ) { 79 JOptionPane.showMessageDialog( this, 80 “FILE ERROR”, 81 “FILE ERROR”, JOptionPane.ERROR_MESSAGE ); 82 } 83 } 84 85 // output directory listing 86 else if ( name.isDirectory() ) { 87 String directory[] = name.list(); 88 89 outputArea.append( “nnDirectory contents:n”); 90 91 for ( int i = 0; i < directory.length; i++ ) 92 outputArea.append( directory[ i ] + "n" ); 93 } 94 } 95 96 // not file or directory, output error message 97 else { 98 JOptionPane.showMessageDialog( this, 99 actionEvent.getActionCommand() + " Does Not Exist", 100 "ERROR", JOptionPane.ERROR_MESSAGE ); 101 } 102 103 } // end method actionPerformed 104 105 // execute application 106 public static void main( String args[] ) 107 { 108 FileTest application = new FileTest(); 109 110 application.setDefaultCloseOperation( 111 JFrame.EXIT_ON_CLOSE ); 112 } 113 114 } // end class FileTest Fig. 16.22 Demonstrating class File(part 3 of 4). #BREAK# 966 Files and Streams Chapter 16 Fig. 16.22 Demonstrating class File(part 4 of 4). The user types a file name or directory name into the text field and presses the Enter key to invoke method actionPerformed (lines 40 103), which creates a new File object (line 42) and assigns it to name. Line 45 invokes Filemethod existsto determine whether the name input by the user exists (either as a file or as a directory) on the disk. If the name input by the user does not exist, the actionPerformedmethod proceeds to lines 97 101 and displays a message dialog containing the name the user typed, followed by DoesNot Exist. Otherwise, the body of the ifstructure (lines 45 94) executes. The program outputs the name of the file or directory, then outputs the results of testing the File object with isFile (line 48), isDirectory (line 50) and isAbsolute (line 52). Next, the program displays the values returned by lastModified (line 54), #BREAK# Chapter 16 Files and Streams 967 length (line 55), getPath (line 56), getAbsolutePath(line 57) and getParent (line 58). If the Fileobject represents a file (line 61), the program reads the contents of the file and displays the contents in the JTextArea. The program uses a BufferedReader object chained to a FileReaderobject (lines 65 66) to open the file for reading and to read the file one line at a time with method readLine (line 71). Note that the FileReader object was initialized with the File object name (line 66). If the File object represents a directory (line 86), the program reads the contents of the directory into the program by using File method list, then displays the directory contents in the JTextArea. The first output demonstrates a File object associated with the jfc directory from the Java 2 Software Development Kit. The second output of this program demonstrates a Fileobject associated with the readme.txt file from the Java 2 Software Development Kit. In both cases, we specified an absolute path on our personal computer. Note that the separator character is used to separate directories and files in the path. On a UNIX workstation, the separator character would be the /character. Java processes both characters identically in a path name. So, if we specified the path c:java/readme.txt which uses one of each separator character, Java still processes the file properly. Common Programming Error 16.2 Using as a directory separator rather than \ in a string literal is a logic error. A single indicates that the and the next character represent an escape sequence. To insert a in a string literal, you must use \. Good Programming Practice 16.6 When building Strings that represent path information, use File.separatorCharto obtain the local computer s proper separator character, rather than explicitly using /or . SUMMARY All data items processed by a computer are reduced to combinations of zeros and ones. The smallest data item in a computer (a bit) can assume the value 0 or the value 1. Digits, letters and special symbols are called characters. The set of all characters used to write programs and represent data items on a particular computer is called that computer s character set. Every character in a computer s character set is represented as a pattern of 1s and 0s. (Characters in Java are Unicode characters composed of 2 bytes.) A field is a group of characters (or bytes) that conveys meaning. A record is a group of related fields. At least one field in a record is chosen as a record key to identify a record as belonging to a particular person or entity that is unique from all other records in the file. Java imposes no structure on a file. Notions like record do not exist in Java. The programmer must structure a file appropriately to meet the requirements of an application. A collection of programs designed to create and manage databases is called a database management system (DBMS). Java views each file as a sequential stream of bytes. #BREAK# 968 Files and Streams Chapter 16 Each file ends in some machine-dependent form of end-of-file marker. Streams provide communication channels between programs and files, memory or other programs across a network. Programs use classes from package java.io to perform Java file I/O. This package includes the definitions for the stream classes, such as FileInputStream, FileOutputStream, DataInputStreamand DataOutputStream. Files are opened by instantiating objects of stream classes FileInputStream, FileOutputStream, RandomAccessFile, FileReaderand FileWriter. InputStream (a subclass of Object) and OutputStream (a subclass of Object) are abstractclasses that define methods for performing input and output, respectively. File input/output is done with FileInputStream (a subclass of InputStream) and File- OutputStream (a subclass of OutputStream). Pipes are synchronized communication channels between threads. A pipe is established between two threads. One thread sends data to another by writing to a PipedOutputStream (a subclass of OutputStream). The target thread reads information from the pipe via a PipedInput- Stream (a subclass of InputStream). A PrintStream (a subclass of FilterOutputStream) performs output to the screen (or the standard output as defined by your local operating system). System.out is a PrintStream (as is System.err). A FilterInputStream filters an InputStream; a FilterOutStream filters an OutputStream. Filtering means simply that the filter stream provides additional functionality, such as buffering, monitoring of line numbers or aggregating of data bytes into meaningful primitive- data-type units. Reading data as raw bytes is fast but crude. Usually, programs read data as aggregates of bytes that form an int, a float, a double, and so on. To accomplish this, we use a DataInputStream (a subclass of class FilterInputStream). Interface DataInput is implemented by class DataInputStream and class RandomAccessFile; each needs to read primitive data types from a stream. DataInputStreams enable a program to read binary data from an InputStream. The DataInput interface includes methods read (for byte arrays), readBoolean, read- Byte, readChar, readDouble, readFloat, readFully (for byte arrays), readInt, readLine, readLong, readShort, readUnsignedByte, readUnsignedShort, readUTF(for Unicode) and skipBytes. The DataOutput interface is implemented by class DataOutputStream (a subclass of class FilterOutputStream) and class RandomAccessFile; each needs to write primitive data types to an OutputStream. DataOutputStreams enable a program to write binary data to an OutputStream. The DataOutput interface includes methods flush, size, write (for a byte), write (for a byte array), writeBoolean, writeByte, writeBytes, writeChar, writeChars (for Unicode Strings), writeDouble, writeFloat, writeInt, writeLong, write- Shortand writeUTF. Buffering is an I/O-performance-enhancement technique. With a BufferedOutputStream (a subclass of class FilterOutputStream), each output statement does not necessarily result in an actual physical transfer of data to the output device. Rather, each output operation is directed to a region in memory called a buffer that is large enough to hold the data of many output operations. Then, actual output to the output device is performed #BREAK# Chapter 16 Files and Streams 969 in one large physical output operation each time the buffer fills. The output operations directed to the output buffer in memory are often called logical output operations. With a BufferedInputStream, many logical chunks of data from a file are read as one large physical input operation into a memory buffer. As a program requests each new chunk of data, it is taken from the buffer (this is sometimes referred to as a logical input operation). When the buffer is empty, the next physical input operation from the input device is performed to read in the next group of logical chunks of data. Thus, the number of physical input operations is small compared with the number of read requests issued by the program. With a BufferedOutputStream a partially filled buffer can be forced out to the device at any time with an explicit flush. The ObjectInput interface is similar to the DataInput interface, but includes additional methods to read Objects from InputStreams. The ObjectOutput interface is similar to the DataOutputinterface, but includes additional methods to write Objects to OutputStreams. The ObjectInputStream and ObjectOutputStream classes implement the Object- Inputand ObjectOutput interfaces, respectively. A PushbackInputStream is a subclass of class FilterInputStream. The application reading a PushbackInputStreamreads bytes from the stream and forms aggregates consisting of several bytes. Sometimes, to determine that one aggregate is complete, the application must read the first character past the end of the first aggregate. Once the program has determined that the current aggregate is complete, the extra character is pushed back onto the stream. PushbackInputStreams are used by programs, like compilers, that parse their inputs that is, break them into meaningful units (such as the keywords, identifiers and operators that the Java compiler must recognize). A RandomAccessFile (a subclass of Object) is useful for such direct-access applications as transaction-processing applications, like airline-reservations systems and point-of-sale systems. With a sequential-access file, each successive input/output request reads or writes the next consecutive set of data in the file. With a random-access file, each successive input/output request might be directed to any part of the file, perhaps one widely separated from the part of the file referenced in the previous request. Direct-access applications provide rapid access to specific data items in large files; such applications are often used while people are waiting for answers these answers must be made available quickly, or the people might become impatient and take their business elsewhere. A ByteArrayInputStream (a subclass of abstract class InputStream) performs its inputs from a byte array in memory. A ByteArrayOutputStream (a subclass of abstract class OutputStream) outputs to a byte array in memory. An application of byte-array input/output is data validation. A program can input an entire line at a time from the input stream into a byte array. Then, a validation routine can scrutinize the contents of the byte array and correct the data, if necessary. The program can now proceed to input from the byte array, knowing that the input data is in the proper format. A StringBufferInputStream (a subclass of abstract class InputStream) inputs from a StringBufferobject. A SequenceInputStream (a subclass of abstract class InputStream) enables several InputStreams to be concatenated so that the program will see the group as one continuous InputStream. As the end of each input stream is reached, the stream is closed, and the next stream in the sequence is opened. #BREAK# 970 Files and Streams Chapter 16 Class BufferedReader and class BufferedWriter enable efficient buffering for character-based streams. Class CharArrayReader and class CharArrayWriter read and write a stream of characters to a character array. A PushbackReader (a subclass of abstract class FilterReader) enables characters to be placed back on a character stream. A LineNumberReader (a subclass of BufferedReader) is a buffered character-stream that keeps track of line numbers (e.g., a newline, a return or a carriage-return line-feed combination). Class FileReader (a subclass of InputStreamReader) and class FileWriter (a subclass of OutputStreamWriter) read and write characters to a file. Class PipedReaderand class PipedWriter are piped-character streams. Class StringReaderand StringWriter read and write characters to Strings. A PrintWriter writes characters to a stream. Class File enables programs to obtain information about a file or directory. Files are opened for output by creating a FileOutputStream class object. One argument is passed to the constructor the filename. Existing files are truncated, and all data in the file is lost. Nonexistent files are created. A program can process no files, one file or several files. Each file has a unique name and is associated with an appropriate file stream object. All file-processing methods must refer to a file with the appropriate object. A file-position pointer indicates the position in the file from which the next input is to occur or at which the next output is to be placed. A convenient way to implement random-access files is by using only fixed-length records. Using this technique, a program can quickly calculate the exact location of a record relative to the beginning of the file. Data can be inserted in a random-access file without destroying other data in the file. Data can be updated or deleted without rewriting the entire file. The RandomAccessFileclass has the same capabilities for input and output as the DataInputStream and DataOutputStreamclasses and also supports seeking to a specific byte position in the file, with method seek. TERMINOLOGY absolute path canWrite method of File class alphabetic field chaining stream objects alphanumeric field character field binary digit character set bit CharArrayReader class buffer CharArrayWriter class BufferedInputStream class close a file BufferedOutputStream class closemethod BufferedReader class data hierarchy BufferedWriter class data validation buffering database byte database management system (DBMS) ByteArrayInputStream class DataInput interface ByteArrayOutputStream class DataOutput interface CANCEL_OPTION constant decimal digit canRead method of File class direct-access applications #BREAK# Chapter 16 DIRECTORIES_ONLY constant directory end-of-file end-of-file marker EndOfFileException exists method of File class field file File class file name FileInputStream class FileOutputStream class file-position pointer FileReader class FILES_AND_DIRECTORIES constant FILES_ONLY constant FileWriter class FilterInputStream class FilterOutputStream class FilterReader class flush getAbsolutePath method of File class getName method of File class getParentmethod of File class getPath method of File class getSelectedFile method input stream InputStream class InputStreamReader class instant-access application IOException isAbsolute method of File class isDirectory method of File class isFile method of File class JFileChooser class lastModified method of File class length method of File class LineNumberReader class list method of File class logical input operation logical output operation memory buffer modal dialog numeric field ObjectInput interface ObjectInputStream class ObjectOutput interface ObjectOutputStream class open a file output stream Files and Streams 971 OutputStream class OutputStreamWriter class partially filled buffer persistent data physical input operation physical output operation pipe PipedInputStream class PipedOutputStream class PipedReader class PipedWriter class PrintStream class PrintWriter class PushbackInputStream class PushbackReader class rfile open mode random-access file RandomAccessFile class readmethod readBoolean method readBytemethod readCharmethod readDoublemethod Reader class readFloatmethod readFullymethod readInt method readLongmethod readObjectmethod readShortmethod readUnsignedBytemethod readUnsignedShortmethod record record key relative path root directory rwfile open mode seekmethod SequenceInputStream class sequential-access file Serializable interface setFileSelectionModemethod showOpenDialogmethod showSaveDialogmethod standard output StringReader class StringWriter class System.err (standard error stream) System.in (standard input stream) System.out (standard output stream) #BREAK# 972 Files and Streams Chapter 16 transaction-processing systems writeCharsmethod truncate an existing file writeDouble method Unicode character set writeFloatmethod writemethod writeIntmethod writeBooleanmethod writeLongmethod writeBytemethod writeObject method writeBytesmethod Writer class writeCharmethod writeShortmethod SELF-REVIEW EXERCISES 16.1 Fill in the blanks in each of the following statements: a) Ultimately, all data items processed by a computer are reduced to combinations of and . b) The smallest data item a computer can process is called a . c) A is a group of related records. d) Digits, letters and special symbols are referred to as . e) A group of related files is called a . f) Method of the file stream classes FileOutputStream, FileInput- Stream, and RandomAccessFile closes a file. g) RandomAccessFilemethod reads an integer from the specified stream. h) RandomAccessFile method reads a line of text from the specified stream. i) RandomAccessFile method sets the file-position pointer to a specific location in a file for input or output. 16.2 State which of the following are true and which are false. If false, explain why. a) The programmer must explicitly create the System.in, System.out and System.err objects. b) If the file-position pointer points to a location in a sequential file other than the beginning of the file, the file must be closed and reopened to read from the beginning of the file. c) It is not necessary to search through all the records in a random-access file to find a spe cific record. d) Records in random-access files must be of uniform length. e) Method seek must seek relative to the beginning of a file. 16.3 Assume that each of the following statements applies to the same program. a) Write a statement that opens file "oldmast.dat" for input; use ObjectInput- Streamobject inOldMaster chained to a FileInputStreamobject. b) Write a statement that opens file "trans.dat" for input; use ObjectInput- Streamobject inTransaction chained to a FileInputStream object. c) Write a statement that opens file "newmast.dat" for output (and creation); use ObjectOutputStream object outNewMaster chained to a FileOutputStream. d) Write a set of statements that read a record from the file "oldmast.dat". The record consists of integer accountNumber, string name and floating-point currentBalance; use ObjectInputStream object inOldMaster. e) Write a set of statements that read a record from the file "trans.dat". The record consists of integer accountNumberand floating-point dollarAmount; use Object- InputStreamobject inTransaction. f) Write a set of statements that outputs a record to the file "newmast.dat". The record consists of integer accountNumber, string nameand floating point currentBalance; use DataOutputStreamobject outNewMaster. #BREAK# Chapter 16 Files and Streams 973 16.4 Find the error and show how to correct it in each of the following. a) Assume account, companyand amount are declared. ObjectOutputStream outputStream; outputStream.writeInt( account ); outputStream.writeChars( company ); outputStream.writeDouble( amount ); b) The following statement should read a record from the file "payables.dat". The ObjectInputStream object inPayable refers to this file, and FileInput- Streamobject inReceivable refers to the file "receivables.dat". account = inReceivable.readInt(); companyID = inReceivable.readLong(); amount = inReceivable.readDouble(); ANSWERS TO SELF-REVIEW EXERCISES 16.1 a) 1s, 0s. b) bit. c) file. d) characters. e) database. f) close. g) readInt. h) readLine. i) seek. 16.2 a) False. These three streams are created automatically for the programmer. b) True. c) True. d) False. Records in a random-access file are normally of uniform length. e) True. 16.3 a) ObjectInputStream inOldMaster; inOldMaster = new ObjectInputStream( new FileInputStream( "oldmast.dat" ) ); b) ObjectInputStream inTransaction; inTransaction = new ObjectInputStream( new FileInputStream( "trans.dat" ) ); c) ObjectOutputStream outNewMaster; outNewMaster = new ObjectOutputStream( new FileOutputStream( "newmast.dat" ) ); d) accountNumber = inOldMaster.readInt(); name = inOldMaster.readUTF(); currentBalance = inOldMaster.readDouble(); e) accountNumber = inTransaction.readInt(); dollarAmount = inTransaction.readDouble(); f) outNewMaster.writeInt( accountNumber ); outNewMaster.writeUTF( name ); outNewMaster.writeDouble( currentBalance ); 16.4 a) Error: The file has not been opened before the attempt is made to output data to the stream. Correction: Create a new ObjectOutputStream object chained to a FileOutput- Stream object to open the file for output. b) Error: The incorrect FileInputStream object is being used to read a record from file "payables.dat". Correction: Use object inPayable to refer to "payables.dat". #BREAK# 974 Files and Streams Chapter 16 EXERCISES 16.5 Fill in the blanks in each of the following: a) Computers store large amounts of data on secondary storage devices as . b) A is composed of several fields. c) A field that may contain only digits, letters and blanks is called an field. d) To facilitate the retrieval of specific records from a file, one field in each record is chosen as a . e) The vast majority of information stored in computer systems is stored in files. f) The standard stream objects are , and . 16.6 State which of the following are true and which are false. If false, explain why. a) The impressive functions performed by computers essentially involve the manipulation of zeros and ones. b) People specify programs and data items as characters; computers then manipulate and process these characters as groups of zeros and ones. c) A person s 5-digit zip code is an example of a numeric field. d) A person's street address is generally considered to be an alphabetic field. e) Data items represented in computers form a data hierarchy in which data items become larger and more complex as we progress from fields to characters to bits, etc. f) A record key identifies a record as belonging to a particular field. g) Companies store all their information in a single file to facilitate computer processing. h) When a program creates a file, the file is automatically retained by the computer for fu ture reference. 16.7 Exercise 16.3 asked the reader to write a series of single statements. Actually, these statements form the core of an important type of file-processing program, namely, a file-matching program. In commercial data processing, it is common to have several files in each application system. In an accounts receivable system, for example, there is generally a master file containing detailed information about each customer, such as the customer s name, address, telephone number, outstanding balance, credit limit, discount terms, contract arrangements and possibly a condensed history of recent purchases and cash payments. a) As transactions occur (i.e., sales are made and cash payments arrive in the mail), they are entered into a file. At the end of each business period (i.e., a month for some companies, a week for others, and a day in some cases) the file of transactions (called "trans.dat"in Exercise 16.3) is applied to the master file (called "oldmast.dat" in Exercise 16.3), thus updating each account s record of purchases and payments. During an updating run, the master file is rewritten as a new file ("newmast.dat"), which is then used at the end of the next business period to begin the updating process again. b) File-matching programs must deal with certain problems that do not exist in single-file programs. For example, a match does not always occur. A customer on the master file might not have made any purchases or cash payments in the current business period; therefore, no record for this customer will appear on the transaction file. Similarly, a customer who did make some purchases or cash payments could have just moved to this community, and the company might not have had a chance to create a master record for this customer. c) Use the statements in Exercise 16.3 as a basis for writing a complete file-matching accounts receivable program. Use the account number on each file as the record key for matching purposes. Assume that each file is a sequential file with records stored in increasing account-number order. d) When a match occurs (i.e., records with the same account number appear on both the master file and the transaction file), add the dollar amount on the transaction file to the #BREAK# Chapter 16 Files and Streams 975 current balance on the master file, and write the "newmast.dat"record. (Assume that purchases are indicated by positive amounts on the transaction file, payments by negative amounts.) When there is a master record for a particular account but no corresponding transaction record, merely write the master record to "newmast.dat". When there is a transaction record but no corresponding master record, print the message "Unmatched transaction recordfor account number " (fill in the account number from the transaction record). 16.8 After writing the program of Exercise 16.7, write a simple program to create some test data for checking out the program. Use the sample account data in Fig. 16.23 and Fig. 16.24. Run the program of Exercise 16.7, using the files of test data created in this exercise. Print the new master file. Check that the accounts have been updated correctly. 16.9 It is possible (actually common) to have several transaction records with the same record key. This occurs because a particular customer might make several purchases and cash payments during a business period. Rewrite your accounts receivable file-matching program of Exercise 16.7 to provide for the possibility of handling several transaction records with the same record key. Modify the test data of Exercise 16.8 to include the additional transaction records in Fig. 16.25. Master file Account number Name Balance 100 Alan Jones 348.17 300 Mary Smith 27.19 500 Sam Sharp 0.00 700 Suzy Green -14.22 Fig. 16.23 Sample data for master file. Transaction file Account number Transaction amount 100 27.14 300 62.11 400 100.56 900 82.17 Fig. 16.24 Sample data for transaction file. Account number Dollar amount 300 83.89 700 80.78 700 1.53 Fig. 16.25Additional transaction records. 16.25 #BREAK# 976 Files and Streams Chapter 16 16.10 You are the owner of a hardware store and need to keep an inventory that can tell you what different tools you have, how many of each you have on hand and the cost of each one. Write a program that initializes the random-access file "hardware.dat"to one hundred empty records, lets you input the data concerning each tool, enables you to list all your tools, lets you delete a record for a tool that you no longer have and lets you update any information in the file. The tool identification number should be the record number. Use the information in Fig. 16.26 to start your file. 16.11 (Telephone Number Word Generator) Standard telephone keypads contain the digits 0 through 9. The numbers 2 through 9 each have three letters associated with them (see Fig. 16.27). Many people find it difficult to memorize phone numbers, so they use the correspondence between digits and letters to develop seven-letter words that correspond to their phone numbers. For example, a person whose telephone number is 686-2377 might use the correspondence indicated in Fig. 16.27 to develop the seven-letter word NUMBERS. Each seven-letter word corresponds to exactly one seven-digit telephone number. The restaurant wishing to increase its takeout business could surely do so with the number 825-3688 (i.e., TAKEOUT ). Record # Tool name Quantity Cost 3 Electric sander 18 35.99 19 Hammer 128 10.00 26 Jig saw 16 14.25 39 Lawn mower 10 79.50 56 Power saw 8 89.99 76 Screwdriver 236 4.99 81 Sledge hammer 32 19.75 88 Wrench 65 6.48 Fig. 16.26 16.26 Data for Exercise 16.10. Digit Letters 2 A 3 D 4 G 5 J 6 M 7 P 8 T 9 W Fig. 16.27 g. 16.27 Telephone keypad digits and letters. #BREAK# Chapter 16 Files and Streams 977 Each seven-letter phone number corresponds to many separate seven-letter words. Unfortunately, most of these represent unrecognizable juxtapositions of letters. It is possible, however, that the owner of a barber shop would be pleased to know that the shop s telephone number, 424-7288, corresponds to HAIRCUT. The owner of a liquor store would, no doubt, be delighted to find that the store s number, 233-7226, corresponds to BEERCAN. A veterinarian with the phone number 738-2273 would be pleased to know that the number corresponds to the letters PETCARE. An automotive dealership would be pleased to know that the dealership number, 639-2277, corresponds to NEWCARS. Write a program that, given a seven-digit number, writes to a file every possible seven-letter word combination corresponding to that number. There are 2187 (37) such combinations. Avoid phone numbers with the digits 0 and 1. #BREAK# 17 Networking Objectives To understand Java networking with URIs, sockets and datagrams. To implement Java networking applications by using sockets and datagrams. To understand how to implement Java clients and servers that communicate with one another. To understand how to implement network-based collaborative applications. To construct a multithreaded server. If the presence of electricity can be made visible in any part of a circuit, I see no reason why intelligence may not be transmitted instantaneously by electricity. Samuel F. B. Morse Mr. Watson, come here, I want you. Alexander Graham Bell What networks of railroads, highways and canals were in another age, the networks of telecommunications, information and computerization are today. Bruno Kreisky, Austrian Chancellor Science may never come up with a better office- communication system than the coffee break. Earl Wilson It s currently a problem of access to gigabits through punybaud. J. C. R. Licklider #BREAK# Chapter 17 Networking 979 Outline 17.1 Introduction 17.2 Manipulating URIs 17.3 Reading a File on a Web Server 17.4 Establishing a Simple Server Using Stream Sockets 17.5 Establishing a Simple Client Using Stream Sockets 17.6 Client/Server Interaction with Stream Socket Connections 17.7 Connectionless Client/Server Interaction with Datagrams 17.8 Client/Server Tic-Tac-Toe Using a Multithreaded Server 17.9 Security and the Network 17.10 DeitelMessenger Chat Server and Client 17.10.1 DeitelMessengerServer and Supporting Classes 17.10.2 DeitelMessenger Client and Supporting Classes 17.11 (Optional) Discovering Design Patterns: Design Patterns Used in Packages java.io and java.net 17.11.1 Creational Design Patterns 17.11.2 Structural Design Patterns 17.11.3 Architectural Patterns 17.11.4 Conclusion Summary Terminology Self-Review Exercises Answers to Self-Review Exercises Exercises 17.1 Introduction There is much excitement over the Internet and the World Wide Web. The Internet ties the information world together. The World Wide Web makes the Internet easy to use and gives it the flair and sizzle of multimedia. Organizations see the Internet and the Web as crucial to their information-systems strategies. Java provides a number of built-in networking capabilities that make it easy to develop Internet-based and Web-based applications. Not only can Java specify parallelism through multithreading, but it can enable programs to search the world for information and to collaborate with programs running on other computers internationally, nationally or just within an organization. Java can enable applets and applications to communicate with one another (subject to security constraints). Networking is a massive and complex topic. Computer science and computer engineering students will typically take a full-semester, upper level course in computer networking and continue with further study at the graduate level. Java provides a rich complement of networking capabilities and will likely be used as an implementation vehicle in computer networking courses. In Java How to Program, Fourth Edition, we introduce a portion of Java s networking concepts and capabilities. For more advanced networking capabilities, refer to our book Advanced Java 2 Platform How to Program. Java s networking capabilities are grouped into several packages. The fundamental networking capabilities are defined by classes and interfaces of package java.net, #BREAK# 980 Networking Chapter 17 through which Java offers socket-based communications that enable applications to view networking as streams of data. The classes and interfaces of package java.net also offer packet-based communications that enable individual packets of information to be transmitted this is commonly used to transmit audio and video over the Internet. In this chapter, we show how to create and manipulate sockets and how to communicate with packets of data. Our discussion of networking focuses on both sides of a client-server relationship. The client requests that some action be performed, and the server performs the action and responds to the client. A common implementation of the request-response model is between World Wide Web browsers and World Wide Web servers. When a user selects a Web site to browse through a browser (the client application), a request is sent to the appropriate Web server (the server application). The server normally responds to the client by sending an appropriate HTML Web page. We demonstrate the Swing GUI component JEditorPane and its ability to render an HTML document downloaded from the World Wide Web. We also introduce Java s a socket-based communications, which enable applications to view networking as if it were file I/O a program can read from a socket or write to a socket as simply as reading from a file or writing to a file. We show how to create and manipulate sockets. Java provides stream sockets and datagram sockets. With stream sockets, a process establishes a connection to another process. While the connection is in place, data flows between the processes in continuous streams. Stream sockets are said to provide a connection-oriented service. The protocol used for transmission is the popular TCP (Transmission Control Protocol). With datagram sockets, individual packets of information are transmitted. This is not the right protocol for everyday users, because, unlike TCP, the protocol used UDP, the User Datagram Protocol is a connectionless service, and it does not guarantee that packets arrive in any particular order. In fact, packets can be lost, can be duplicated and can even arrive out of sequence. So, with UDP, significant extra programming is required on the user s part to deal with these problems (if the user chooses to do so). UDP is most appropriate for network applications that do not require the error checking and reliability of TCP. Stream sockets and the TCP protocol will be the most desirable for the vast majority of Java programmers. Performance Tip 17.1 Connectionless services generally offer greater performance, but less reliability than connection-oriented services. Portability Tip 17.1 The TCP protocol and its related set of protocols enable a great variety of heterogeneous computer systems (i.e., computer systems with different processors and different operating systems) to intercommunicate. The chapter ends with a case study in which we implement a client/server chat application similar to the instant-messaging services popular on the Web today. The program incorporates many networking techniques introduced in this chapter. The program also introduces multicasting, in which a server can publish information and clients can subscribe to that information. Each time the server publishes more information, all subscribers receive that information. Throughout the examples of this chapter, we will see that many of the networking details are handled by the Java classes we use. #BREAK# Chapter 17 Networking 981 17.2 Manipulating URIs The Internet offers many protocols. The HyperText Transfer Protocol (HTTP) that forms the basis of the World Wide Web uses URIs (Uniform Resource Identifiers) to locate data on the Internet. URIs frequently are called URLs (Uniform Resource Locators). Actually, a URL is a type of URI. Common URIs refer to files or directories and can reference objects that perform complex tasks, such as database lookups and Internet searches. If you know the URI of publicly available HTML files anywhere on the World Wide Web, you can access that data through HTTP. Java makes it easy to manipulate URIs. Using a URI that refers to the exact location of a resource (such as a Web page) as an argument to the showDocument method of interface AppletContext causes the browser in which the applet is executing to display the resource at the specified URI. The applet of Fig. 17.1 and Fig. 17.2 enables the user to select a Web page from a JListand causes the browser to display the corresponding page. This applet takes advantage of applet parameters specified in the HTML document that invokes the applet. When browsing the World Wide Web, often you will come across applets that are in the public domain you can use them free of charge on your own Web pages (normally in exchange for crediting the applet s creator). One common feature of such applets is the ability to customize the applet via parameters that are supplied from the HTML file that invokes the applet. For example, Fig. 17.1 contains the HTML that invokes the applet Site- Selectorin Fig. 17.2. The HTML document contains eight parameters specified with the param element these lines must appear between the starting and ending applettags. The applet can read these values and use them to customize itself. Any number of paramtags can appear between the starting and ending applet tags. Each parameter has a name and a value. Appletmethod getParameter retrieves the valueassociated with a specific parameter and returns the valueas a String. The argument passed to getParameter is a Stringcontaining the name of the parameter in the paramtag. In this example, parameters represent the title of each Web site the user can select and the location of each site. Any number of parameters can be specified. However, these parameters must be named title#, where the value of # starts at 0 and increments by one for each new title. Each title should have a corresponding location parameter of the form location#, where the value of # starts at 0and increments by one for each new location. The statement 1 2 3
4 14 15 Fig. 17.1 HTML document to load SiteSelectorapplet. #BREAK# 982 Networking Chapter 17 String title = getParameter( “title0″ ); gets the value associated with the “title0″ parameter and assigns it to String reference title. If there is not a paramtag containing the specified parameter, getParameter returns null. The applet (Fig. 17.2) obtains from the HTML document (Fig. 17.1) the choices that will be displayed in the applet s JList. Class SiteSelector uses a Hashtable (package java.util) to store the World Wide Web site names and URIs. A Hashtable stores key/ value pairs. The program uses the key to store and retrieve the associated value in the Hash- table. In this example, the key is the String in the JList that represents the Web site name, and the value is a URL object that stores the URI of the Web site to display in the browser. Class Hashtable provides two methods of importance in this example put and get. Method put takes two arguments a key and its associated value and places the value in the Hashtable at a location determined by the key. Method get takes one argument a key and retrieves the value (as an Object reference) associated with the key. Class SiteSelector also contains a Vector (package java.util) in which the site names are placed so they can be used to initialize the JList (one version of the JList constructor receives a Vector object). A Vector is a dynamically resizable array of Objects. Class Vector provides method add to add a new element to the end of the Vector. Classes Hashtable and Vector are discussed in detail in Chapter 20. Lines 23 24 in method init (lines 20 63) create the Hashtable and Vector objects. Line 27 calls our utility method getSitesFromHTMLParameters (lines 66 108) to obtain the HTML parameters from the HTML document that invoked the applet. In method getSitesFromHTMLParameters, line 75 uses Applet method getParameterto obtain a Web site title. If the titleis not null, the loop at lines 78 106 begins executing. Line 81 uses Applet method getParameter to obtain the corresponding location. Line 87 uses the location as the initial value of a new URL object. The URL constructor determines whether the String passed as an argument represents a valid Uniform Resource Identifier. If not, the URL constructor throws a Malformed- URLException. Notice that the URL constructor must be called in a try block. If the URL constructor generates a MalformedURLException, the call to printStack- Trace (line 98) causes the program to display a stack trace. Then the program attempts to obtain the next Web site title. The program does not add the site for the invalid URI to the Hashtable, so the title will not be displayed in the JList. Common Programming Error 17.1 A MalformedURLException is thrown when a String that is not in proper URI format is passed to a URL constructor. For a proper URL, line 90 places the title and URL into the Hashtable, and line 93 adds the titleto the Vector. Line 104 gets the next title from the HTML document. When the call to getParameter at line 104 returns null, the loop terminates. When method getSitesFromHTMLParameters returns to init, lines 30 61 construct the applet s GUI. Lines 31 32 add the JLabel Chooseasitetobrowse to the NORTH of the content pane s BorderLayout. Lines 36 58 register an instance of an anonymous inner class that implements ListSelectionListener to handle the siteChooser s events. Lines 60 61 add siteChooserto the CENTER of the content pane s BorderLayout. #BREAK# Chapter 17 Networking 983 // Fig. 17.2: SiteSelector.java // This program uses a button to load a document from a URL. // Java core packages import java.net.*; import java.util.*; import java.awt.*; import java.applet.AppletContext; // Java extension packages import javax.swing.*; import javax.swing.event.*; public class SiteSelector extends JApplet { private Hashtable sites; // site names and URLs private Vector siteNames; // site names private JList siteChooser; // list of sites to choose from // read HTML parameters and set up GUI public void init() { // create Hashtable and Vector sites = new Hashtable(); siteNames = new Vector(); // obtain parameters from HTML document getSitesFromHTMLParameters(); // create GUI components and layout interface Container container = getContentPane(); container.add( new JLabel( “Choose a site to browse” ), BorderLayout.NORTH ); siteChooser = new JList( siteNames ); siteChooser.addListSelectionListener( new ListSelectionListener() { // go to site user selected public void valueChanged( ListSelectionEvent event ) { // get selected site name Object object = siteChooser.getSelectedValue(); // use site name to locate corresponding URL URL newDocument = ( URL ) sites.get( object ); // get reference to applet container AppletContext browser = getAppletContext(); // tell applet container to change pages browser.showDocument( newDocument ); Fig. 17.2Loading a document from a URL into a browser (part 1 of 3). Fig. 17.2 #BREAK# 984 Networking Chapter 17 54 } // end method valueChanged 55 56 } // end anonymous inner class 57 58 ); // end call to addListSelectionListener 59 60 container.add( new JScrollPane( siteChooser ), 61 BorderLayout.CENTER ); 62 63 } // end method init 64 65 // obtain parameters from HTML document 66 private void getSitesFromHTMLParameters() 67 { 68 // look for applet parameters in the HTML document 69 // and add sites to Hashtable 70 String title, location; 71 URL url; 72 int counter = 0; 73 74 // obtain first site title 75 title = getParameter( “title” + counter ); 76 77 // loop until no more parameters in HTML document 78 while ( title != null ) { 79 80 // obtain site location 81 location = getParameter( “location” + counter ); 82 83 // place title/URL in Hashtable and title in Vector 84 try { 85 86 // convert location to URL 87 url = new URL( location ); 88 89 // put title/URL in Hashtable 90 sites.put( title, url ); 91 92 // put title in Vector 93 siteNames.add( title ); 94 } 95 96 // process invalid URL format 97 catch ( MalformedURLException urlException ) { 98 urlException.printStackTrace(); 99 } 100 101 ++counter; 102 103 // obtain next site title 104 title = getParameter( “title” + counter ); 105 106 } // end while Fig. 17.2Loading a document from a URL into a browser (part 2 of 3). Fig. 17.2 #BREAK# Chapter 17 Networking 985 107 108 } // end method getSitesFromHTMLParameters 109 110 } // end class SiteSelector Fig. 17.2Loading a document from a URL into a browser (part 3 of 3). Fig. 17.2 When the user selects one of the Web sites in siteChooser, the program calls method valueChanged (lines 41 54). Line 44 obtains the selected site name from the JList. Line 47 passes the selected site name (the key) to Hashtablemethod get, which locates and returns an Objectreference to the corresponding URLobject (the value). The URLcast operator converts the reference to a URLthat can be assigned to reference newDocument. Line 50 uses Applet method getAppletContext to get a reference to an AppletContextobject that represents the applet container. Line 53 uses the Applet- Context reference browser to invoke AppletContext method showDocument, which receives a URL object as an argument and passes it to the AppletContext (i.e., the browser). The browser displays in the current browser window the World Wide Web resource associated with that URL. In this example, all the resources are HTML documents. For programmers familiar with HTML frames, there is a second version of Applet- Contextmethod showDocumentthat enables an applet to specify the so-called target frame in which to display the World Wide Web resource. The second version of show- Document takes two arguments a URL object specifying the resource to display and a String representing the target frame. There are some special target frames that can be used as the second argument. The target frame _blank results in a new Web browser #BREAK# 986 Networking Chapter 17 window to display the content from the specified URI. The target frame _self specifies that the content from the specified URI should be displayed in the same frame as the applet (the applet s HTML page is replaced in this case). The target frame _top specifies that the browser should remove the current frames in the browser window, then display the content from the specified URI in the current window. For more information on HTML and frames, see the World Wide Web Consortium (W3C) Web site http://www.w3.org [Note: This applet must be run from a World Wide Web browser, such as Netscape Navigator or Microsoft Internet Explorer, to see the results of displaying another Web page. The appletviewer is capable only of executing applets it ignores all other HTML tags. If the Web sites in the program contained Java applets, only those applets would appear in the appletviewerwhen the user selects a Web site. Each applet would execute in a separate appletviewer window. Of the browsers mentioned here, only Netscape Navigator 6 currently supports the features of Java 2. You will need to use the Java Plug-in (discussed in Chapter 3) to execute this applet in Microsoft Internet Explorer or older versions of Netscape Navigator.] 17.3 Reading a File on a Web Server The application of Fig. 17.3 uses Swing GUI component JEditorPane (from package javax.swing) to display the contents of a file on a Web server. The user inputs the URI in the JTextFieldat the top of the window, and the program displays the corresponding document (if it exists) in the JEditorPane. Class JEditorPaneis able to render both plain text and HTML-formatted text, so this application acts as a simple Web browser. The application also demonstrates how to process HyperlinkEvents when the user clicks a hyperlink in the HTML document. The screen captures in Fig. 17.3 illustrate that the JEditorPane can display both simple text (the first screen) and HTML text (the second screen). The techniques shown in this example also can be used in applets. However, applets are allowed to read files only on the server from which the applet was downloaded. 1 // Fig. 17.3: ReadServerFile.java 2 // This program uses a JEditorPane to display the 3 // contents of a file on a Web server. 4 5 // Java core packages 6 import java.awt.*; 7 import java.awt.event.*; 8 import java.net.*; 9 import java.io.*; 10 11 // Java extension packages 12 import javax.swing.*; 13 import javax.swing.event.*; 14 15 public class ReadServerFile extends JFrame { 16 private JTextField enterField; 17 private JEditorPane contentsArea; Fig. 17.3Reading a file by opening a connection through a URL(part 1 of 4) Fig. 17.3 #BREAK# Chapter 17 Networking 987 18 19 // set up GUI 20 public ReadServerFile() 21 { 22 super( “Simple Web Browser” ); 23 24 Container container = getContentPane(); 25 26 // create enterField and register its listener 27 enterField = new JTextField( “Enter file URL here” ); 28 29 enterField.addActionListener( 30 31 new ActionListener() { 32 33 // get document specified by user 34 public void actionPerformed( ActionEvent event ) 35 { 36 getThePage( event.getActionCommand() ); 37 } 38 39 } // end anonymous inner class 40 41 ); // end call to addActionListener 42 43 container.add( enterField, BorderLayout.NORTH ); 44 45 // create contentsArea and register HyperlinkEvent listener 46 contentsArea = new JEditorPane(); 47 contentsArea.setEditable( false ); 48 49 contentsArea.addHyperlinkListener( 50 51 new HyperlinkListener() { 52 53 // if user clicked hyperlink, go to specified page 54 public void hyperlinkUpdate( HyperlinkEvent event ) 55 { 56 if ( event.getEventType() == 57 HyperlinkEvent.EventType.ACTIVATED ) 58 getThePage( event.getURL().toString() ); 59 } 60 61 } // end anonymous inner class 62 63 ); // end call to addHyperlinkListener 64 65 container.add( new JScrollPane( contentsArea ), 66 BorderLayout.CENTER ); 67 68 setSize( 400, 300 ); 69 setVisible( true ); 70 } Fig. 17.3Reading a file by opening a connection through a URL(part 2 of 4) Fig. 17.3 #BREAK# 988 Networking Chapter 17 71 72 // load document; change mouse cursor to indicate status 73 private void getThePage( String location ) 74 { 75 // change mouse cursor to WAIT_CURSOR 76 setCursor( Cursor.getPredefinedCursor( 77 Cursor.WAIT_CURSOR ) ); 78 79 // load document into contentsArea and display location in 80 // enterField 81 try { 82 contentsArea.setPage( location ); 83 enterField.setText( location ); 84 } 85 86 // process problems loading document 87 catch ( IOException ioException ) { 88 JOptionPane.showMessageDialog( this, 89 “Error retrieving specified URL”, 90 “Bad URL”, JOptionPane.ERROR_MESSAGE ); 91 } 92 93 setCursor( Cursor.getPredefinedCursor( 94 Cursor.DEFAULT_CURSOR ) ); 95 } 96 97 // begin application execution 98 public static void main( String args[] ) 99 { 100 ReadServerFile application = new ReadServerFile(); 101 102 application.setDefaultCloseOperation( 103 JFrame.EXIT_ON_CLOSE ); 104 } 105 106 } // end class ReadServerFile Fig. 17.3Reading a file by opening a connection through a URL(part 3 of 4) Fig. 17.3 #BREAK# Chapter 17 Networking 989 Fig. 17.3Reading a file by opening a connection through a URL(part 4 of 4) Fig. 17.3 The application class ReadServerFilecontains JTextFieldenterField, in which the user enters the URI of the file to read and JEditorPanecontentsAreato display the contents of the file. When the user presses the Enter key in the JTextField, the program calls method actionPerformed (lines 34 37). Line 36 uses Action- Eventmethod getActionCommandto get the Stringthe user input in the JText- Fieldand passes that string to utility method getThePage(lines 73 95). Lines 76 77 in method getThePageuse method setCursor (inherited into class JFrame from class Component) to change the mouse cursor to the wait cursor (normally, an hourglass or a watch). If the file being downloaded is large, the wait cursor indicates to the user that the program is performing a task and that the user should wait for the task to complete. The staticCursormethod getPredefinedCursor receives an integer indicating the cursor type (Cursor.WAIT_CURSOR in this case). See the API documentation for class Cursorfor a complete list of cursors. Line 82 uses JEditorPanemethod setPage to download the document specified by locationand display it in the JEditorPanecontents. If there is an error downloading the document, method setPage throws an IOException. Also, if an invalid URL is specified, a MalformedURLException(a sublcass of IOException) occurs. If the document loads successfully, line 83 displays the current location in enterField. Lines 93 94 sets the Cursor back to Cursor.DEFAULT_CURSOR (the default Cursor) to indicate that the document download is complete. Typically, an HTML document contains hyperlinks text, images or GUI components which, when clicked, provide quick access to another document on the Web. If a JEditorPane contains an HTML document and the user clicks a hyperlink, the JEditor- Pane generates a HyperlinkEvent (package javax.swing.event) and notifies all registered HyperlinkListeners (package javax.swing.event) of that event. Lines 49 63 register a HyperlinkListenerto handle HyperlinkEvents. When a HyperlinkEvent occurs, the program calls method hyperlinkUpdate (lines 54 #BREAK# 990 Networking Chapter 17 59). Lines 56 57 use HyperlinkEvent method getEventType to determine the type of the HyperlinkEvent. Class HyperlinkEvent contains public inner class EventType that defines three hyperlink event types: ACTIVATED (the user clicked a hyperlink to change Web pages), ENTERED (the user moved the mouse over a hyperlink) and EXITED (the user moved the mouse away from a hyperlink). If a hyperlink was ACTIVATED, line 58 uses HyperlinkEvent method getURL to obtain the URL represented by the hyperlink. Method toString converts the returned URL to a String format that can be passed to utility method getThePage. Software Engineering Observation 17.1 A JEditorPane generates HyperlinkEvents only if it is uneditable. 17.4 Establishing a Simple Server Using Stream Sockets The two examples discussed so far use high-level Java networking capabilities to communicate between applications. In those examples, it was not the Java programmer s responsibility to establish the connection between a client and a server. The first program relied on the Web browser to communicate with a Web server. The second program relied on a JEditorPane to perform the connection. This section begins our discussion of creating your own applications that can communicate with one another. Establishing a simple server in Java requires five steps. Step 1 is to create a Server- Socket object. A call to the ServerSocket constructor such as ServerSocket server = new ServerSocket( port, queueLength ); registers an available port number and specifies a maximum number of clients that can wait to connect to the server (i.e., the queueLength). The port number is used by clients to located the server application on the server computer. This often is called the handshake point. If the queue is full, the server refuses client connections. The preceding statement establishes the port where the server waits for connections from clients (a process known as binding the server to the port). Each client will ask to connect to the server on this port. Programs manage each client connection with a Socket object. After binding the server to a port with a ServerSocket (Step 2), the server listens indefinitely (or blocks) for an attempt by a client to connect. To listen for a client, the program calls Server- Socket method accept, as in Socket connection = server.accept(); This statement returns a Socket object when a connection with a client is established. Step 3 is to get the OutputStream and InputStream objects that enable the server to communicate with the client by sending and receiving bytes. The server sends information to the client via an OutputStream object. The server receives information from the client via an InputStream object. To obtain the streams, the server invokes method getOutputStreamon the Socket to get a reference to the OutputStream associated with the Socket and invokes method getInputStream on the Socketto get a reference to the InputStream associated with the Socket. The OutputStream and InputStream objects can be used to send or receive individual bytes or sets of bytes with the OutputStream method write and the Input- Stream method read, respectively. Often it is useful to send or receive values of primitive #BREAK# Chapter 17 Networking 991 data types (such as int and double) or Serializable class data types (such as String) rather than sending bytes. In this case, we can use the techniques of Chapter 16 to chain other stream types (such as ObjectOutputStream and ObjectInputStream) to the OutputStream and InputStream associated with the Socket. For example, ObjectInputStream input = new ObjectInputStream( connection.getInputStream() ); ObjectOutputStream output = new ObjectOutputStream( connection.getOutputStream() ); The beauty of establishing these relationships is that whatever the server writes to the ObjectOutputStream is sent via the OutputStream and is available at the client s InputStreamand whatever the client writes to its OutputStream (with a corresponding ObjectOutputStream) is available via the server s InputStream. Step 4 is the processing phase, in which the server and the client communicate via the InputStream and OutputStream objects. In Step 5, when the transmission is complete, the server closes the connection by invoking the close method on the Socket and on the corresponding streams. Software Engineering Observation 17.2 With sockets, network I/O appears to Java programs to be identical to sequential file I/O. Sockets hide much of the complexity of network programming from the programmer. Software Engineering Observation 17.3 With Java s multithreading, we can create multithreaded servers that can manage many simultaneous connections with many clients; this multithreaded-server architecture is precisely what popular network servers use. Software Engineering Observation 17.4 A multithreaded server can take the Socket returned by each call to accept and create a new thread that manages network I/O across that Socket, or a multithreaded server can maintain a pool of threads (a set of already existing threads) ready to manage network I/O across the new Sockets as they are created. Performance Tip 17.2 In high-performance systems in which memory is abundant, a multithreaded server can be implemented to create a pool of threads that can be assigned quickly to handle network I/O across each new Socket as it is created. Thus, when the server receives a connection, the server need not incur the overhead of thread creation. 17.5 Establishing a Simple Client Using Stream Sockets Establishing a simple client in Java requires four steps. In Step 1, we create a Socket to connect to the server. The Socketconstructor establishes the connection to the server. For example, the statement Socket connection = new Socket( serverAddress, port ); uses the Socket constructor with two arguments the server s Internet address (server- Address) and the port number. If the connection attempt is successful, this statement returns #BREAK# 992 Networking Chapter 17 a Socket. A connection attempt that fails throws an instance of a subclass of IOException, so many programs simply catch IOException. An UnknownHostException occurs when a server address indicated by a client cannot be resolved. A ConnectException is thrown when an error occurs while attempting to connect to a server. In Step 2, the client uses Socket methods getInputStream and getOutput- Stream to obtain references to the Socket s InputStream and OutputStream. As we mentioned in the preceding section, often it is useful to send or receive values of primitive data types (such as int and double) or class data types (such as String and Employee) rather than sending bytes. If the server is sending information in the form of actual data types, the client should receive the information in the same format. Thus, if the server sends values with an ObjectOutputStream, the client should read those values with an ObjectInputStream. Step 3 is the processing phase in which the client and the server communicate via the InputStream and OutputStreamobjects. In Step 4, the client closes the connection when the transmission is complete by invoking the close method on the Socket and the corresponding streams. When processing information sent by a server, the client must determine when the server is finished sending information so the client can call closeto close the Socket connection. For example, the InputStream method read returns the value 1 when it detects end-of-stream (also called EOF end-of-file). If an ObjectInputStream is used to read information from the server, an EOFException occurs when the client attempts to read a value from a stream on which end-of-stream is detected. 17.6 Client/Server Interaction with Stream Socket Connections The applications of Fig. 17.4 and Fig. 17.5 use stream sockets to demonstrate a simple client/server chat application. The server waits for a client connection attempt. When a client application connects to the server, the server application sends a String object (remember that Strings are Serializable) indicating that the connection was successful to the client. Then the client displays the message. Both the client and the server applications contain JTextFields, which allow the user to type a message and send it to the other application. When the client or the server sends the String TERMINATE , the connection between the client and the server terminates. Then the server waits for the next client to connect. The definition of class Server appears in Fig. 17.4. The definition of class Clientappears in Fig. 17.5. The screen captures showing the execution between the client and the server are shown as part of Fig. 17.5. Class Server s constructor (lines 25 58) creates the GUI of the application (a JTextField and a JTextArea). The Server object displays its output in a JText- Area. When the main method (lines 186 194) executes, it creates an instance of class Server, specifies the window s default close operation and calls method runServer (defined at lines 61 97). Method runServer does the work of setting up the server to receive a connection and processing the connection when it occurs. The method creates a ServerSocket called server (line 68) to wait for connections. The ServerSocket is set up to listen for a connection from a client at port 5000. The second argument to the constructor is the number of connections that can wait in a queue to connect to the server (100 in this example). If the queue is full when a client attempts to connect, the server refuses the connection. #BREAK# Chapter 17 Networking 993 // Fig. 17.4: Server.java // Set up a Server that will receive a connection // from a client, send a string to the client, // and close the connection. // Java core packages import java.io.*; import java.net.*; import java.awt.*; import java.awt.event.*; // Java extension packages import javax.swing.*; public class Server extends JFrame { private JTextField enterField; private JTextArea displayArea; private ObjectOutputStream output; private ObjectInputStream input; private ServerSocket server; private Socket connection; private int counter = 1; // set up GUI public Server() { super( “Server” ); Container container = getContentPane(); // create enterField and register listener enterField = new JTextField(); enterField.setEnabled( false ); enterField.addActionListener( new ActionListener() { // send message to client public void actionPerformed( ActionEvent event ) { sendData( event.getActionCommand() ); } } // end anonymous inner class ); // end call to addActionListener container.add( enterField, BorderLayout.NORTH ); // create displayArea displayArea = new JTextArea(); Fig. 17.4Server portion of a client/server stream-socket connection (part 1 of 4). Fig. 17.4 #BREAK# 994 Networking Chapter 17 53 container.add( new JScrollPane( displayArea ), 54 BorderLayout.CENTER ); 55 56 setSize( 300, 150 ); 57 setVisible( true ); 58 } 59 60 // set up and run server 61 public void runServer() 62 { 63 // set up server to receive connections; 64 // process connections 65 try { 66 67 // Step 1: Create a ServerSocket. 68 server = new ServerSocket( 5000, 100 ); 69 70 while ( true ) { 71 72 // Step 2: Wait for a connection. 73 waitForConnection(); 74 75 // Step 3: Get input and output streams. 76 getStreams(); 77 78 // Step 4: Process connection. 79 processConnection(); 80 81 // Step 5: Close connection. 82 closeConnection(); 83 84 ++counter; 85 } 86 } 87 88 // process EOFException when client closes connection 89 catch ( EOFException eofException ) { 90 System.out.println( “Client terminated connection” ); 91 } 92 93 // process problems with I/O 94 catch ( IOException ioException ) { 95 ioException.printStackTrace(); 96 } 97 } 98 99 // wait for connection to arrive, then display connection info 100 private void waitForConnection() throws IOException 101 { 102 displayArea.setText( “Waiting for connectionn” ); 103 104 // allow server to accept a connection 105 connection = server.accept(); Fig. 17.4Server portion of a client/server stream-socket connection (part 2 of 4). Fig. 17.4 #BREAK# Chapter 17 Networking 995 106 107 displayArea.append( “Connection ” + counter + 108 ” received from: ” + 109 connection.getInetAddress().getHostName() ); 110 } 111 112 // get streams to send and receive data 113 private void getStreams() throws IOException 114 { 115 // set up output stream for objects 116 output = new ObjectOutputStream( 117 connection.getOutputStream() ); 118 119 // flush output buffer to send header information 120 output.flush(); 121 122 // set up input stream for objects 123 input = new ObjectInputStream( 124 connection.getInputStream() ); 125 126 displayArea.append( “nGot I/O streamsn” ); 127 } 128 129 // process connection with client 130 private void processConnection() throws IOException 131 { 132 // send connection successful message to client 133 String message = “SERVER>>> Connection successful”; 134 output.writeObject( message ); 135 output.flush(); 136 137 // enable enterField so server user can send messages 138 enterField.setEnabled( true ); 139 140 // process messages sent from client 141 do { 142 143 // read message and display it 144 try { 145 message = ( String ) input.readObject(); 146 displayArea.append( “n” + message ); 147 displayArea.setCaretPosition( 148 displayArea.getText().length() ); 149 } 150 151 // catch problems reading from client 152 catch ( ClassNotFoundException classNotFoundException ) { 153 displayArea.append( “nUnknown object type received” ); 154 } 155 156 } while ( !message.equals( “CLIENT>>> TERMINATE” ) ); 157 } 158 Fig. 17.4Server portion of a client/server stream-socket connection (part 3 of 4). Fig. 17.4 #BREAK# 996 Networking Chapter 17 159 // close streams and socket 160 private void closeConnection() throws IOException 161 { 162 displayArea.append( “nUser terminated connection” ); 163 enterField.setEnabled( false ); 164 output.close(); 165 input.close(); 166 connection.close(); 167 } 168 169 // send message to client 170 private void sendData( String message ) 171 { 172 // send object to client 173 try { 174 output.writeObject( “SERVER>>> ” + message ); 175 output.flush(); 176 displayArea.append( “nSERVER>>>” + message ); 177 } 178 179 // process problems sending object 180 catch ( IOException ioException ) { 181 displayArea.append( “nError writing object” ); 182 } 183 } 184 185 // execute application 186 public static void main( String args[] ) 187 { 188 Server application = new Server(); 189 190 application.setDefaultCloseOperation( 191 JFrame.EXIT_ON_CLOSE ); 192 193 application.runServer(); 194 } 195 196 } // end class Server Fig. 17.4Server portion of a client/server stream-socket connection (part 4 of 4). Fig. 17.4 Software Engineering Observation 17.5 Port numbers can be between 0 and 65,535. Many operating systems reserve port numbers below 1024 for system services (such as e-mail and World Wide Web servers). Generally, these ports should not be specified as connection ports in user programs. In fact, some operating systems require special access privileges to use port numbers below 1024. Line 73 calls method waitForConnection (lines 100 110) to wait for a client connection. After the connection is established, line 76 calls method getStreams(lines 113 127) to obtain references to the InputStream and OutputStream for the connection. Line 79 calls method processConnectionto send the initial connection message to the client and to process all messages received from the client. Line 82 calls method closeConnection to terminate the connection with the client. #BREAK# Chapter 17 Networking 997 In method waitForConnection (lines 100 110), line 105 uses ServerSocket method accept to wait for a connection from a client and assigns the resulting Socket to connection. This method blocks until a connection is received (i.e., the thread in which acceptis called stops executing until a client connects). Lines 107 109 output the host name of the computer that made the connection. Socket method getInetAddress returns an InetAddress object (package java.net) containing information about the client computer. InetAddress method getHostName returns the host name of the client computer. For example, if the Internet address of the computer is 127.0.0.1, the corresponding host name would be localhost. Method getStreams (lines 113 127) obtains references to the InputStream and OutputStream of the Socket and uses them to initialize an ObjectInputStream and an ObjectOutputStream, respectively. Notice the call to ObjectOutput- Stream method flush at line 120. This statement causes the ObjectOutputStream on the server to send a stream header to the corresponding client s ObjectInputStream. The stream header contains information such as the version of object serialization being used to send objects. This information is required by the ObjectInputStream so it can prepare to receive those objects correctly. Software Engineering Observation 17.6 When using an ObjectOutputStream and ObjectInputStream to send and receive objects over a network connection, always create the ObjectOutputStream first and flush the stream so the client s ObjectInputStream can prepare to receive the data. This is required only for applications that communicate using ObjectOutputStream and ObjectInputStream. Line 134 of method processConnection (lines 130 157) uses ObjectOutputStream method writeObject to send the string SERVER>>> Connection successful to the client. Line 135 flushes the output stream to ensure that the object is sent immediately; otherwise, the object could be held in an output buffer until more information is available to send. Performance Tip 17.3 Output buffers typically are used to increase the efficiency of an application by sending larger amounts of data fewer times. The input and output components of a computer are typically much slower than the memory of the computer. The do/while structure at lines 141 156 loops until the server receives the message CLIENT>>>TERMINATE. Line 145 uses ObjectInputStream method read- Objectto read a Stringfrom the client. Line 146 displays the message in the JText- Area. Lines 147 148 use JTextComponent method setCaretPosition to position the input cursor in the JTextArea after the last character in the JTextArea. This scrolls the JTextArea as text is appended to it. When the transmission is complete, method processConnection returns and the program calls method closeConnection(lines 160 167) to close the streams associated with the Socket and close the Socket. Next, the server waits for the next connection attempt from a client by continuing with line 73 at the beginning of the whileloop. When the user of the server application enters a String in the JTextField and presses the Enter key, the program calls method actionPerformed (lines 40 43), reads the String from the JTextField and calls utility method sendData (lines 170 183). #BREAK# 998 Networking Chapter 17 Method sendData sends the String object to the client, flushes the output buffer and appends the same Stringto the JTextAreain the server window. Notice that the Server receives a connection, processes the connection, closes the connection and waits for the next connection. A more likely scenario would be a Server that receives a connection, sets up that connection to be processed as a separate thread of execution, and waits for new connections. The separate threads that process existing connections can continue to execute while the Server concentrates on new connection requests. Like class Server, class Client s (Fig. 17.5) constructor creates the GUI of the application (a JTextFieldand a JTextArea). The Clientobject displays its output in a JTextArea. When the mainmethod (line 175 188) executes, it creates an instance of class Client, specifies the window s default close operation and calls method run- Client (defined at lines 63 90). In this example, you can execute the client from any computer on the Internet and specify the Internet address or host name of the server computer as a command-line argument to the program. For example, java Client 192.168.1.15 connects to the Serveron the computer with Internet address 192.168.1.15. 1 // Fig. 17.5: Client.java 2 // Set up a Client that will read information sent 3 // from a Server and display the information. 4 5 // Java core packages 6 import java.io.*; 7 import java.net.*; 8 import java.awt.*; 9 import java.awt.event.*; 10 11 // Java extension packages 12 import javax.swing.*; 13 14 public class Client extends JFrame { 15 private JTextField enterField; 16 private JTextArea displayArea; 17 private ObjectOutputStream output; 18 private ObjectInputStream input; 19 private String message = “”; 20 private String chatServer; 21 private Socket client; 22 23 // initialize chatServer and set up GUI 24 public Client( String host ) 25 { 26 super( “Client” ); 27 28 // set server to which this client connects 29 chatServer = host; Fig. 17.5Demonstrating the client portion of a stream-socket connection between a Fig. 17.5 client and a server (part 1 of 5). #BREAK# Chapter 17 Networking 999 30 31 Container container = getContentPane(); 32 33 // create enterField and register listener 34 enterField = new JTextField(); 35 enterField.setEnabled( false ); 36 37 enterField.addActionListener( 38 39 new ActionListener() { 40 41 // send message to server 42 public void actionPerformed( ActionEvent event ) 43 { 44 sendData( event.getActionCommand() ); 45 } 46 47 } // end anonymous inner class 48 49 ); // end call to addActionListener 50 51 container.add( enterField, BorderLayout.NORTH ); 52 53 // create displayArea 54 displayArea = new JTextArea(); 55 container.add( new JScrollPane( displayArea ), 56 BorderLayout.CENTER ); 57 58 setSize( 300, 150 ); 59 setVisible( true ); 60 } 61 62 // connect to server and process messages from server 63 public void runClient() 64 { 65 // connect to server, get streams, process connection 66 try { 67 68 // Step 1: Create a Socket to make connection 69 connectToServer(); 70 71 // Step 2: Get the input and output streams 72 getStreams(); 73 74 // Step 3: Process connection 75 processConnection(); 76 77 // Step 4: Close connection 78 closeConnection(); 79 } 80 Fig. 17.5Demonstrating the client portion of a stream-socket connection between a Fig. 17.5 client and a server (part 2 of 5). #BREAK# 1000 Networking Chapter 17 81 // server closed connection 82 catch ( EOFException eofException ) { 83 System.out.println( “Server terminated connection” ); 84 } 85 86 // process problems communicating with server 87 catch ( IOException ioException ) { 88 ioException.printStackTrace(); 89 } 90 } 91 92 // get streams to send and receive data 93 private void getStreams() throws IOException 94 { 95 // set up output stream for objects 96 output = new ObjectOutputStream( 97 client.getOutputStream() ); 98 99 // flush output buffer to send header information 100 output.flush(); 101 102 // set up input stream for objects 103 input = new ObjectInputStream( 104 client.getInputStream() ); 105 106 displayArea.append( “nGot I/O streamsn” ); 107 } 108 109 // connect to server 110 private void connectToServer() throws IOException 111 { 112 displayArea.setText( “Attempting connectionn” ); 113 114 // create Socket to make connection to server 115 client = new Socket( 116 InetAddress.getByName( chatServer ), 5000 ); 117 118 // display connection information 119 displayArea.append( “Connected to: ” + 120 client.getInetAddress().getHostName() ); 121 } 122 123 // process connection with server 124 private void processConnection() throws IOException 125 { 126 // enable enterField so client user can send messages 127 enterField.setEnabled( true ); 128 129 // process messages sent from server 130 do { 131 Fig. 17.5Demonstrating the client portion of a stream-socket connection between a Fig. 17.5 client and a server (part 3 of 5). #BREAK# Chapter 17 Networking 1001 132 // read message and display it 133 try { 134 message = ( String ) input.readObject(); 135 displayArea.append( “n” + message ); 136 displayArea.setCaretPosition( 137 displayArea.getText().length() ); 138 } 139 140 // catch problems reading from server 141 catch ( ClassNotFoundException classNotFoundException ) { 142 displayArea.append( “nUnknown object type received” ); 143 } 144 145 } while ( !message.equals( “SERVER>>> TERMINATE” ) ); 146 147 } // end method process connection 148 149 // close streams and socket 150 private void closeConnection() throws IOException 151 { 152 displayArea.append( “nClosing connection” ); 153 output.close(); 154 input.close(); 155 client.close(); 156 } 157 158 // send message to server 159 private void sendData( String message ) 160 { 161 // send object to server 162 try { 163 output.writeObject( “CLIENT>>> ” + message ); 164 output.flush(); 165 displayArea.append( “nCLIENT>>>” + message ); 166 } 167 168 // process problems sending object 169 catch ( IOException ioException ) { 170 displayArea.append( “nError writing object” ); 171 } 172 } 173 174 // execute application 175 public static void main( String args[] ) 176 { 177 Client application; 178 179 if ( args.length == 0 ) 180 application = new Client( “127.0.0.1″ ); 181 else 182 application = new Client( args[ 0 ] ); 183 Fig. 17.5Demonstrating the client portion of a stream-socket connection between a Fig. 17.5 client and a server (part 4 of 5). #BREAK# 1002 Networking Chapter 17 184 application.setDefaultCloseOperation( 185 JFrame.EXIT_ON_CLOSE ); 186 187 application.runClient(); 188 } 189 190 } // end class Client The Server and Client windows after the Client connects to the Server The Server and Client windows after the Client sends a message to the Server The Server and Client windows after the Server sends a message to the Client The Server and Client windows after the Client terminates the connection Fig. 17.5Demonstrating the client portion of a stream-socket connection between a Fig. 17.5 client and a server (part 5 of 5). Clientmethod runClient(lines 63 90) performs the work necessary to connect to the Server, to receive data from the Serverand to send data to the Server. Line 69 calls method connectToServer(lines 110 121) to perform the connection. After connecting, line 72 calls method getStreams (lines 93 107) to obtain references to the Socket s InputStream and OutputStream objects. Then line 75 calls method processConnection (124 147) to handle messages sent from the server. When the connection terminates, line 78 calls closeConnection to close the streams and the Socket. Method connectToServer (lines 110 121) creates a Socket called client (lines 115 116) to establish a connection. The method passes two arguments to the #BREAK# Chapter 17 Networking 1003 Socket constructor the Internet address of the server computer and the port number (5000) where that computer is awaiting client connections. The call to InetAddress static method getByName in the first argument returns an InetAddress object containing the Internet address specified as a command-line argument to the application (or 127.0.0.1 if no command-line arguments are specified). Method getByName can receive a String containing either the actual Internet address or the host name of the server. The first argument also could have been written other ways. For the localhost address 127.0.0.1, the first argument could be InetAddress.getByName( “localhost” ) or InetAddress.getLocalHost() Also, there are versions of the Socket constructor that receive a String for the Internet address or host name. The first argument could have been specified as “127.0.0.1″or “localhost”. [Note: We chose to demonstrate the client/server relationship by connecting between programs executing on the same computer (localhost). Normally, this first argument would be the Internet address of another computer. The InetAddress object for another computer can be obtained by specifying the Internet address or host name of the other computer as the String argument to InetAddress.getByName.] The Socket constructor s second argument is the server port number. This number must match the port number at which the server is waiting for connections (called the handshake point). Once the connection is made, a message is displayed in the JTextArea (lines 119 120) indicating the name of the server computer to which the client connected. The Client uses an ObjectOutputStream to send data to the server and an ObjectInputStream to receive data from the server. Method getStreams (lines 93 107) creates the ObjectOutputStream and ObjectInputStream objects that use the OutputStream and InputStream objects associated with client. Method processConnection (lines 124 147) contains a do/while structure that loops until the client receives the message SERVER>>>TERMINATE. Line 134 uses ObjectInputStream method readObject to read a String from the server. Line 135 displays the message in the JTextArea. Lines 136 137 use JTextComponent method setCaretPosition to position the input cursor in the JTextArea after the last character in the JTextArea. When the transmission is complete, method closeConnection (lines 150 156) closes the streams and the Socket. When the user of the client application enters a String in the JTextField and presses the Enter key, the program calls method actionPerformed (lines 42 45) to read the String from the JTextField and invoke utility method sendData (159 172). Method sendData sends the String object to server client, flushes the output buffer and appends the same Stringto the JTextArea in the client window. 17.7 Connectionless Client/Server Interaction with Datagrams We have been discussing connection-oriented, streams-based transmission. Now we consider connectionless transmission with datagrams. #BREAK# 1004 Networking Chapter 17 Connection-oriented transmission is like the telephone system in which you dial and are given a connection to the telephone of the person with whom you wish to communicate. The connection is maintained for the duration of your phone call, even when you are not talking. Connectionless transmission with datagrams is more like the way mail is carried via the postal service. If a large message will not fit in one envelope, you break it into separate message pieces that you place in separate, sequentially numbered envelopes. Each of the letters is then mailed at the same time. The letters could arrive in order, out of order or not at all (although the last case is rare, it does happen). The person at the receiving end reassembles the message pieces into sequential order before attempting to make sense of the message. If your message is small enough to fit in one envelope, you do not have to worry about the out-of-sequence problem, but it is still possible that your message might not arrive. One difference between datagrams and postal mail is that duplicates of datagrams can arrive on the receiving computer. The programs of Fig. 17.6 and Fig. 17.7 use datagrams to send packets of information between a client application and a server application. In the Client application (Fig. 17.7), the user types a message into a JTextFieldand presses Enter. The program converts the message into a byte array and places it in a datagram packet that is sent to the server. The Server(Fig. 17.6) receives the packet and displays the information in the packet, then echoes the packet back to the client. When the client receives the packet, the client displays the information in the packet. In this example, the Client and Server classes are implemented similarly. Class Server (Fig. 17.6) defines two DatagramPackets that the server uses to send and receive information and one DatagramSocket that sends and receives these packets. The constructor for class Server (lines 20 41) creates the graphical user interface where the packets of information will be displayed. Next the constructor creates the DatagramSocket in a try block. Line 32 uses the DatagramSocket constructor that takes an integer port number argument (5000) to bind the server to a port where the server can receive packets from clients. Clients sending packets to this Serverspecify port 5000 in the packets they send. The DatagramSocket constructor throws a SocketException if it fails to bind the DatagramSocketto a port. Common Programming Error 17.2 Specifying a port that is already in use or specifying an invalid port number when creating a DatagramSocket results in a BindException. 1 // Fig. 17.6: Server.java 2 // Set up a Server that will receive packets from a 3 // client and send packets to a client. 4 5 // Java core packages 6 import java.io.*; 7 import java.net.*; 8 import java.awt.*; 9 import java.awt.event.*; 10 Fig. 17.6Demonstrating the server side of connectionless client/server computing Fig. 17.6 with datagrams (part 1 of 4). #BREAK# Chapter 17 Networking 1005 11 // Java extension packages 12 import javax.swing.*; 13 14 public class Server extends JFrame { 15 private JTextArea displayArea; 16 private DatagramPacket sendPacket, receivePacket; 17 private DatagramSocket socket; 18 19 // set up GUI and DatagramSocket 20 public Server() 21 { 22 super( “Server” ); 23 24 displayArea = new JTextArea(); 25 getContentPane().add( new JScrollPane( displayArea ), 26 BorderLayout.CENTER ); 27 setSize( 400, 300 ); 28 setVisible( true ); 29 30 // create DatagramSocket for sending and receiving packets 31 try { 32 socket = new DatagramSocket( 5000 ); 33 } 34 35 // process problems creating DatagramSocket 36 catch( SocketException socketException ) { 37 socketException.printStackTrace(); 38 System.exit( 1 ); 39 } 40 41 } // end Server constructor 42 43 // wait for packets to arrive, then display data and echo 44 // packet to client 45 public void waitForPackets() 46 { 47 // loop forever 48 while ( true ) { 49 50 // receive packet, display contents, echo to client 51 try { 52 53 // set up packet 54 byte data[] = new byte[ 100 ]; 55 receivePacket = 56 new DatagramPacket( data, data.length ); 57 58 // wait for packet 59 socket.receive( receivePacket ); 60 61 // process packet 62 displayPacket(); Fig. 17.6Demonstrating the server side of connectionless client/server computing Fig. 17.6 with datagrams (part 2 of 4). #BREAK# 1006 Networking Chapter 17 63 64 // echo information from packet back to client 65 sendPacketToClient(); 66 } 67 68 // process problems manipulating packet 69 catch( IOException ioException ) { 70 displayArea.append( ioException.toString() + “n” ); 71 ioException.printStackTrace(); 72 } 73 74 } // end while 75 76 } // end method waitForPackets 77 78 // display packet contents 79 private void displayPacket() 80 { 81 displayArea.append( “nPacket received:” + 82 “nFrom host: ” + receivePacket.getAddress() + 83 “nHost port: ” + receivePacket.getPort() + 84 “nLength: ” + receivePacket.getLength() + 85 “nContaining:nt” + 86 new String( receivePacket.getData(), 0, 87 receivePacket.getLength() ) ); 88 } 89 90 // echo packet to client 91 private void sendPacketToClient() throws IOException 92 { 93 displayArea.append( “nnEcho data to client…” ); 94 95 // create packet to send 96 sendPacket = new DatagramPacket( receivePacket.getData(), 97 receivePacket.getLength(), receivePacket.getAddress(), 98 receivePacket.getPort() ); 99 100 // send packet 101 socket.send( sendPacket ); 102 103 displayArea.append( “Packet sentn” ); 104 displayArea.setCaretPosition( 105 displayArea.getText().length() ); 106 } 107 108 // execute application 109 public static void main( String args[] ) 110 { 111 Server application = new Server(); 112 113 application.setDefaultCloseOperation( 114 JFrame.EXIT_ON_CLOSE ); Fig. 17.6Demonstrating the server side of connectionless client/server computing Fig. 17.6 with datagrams (part 3 of 4). #BREAK# Chapter 17 Networking 1007 115 116 application.waitForPackets(); 117 } 118 119 } // end class Server The Server window after the client sends a packet of data Fig. 17.6Demonstrating the server side of connectionless client/server computing Fig. 17.6 with datagrams (part 4 of 4). Server method waitForPackets (lines 45 76) uses an infinite loop to wait for packets to arrive at the Server. Lines 54 56 create a DatagramPacket in which a received packet of information can be stored. The DatagramPacket constructor for this purpose receives two arguments a byte array containing the data and the length of the bytearray. Line 59 waits for a packet to arrive at the Server. Method receive blocks until a packet arrives, then stores the packet in its DatagramPacket argument. Method receivethrows an IOExceptionif an error occurs receiving a packet. When a packet arrives, the program calls method displayPacket (lines 79 88) to append the packet s contents to displayArea. DatagramPacket method getAddress (line 82) returns an InetAddressobject containing the host name of the computer from which the packet was sent. Method getPort (line 83) returns an integer specifying the port number through which the host computer sent the packet. Method getLength (line 84) returns an integer representing the number of bytes of data that were sent. Method getData (line 86) returns a bytearray containing the data that was sent. The program uses the byte array to initialize a Stringobject so the data can be output to the JTextArea. After displaying a packet, the program calls method sendPacketToClient (line 65) to create a new packet and send it to the client. Lines 96 98 create sendPacketand pass four arguments to the DatagramPacket constructor. The first argument specifies the byte array to send. The second argument specifies the number of bytes to send. The third argument specifies the client computer s Internet address, to which the packet will be sent. The fourth argument specifies the port where the client is waiting to receive packets. Line 101 sends the packet over the network. Method send throws an IOException if an error occurs sending a packet. Class Client(Fig. 17.7) works similarly to class Server, except that the Client sends packets only when the user types a message in a JTextFieldand presses the Enter #BREAK# 1008 Networking Chapter 17 key. When this occurs, the program calls method actionPerformed (lines 34 67), which converts the Stringthe user entered in the JTextFieldinto a bytearray (line 45). Lines 48 50 create a DatagramPacket and initialize it with the byte array, the length of the Stringthat was entered by the user, the Internet address to which the packet is to be sent (InetAddress.getLocalHost()in this example) and the port number at which the Serveris waiting for packets. Line 53 sends the packet. Note that the client in this example must know that the server is receiving packets at port 5000; otherwise, the server will not receive the packets. 1 // Fig. 17.7: Client.java 2 // Set up a Client that will send packets to a 3 // server and receive packets from a server. 4 5 // Java core packages 6 import java.io.*; 7 import java.net.*; 8 import java.awt.*; 9 import java.awt.event.*; 10 11 // Java extension packages 12 import javax.swing.*; 13 14 public class Client extends JFrame { 15 private JTextField enterField; 16 private JTextArea displayArea; 17 private DatagramPacket sendPacket, receivePacket; 18 private DatagramSocket socket; 19 20 // set up GUI and DatagramSocket 21 public Client() 22 { 23 super( “Client” ); 24 25 Container container = getContentPane(); 26 27 enterField = new JTextField( “Type message here” ); 28 29 enterField.addActionListener( 30 31 new ActionListener() { 32 33 // create and send a packet 34 public void actionPerformed( ActionEvent event ) 35 { 36 // create and send packet 37 try { 38 displayArea.append( 39 “nSending packet containing: ” + 40 event.getActionCommand() + “n” ); 41 Fig. 17.7Demonstrating the client side of connectionless client/server computing Fig. 17.7 with datagrams (part 1 of 4). #BREAK# Chapter 17 Networking 1009 42 // get message from textfield and convert to 43 // array of bytes 44 String message = event.getActionCommand(); 45 byte data[] = message.getBytes(); 46 47 // create sendPacket 48 sendPacket = new DatagramPacket( 49 data, data.length, 50 InetAddress.getLocalHost(), 5000 ); 51 52 // send packet 53 socket.send( sendPacket ); 54 55 displayArea.append( “Packet sentn” ); 56 displayArea.setCaretPosition( 57 displayArea.getText().length() ); 58 } 59 60 // process problems creating or sending packet 61 catch ( IOException ioException ) { 62 displayArea.append( 63 ioException.toString() + “n” ); 64 ioException.printStackTrace(); 65 } 66 67 } // end actionPerformed 68 69 } // end anonymous inner class 70 71 ); // end call to addActionListener 72 73 container.add( enterField, BorderLayout.NORTH ); 74 75 displayArea = new JTextArea(); 76 container.add( new JScrollPane( displayArea ), 77 BorderLayout.CENTER ); 78 79 setSize( 400, 300 ); 80 setVisible( true ); 81 82 // create DatagramSocket for sending and receiving packets 83 try { 84 socket = new DatagramSocket(); 85 } 86 87 // catch problems creating DatagramSocket 88 catch( SocketException socketException ) { 89 socketException.printStackTrace(); 90 System.exit( 1 ); 91 } 92 93 } // end Client constructor Fig. 17.7Demonstrating the client side of connectionless client/server computing Fig. 17.7 with datagrams (part 2 of 4). #BREAK# 1010 Networking Chapter 17 94 95 // wait for packets to arrive from Server, 96 // then display packet contents 97 public void waitForPackets() 98 { 99 // loop forever 100 while ( true ) { 101 102 // receive packet and display contents 103 try { 104 105 // set up packet 106 byte data[] = new byte[ 100 ]; 107 receivePacket = 108 new DatagramPacket( data, data.length ); 109 110 // wait for packet 111 socket.receive( receivePacket ); 112 113 // display packet contents 114 displayPacket(); 115 } 116 117 // process problems receiving or displaying packet 118 catch( IOException exception ) { 119 displayArea.append( exception.toString() + “n” ); 120 exception.printStackTrace(); 121 } 122 123 } // end while 124 125 } // end method waitForPackets 126 127 // display contents of receivePacket 128 private void displayPacket() 129 { 130 displayArea.append( “nPacket received:” + 131 “nFrom host: ” + receivePacket.getAddress() + 132 “nHost port: ” + receivePacket.getPort() + 133 “nLength: ” + receivePacket.getLength() + 134 “nContaining:nt” + 135 new String( receivePacket.getData(), 0, 136 receivePacket.getLength() ) ); 137 138 displayArea.setCaretPosition( 139 displayArea.getText().length() ); 140 } 141 142 // execute application 143 public static void main( String args[] ) 144 { 145 Client application = new Client(); Fig. 17.7Demonstrating the client side of connectionless client/server computing Fig. 17.7 with datagrams (part 3 of 4). #BREAK# Chapter 17 Networking 1011 146 147 application.setDefaultCloseOperation( 148 JFrame.EXIT_ON_CLOSE ); 149 150 application.waitForPackets(); 151 } 152 153 } // end class Client The Client window after sending a packet to the server and receiving the packet back from the server Fig. 17.7Demonstrating the client side of connectionless client/server computing Fig. 17.7 with datagrams (part 4 of 4). Notice that the DatagramSocketconstructor call (line 84) in this application does not specify any arguments. This constructor allows the computer to select the next available port number for the DatagramSocket. The client does not need a specific port number, because the server receives the client s port number as part of each DatagramPacket sent by the client. Thus, the server can send packets back to the same computer and port number from which the server receives a packet of information. Clientmethod waitForPackets(lines 97 125) uses an infinite loop to wait for packets from the server. Line 111 blocks until a packet arrives. Note that this does not prevent the user from sending a packet, because the GUI events are handled in the event dispatch thread. It only prevents the whileloop from continuing until a packet arrives at the Client. When a packet arrives, line 111 stores the packet in receivePacket, and line 114 calls method displayPacket (128 140) to display the packet s contents in the JTextArea. 17.8 Client/Server Tic-Tac-Toe Using a Multithreaded Server In this section, we present the popular game Tic-Tac-Toe implemented by using client/server techniques with stream sockets. The program consists of a TicTacToeServerapplication (Fig. 17.8) that allows two TicTacToeClientapplets (Fig. 17.9) to connect to the server and play Tic-Tac-Toe (outputs shown in Fig. 17.10). As the server receives each client connection, it creates an instance of inner class Player(lines 158 279 of Fig. 17.8) to process the client in a separate thread. These threads enable the clients to play the game independently. The server assigns Xs to the first client to connect (X makes the first move) and assigns #BREAK# 1012 Networking Chapter 17 Os to the second client to connect. The server maintains the information about the board so it can determine whether a player s move is a valid or invalid move. Each TicTacToe- Clientapplet (Fig. 17.9) maintains its own GUI version of the Tic-Tac-Toe board on which it displays the state of the game. The clients can place a mark only in an empty square on the board. Class Square (lines 212 270 of Fig. 17.9) implements each of the nine squares on the board. Class TicTacToeServerand class Playerare implemented in file TicTac- ToeServer.java(Fig. 17.8). Class TicTacToeClientand class Squareare implemented in file TicTacToeClient.java(Fig. 17.9). 1 // Fig. 17.8: TicTacToeServer.java 2 // This class maintains a game of Tic-Tac-Toe for two 3 // client applets. 4 5 // Java core packages 6 import java.awt.*; 7 import java.awt.event.*; 8 import java.net.*; 9 import java.io.*; 10 11 // Java extension packages 12 import javax.swing.*; 13 14 public class TicTacToeServer extends JFrame { 15 private byte board[]; 16 private JTextArea outputArea; 17 private Player players[]; 18 private ServerSocket server; 19 private int currentPlayer; 20 21 // set up tic-tac-toe server and GUI that displays messages 22 public TicTacToeServer() 23 { 24 super( “Tic-Tac-Toe Server” ); 25 26 board = new byte[ 9 ]; 27 players = new Player[ 2 ]; 28 currentPlayer = 0; 29 30 // set up ServerSocket 31 try { 32 server = new ServerSocket( 5000, 2 ); 33 } 34 35 // process problems creating ServerSocket 36 catch( IOException ioException ) { 37 ioException.printStackTrace(); 38 System.exit( 1 ); 39 } 40 41 // set up JTextArea to display messages during execution 42 outputArea = new JTextArea(); Fig. 17.8Server side of client/server Tic-Tac-Toe program (part 1 of 6). Fig. 17.8 #BREAK# Chapter 17 Networking 1013 43 getContentPane().add( outputArea, BorderLayout.CENTER ); 44 outputArea.setText( “Server awaiting connectionsn” ); 45 46 setSize( 300, 300 ); 47 setVisible( true ); 48 } 49 50 // wait for two connections so game can be played 51 public void execute() 52 { 53 // wait for each client to connect 54 for ( int i = 0; i < players.length; i++ ) { 55 56 // wait for connection, create Player, start thread 57 try { 58 players[ i ] = new Player( server.accept(), i ); 59 players[ i ].start(); 60 } 61 62 // process problems receiving connection from client 63 catch( IOException ioException ) { 64 ioException.printStackTrace(); 65 System.exit( 1 ); 66 } 67 } 68 69 // Player X is suspended until Player O connects. 70 // Resume player X now. 71 synchronized ( players[ 0 ] ) { 72 players[ 0 ].setSuspended( false ); 73 players[ 0 ].notify(); 74 } 75 76 } // end method execute 77 78 // display a message in outputArea 79 public void display( String message ) 80 { 81 outputArea.append( message + "n" ); 82 } 83 84 // Determine if a move is valid. 85 // This method is synchronized because only one move can be 86 // made at a time. 87 public synchronized boolean validMove( 88 int location, int player ) 89 { 90 boolean moveDone = false; 91 92 // while not current player, must wait for turn 93 while ( player != currentPlayer ) { 94 Fig. 17.8Server side of client/server Tic-Tac-Toe program (part 2 of 6). Fig. 17.8 #BREAK# 1014 Networking Chapter 17 95 // wait for turn 96 try { 97 wait(); 98 } 99 100 // catch wait interruptions 101 catch( InterruptedException interruptedException ) { 102 interruptedException.printStackTrace(); 103 } 104 } 105 106 // if location not occupied, make move 107 if ( !isOccupied( location ) ) { 108 109 // set move in board array 110 board[ location ] = 111 ( byte ) ( currentPlayer == 0 ? 'X' : 'O' ); 112 113 // change current player 114 currentPlayer = ( currentPlayer + 1 ) % 2; 115 116 // let new current player know that move occurred 117 players[ currentPlayer ].otherPlayerMoved( location ); 118 119 // tell waiting player to continue 120 notify(); 121 122 // tell player that made move that the move was valid 123 return true; 124 } 125 126 // tell player that made move that the move was not valid 127 else 128 return false; 129 } 130 131 // determine whether location is occupied 132 public boolean isOccupied( int location ) 133 { 134 if ( board[ location ] == 'X' || board [ location ] == 'O' ) 135 return true; 136 else 137 return false; 138 } 139 140 // place code in this method to determine whether game over 141 public boolean gameOver() 142 { 143 return false; 144 } 145 Fig. 17.8Server side of client/server Tic-Tac-Toe program (part 3 of 6). Fig. 17.8 #BREAK# Chapter 17 Networking 1015 146 // execute application 147 public static void main( String args[] ) 148 { 149 TicTacToeServer application = new TicTacToeServer(); 150 151 application.setDefaultCloseOperation( 152 JFrame.EXIT_ON_CLOSE ); 153 154 application.execute(); 155 } 156 157 // private inner class Player manages each Player as a thread 158 private class Player extends Thread { 159 private Socket connection; 160 private DataInputStream input; 161 private DataOutputStream output; 162 private int playerNumber; 163 private char mark; 164 protected boolean suspended = true; 165 166 // set up Player thread 167 public Player( Socket socket, int number ) 168 { 169 playerNumber = number; 170 171 // specify player's mark 172 mark = ( playerNumber == 0 ? 'X' : 'O' ); 173 174 connection = socket; 175 176 // obtain streams from Socket 177 try { 178 input = new DataInputStream( 179 connection.getInputStream() ); 180 output = new DataOutputStream( 181 connection.getOutputStream() ); 182 } 183 184 // process problems getting streams 185 catch( IOException ioException ) { 186 ioException.printStackTrace(); 187 System.exit( 1 ); 188 } 189 } 190 191 // send message that other player moved; message contains 192 // a String followed by an int 193 public void otherPlayerMoved( int location ) 194 { 195 // send message indicating move 196 try { 197 output.writeUTF( "Opponent moved" ); Fig. 17.8Server side of client/server Tic-Tac-Toe program (part 4 of 6). Fig. 17.8 #BREAK# 1016 Networking Chapter 17 198 output.writeInt( location ); 199 } 200 201 // process problems sending message 202 catch ( IOException ioException ) { 203 ioException.printStackTrace(); 204 } 205 } 206 207 // control thread's execution 208 public void run() 209 { 210 // send client message indicating its mark (X or O), 211 // process messages from client 212 try { 213 display( "Player " + ( playerNumber == 0 ? 214 'X' : 'O' ) + " connected" ); 215 216 // send player's mark 217 output.writeChar( mark ); 218 219 // send message indicating connection 220 output.writeUTF( "Player " + 221 ( playerNumber == 0 ? "X connectedn" : 222 "O connected, please waitn" ) ); 223 224 // if player X, wait for another player to arrive 225 if ( mark == 'X' ) { 226 output.writeUTF( "Waiting for another player" ); 227 228 // wait for player O 229 try { 230 synchronized( this ) { 231 while ( suspended ) 232 wait(); 233 } 234 } 235 236 // process interruptions while waiting 237 catch ( InterruptedException exception ) { 238 exception.printStackTrace(); 239 } 240 24 // send message that other player connected and 24 // player X can make a move 24 output.writeUTF( 24 "Other player connected. Your move." ); 24 } 24 24 // while game not over 24 while ( ! gameOver() ) { 24 Fig. 17.8Server side of client/server Tic-Tac-Toe program (part 5 of 6). Fig. 17.8 #BREAK# Chapter 17 Networking 1017 250 // get move location from client 251 int location = input.readInt(); 252 253 // check for valid move 254 if ( validMove( location, playerNumber ) ) { 255 display( "loc: " + location ); 256 output.writeUTF( "Valid move." ); 257 } 258 else 259 output.writeUTF( "Invalid move, try again" ); 260 } 261 262 // close connection to client 263 connection.close(); 264 } 265 266 // process problems communicating with client 267 catch( IOException ioException ) { 268 ioException.printStackTrace(); 269 System.exit( 1 ); 270 } 271 } 272 273 // set whether or not thread is suspended 274 public void setSuspended( boolean status ) 275 { 276 suspended = status; 277 } 278 279 } // end class Player 280 281 } // end class TicTacToeServer Fig. 17.8Server side of client/server Tic-Tac-Toe program (part 6 of 6). Fig. 17.8 We begin with a discussion of the server side of the Tic-Tac-Toe game. When the TicTacToeServer application executes, the main method (lines 147 155) creates a TicTacToeServer object called application. The constructor (lines 22 48) attempts to set up a ServerSocket. If successful, the program displays the server #BREAK# 1018 Networking Chapter 17 window, and main invokes the TicTacToeServer method execute (lines 51 76). Method execute loops twice, blocking at line 58 each time while waiting for a client connection. When a client connects, line 58 creates a new Player object to manage the connection as a separate thread, and line 59 calls that object s start method to begin executing the thread. When the TicTacToeServer creates a Player, the Player constructor (lines 167 189) receives the Socket object representing the connection to the client and gets the associated input and output streams. The Player s run method (lines 208 271) controls the information that is sent to the client and the information that is received from the client. First, it tells the client that the client s connection has been made (lines 213 214), then it passes to the client the character that the client will place on the board when a move is made (line 217). Lines 230 233 suspend each Player thread as it starts executing, because neither player is allowed to make a move when it first connects. Player X can move only when player O connects, and player O can make a move only after player X. At this point, the game can be played, and the runmethod begins executing its while structure (lines 248 260). Each iteration of this while structure reads an integer (line 253) representing the location where the client wants to place a mark, and line 254 invokes the TicTacToeServer method validMove (lines 87 129) to check the move. Lines 254 259 send a message to the client indicating whether the move was valid. The program maintains board locations as numbers from 0 to 8 (0 through 2 for the first row, 3 through 5 for the second row and 6 through 8 for the third row). Method validMove (lines 87 129 in class TicTacToeServer) is a synchronized method that allows only one player at a time to move. Synchronizing validMove prevents both players from modifying the state information of the game simultaneously. If the Player attempting to validate a move is not the current player (i.e., the one allowed to make a move), the Player is placed in a wait state until it is that Player s turn to move. If the position for the move being validated is already occupied on the board, validMove returns false. Otherwise, the server places a mark for the player in its local representation of the board (lines 110 111), notifies the other Player object (line 117) that a move has been made (so the client can be sent a message), invokes method notify (line 120) so the waiting Player (if there is one) can validate a move and returns true (line 123) to indicate that the move is valid. When a TicTacToeClient (Fig. 17.9) applet begins execution, it creates a JTextArea in which messages from the server and a representation of the board using nine Square objects are displayed. The applet s start method (lines 80 104) opens a connection to the server and gets the associated input and output streams from the Socket object. Class TicTacToeClient implements interface Runnable so that a separate thread can read messages from the server. This approach enables the user to interact with the board (in the event-dispatch thread) while waiting for messages from the server. After establishing the connection to the server, line 102 creates Thread object outputThread and initializes it with the Runnable applet, then line 103 calls the thread s startmethod. The applet s runmethod (lines 108 137) controls the separate thread of execution. The method first reads the mark character (X or O) from the server (line 112), then loops continually (lines 123 135) and reads messages from the server (line 127). Each message is passed to the applet s processMessage method (lines 140 211) for processing. #BREAK# Chapter 17 Networking 1019 // Fig. 17.9: TicTacToeClient.java // Client for the TicTacToe program // Java core packages import java.awt.*; import java.awt.event.*; import java.net.*; import java.io.*; // Java extension packages import javax.swing.*; // Client class to let a user play Tic-Tac-Toe with // another user across a network. public class TicTacToeClient extends JApplet implements Runnable { private JTextField idField; private JTextArea displayArea; private JPanel boardPanel, panel2; private Square board[][], currentSquare; private Socket connection; private DataInputStream input; private DataOutputStream output; private Thread outputThread; private char myMark; private boolean myTurn; // Set up user-interface and board public void init() { Container container = getContentPane(); // set up JTextArea to display messages to user displayArea = new JTextArea( 4, 30 ); displayArea.setEditable( false ); container.add( new JScrollPane( displayArea ), BorderLayout.SOUTH ); // set up panel for squares in board boardPanel = new JPanel(); boardPanel.setLayout( new GridLayout( 3, 3, 0, 0 ) ); // create board board = new Square[ 3 ][ 3 ]; // When creating a Square, the location argument to the // constructor is a value from 0 to 8 indicating the // position of the Square on the board. Values 0, 1, // and 2 are the first row, values 3, 4, and 5 are the // second row. Values 6, 7, and 8 are the third row. for ( int row = 0; row < board.length; row++ ) { Fig. 17.9Client side of client/server Tic-Tac-Toe program (part 1 of 6). Fig. 17.9 #BREAK# 1020 Networking Chapter 17 54 for ( int column = 0; 55 column < board[ row ].length; column++ ) { 56 57 // create Square 58 board[ row ][ column ] = 59 new Square( ' ', row * 3 + column ); 60 61 boardPanel.add( board[ row ][ column ] ); 62 } 63 64 } 65 66 // textfield to display player's mark 67 idField = new JTextField(); 68 idField.setEditable( false ); 69 container.add( idField, BorderLayout.NORTH ); 70 71 // set up panel to contain boardPanel (for layout purposes) 72 panel2 = new JPanel(); 73 panel2.add( boardPanel, BorderLayout.CENTER ); 74 container.add( panel2, BorderLayout.CENTER ); 75 } 76 77 // Make connection to server and get associated streams. 78 // Start separate thread to allow this applet to 79 // continually update its output in text area display. 80 public void start() 81 { 82 // connect to server, get streams and start outputThread 83 try { 84 85 // make connection 86 connection = new Socket( 87 InetAddress.getByName( "127.0.0.1" ), 5000 ); 88 89 // get streams 90 input = new DataInputStream( 91 connection.getInputStream() ); 92 output = new DataOutputStream( 93 connection.getOutputStream() ); 94 } 95 96 // catch problems setting up connection and streams 97 catch ( IOException ioException ) { 98 ioException.printStackTrace(); 99 } 100 101 // create and start output thread 102 outputThread = new Thread( this ); 103 outputThread.start(); 104 } 105 Fig. 17.9Client side of client/server Tic-Tac-Toe program (part 2 of 6). Fig. 17.9 #BREAK# Chapter 17 Networking 1021 106 // control thread that allows continuous update of the 107 // text area displayArea 108 public void run() 109 { 110 // get player's mark (X or O) 111 try { 112 myMark = input.readChar(); 113 idField.setText( "You are player "" + myMark + """ ); 114 myTurn = ( myMark == 'X' ? true : false ); 115 } 116 117 // process problems communicating with server 118 catch ( IOException ioException ) { 119 ioException.printStackTrace(); 120 } 121 122 // receive messages sent to client and output them 123 while ( true ) { 124 125 // read message from server and process message 126 try { 127 String message = input.readUTF(); 128 processMessage( message ); 129 } 130 131 // process problems communicating with server 132 catch ( IOException ioException ) { 133 ioException.printStackTrace(); 134 } 135 } 136 137 } // end method run 138 139 // process messages received by client 140 public void processMessage( String message ) 141 { 142 // valid move occurred 143 if ( message.equals( "Valid move." ) ) { 144 displayArea.append( "Valid move, please wait.n" ); 145 146 // set mark in square from event-dispatch thread 147 SwingUtilities.invokeLater( 148 149 new Runnable() { 150 151 public void run() 152 { 153 currentSquare.setMark( myMark ); 154 } 155 156 } 157 Fig. 17.9Client side of client/server Tic-Tac-Toe program (part 3 of 6). Fig. 17.9 #BREAK# 1022 Networking Chapter 17 158 ); // end call to invokeLater 159 } 160 161 // invalid move occurred 162 else if ( message.equals( "Invalid move, try again" ) ) { 163 displayArea.append( message + "n" ); 164 myTurn = true; 165 } 166 167 // opponent moved 168 else if ( message.equals( "Opponent moved" ) ) { 169 170 // get move location and update board 171 try { 172 final int location = input.readInt(); 173 174 // set mark in square from event-dispatch thread 175 SwingUtilities.invokeLater( 176 177 new Runnable() { 178 179 public void run() 180 { 181 int row = location / 3; 182 int column = location % 3; 183 184 board[ row ][ column ].setMark( 185 ( myMark == 'X' ? 'O' : 'X' ) ); 186 displayArea.append( 187 "Opponent moved. Your turn.n" ); 188 } 189 190 } 191 192 ); // end call to invokeLater 193 194 myTurn = true; 195 } 196 197 // process problems communicating with server 198 catch ( IOException ioException ) { 199 ioException.printStackTrace(); 200 } 20 20 } 20 20 // simply display message 20 else 20 displayArea.append( message + "n" ); 20 20 displayArea.setCaretPosition( 20 displayArea.getText().length() ); Fig. 17.9Client side of client/server Tic-Tac-Toe program (part 4 of 6). Fig. 17.9 #BREAK# Chapter 17 Networking 1023 210 211 } // end method processMessage 212 213 // send message to server indicating clicked square 214 public void sendClickedSquare( int location ) 215 { 216 if ( myTurn ) { 217 218 // send location to server 219 try { 220 output.writeInt( location ); 221 myTurn = false; 222 } 223 224 // process problems communicating with server 225 catch ( IOException ioException ) { 226 ioException.printStackTrace(); 227 } 228 } 229 } 230 231 // set current Square 232 public void setCurrentSquare( Square square ) 233 { 234 currentSquare = square; 235 } 236 237 // private class for the sqaures on the board 238 private class Square extends JPanel { 239 private char mark; 240 private int location; 241 242 public Square( char squareMark, int squareLocation ) 243 { 244 mark = squareMark; 245 location = squareLocation; 246 247 addMouseListener( 248 249 new MouseAdapter() { 250 25 public void mouseReleased( MouseEvent e ) 25 { 25 setCurrentSquare( Square.this ); 25 sendClickedSquare( getSquareLocation() ); 25 } 25 25 } // end anonymous inner class 25 25 ); // end call to addMouseListener 260 261 } // end Square constructor Fig. 17.9Client side of client/server Tic-Tac-Toe program (part 5 of 6). Fig. 17.9 #BREAK# 1024 Networking Chapter 17 262 263 // return preferred size of Square 264 public Dimension getPreferredSize() 265 { 266 return new Dimension( 30, 30 ); 267 } 268 269 // return minimum size of Square 270 public Dimension getMinimumSize() 271 { 272 return getPreferredSize(); 273 } 274 275 // set mark for Square 276 public void setMark( char newMark ) 277 { 278 mark = newMark; 279 repaint(); 280 } 281 282 // return Square location 283 public int getSquareLocation() 284 { 285 return location; 286 } 287 288 // draw Square 289 public void paintComponent( Graphics g ) 290 { 291 super.paintComponent( g ); 292 293 g.drawRect( 0, 0, 29, 29 ); 294 g.drawString( String.valueOf( mark ), 11, 20 ); 295 } 296 297 } // end class Square 298 299 } // end class TicTacToeClient Fig. 17.9Client side of client/server Tic-Tac-Toe program (part 6 of 6). Fig. 17.9 If the message received is Validmove., lines 143 159 display the message Valid move,pleasewait. and call class Square s setMark method to set the client s mark in the current square (the one in which the user clicked) using SwingUtilities method invokeLaterto ensure that the GUI updates occur in the event dispatch thread. If the message received is Invalidmove, try again., lines 162 165 display the message so the user can click a different square. If the message received is Opponent moved., lines 168 195 read an integer from the server indicating where the opponent moved and place a mark in that square of the board (again using SwingUtilities method invokeLaterto ensure that the GUI updates occur in the event dispatch thread). If any other message is received, line 206 simply displays the message. #BREAK# Chapter 17 Networking 1025 Fig. 17.10 Fig. 17.10 Sample outputs from the client/server Tic-Tac-Toe program (part 1 of 2). #BREAK# 1026 Networking Chapter 17 Fig. 17.10 Fig. 17.10 Sample outputs from the client/server Tic-Tac-Toe program (part 2 of 2). 17.9 Security and the Network As much as we look forward to writing a great variety of powerful network-based applications, our efforts may be crimped by limitations imposed on Java because of security concerns.c Many Web browsers, such as Netscape Communicator and Microsoft Internet Explorer, by default prohibit Java applets from doing file processing on the machines on which they execute. Think about it. A Java applet is designed to be sent to your browser via an HTML document that could be downloaded from any Web server in the world. Often you will know very little about the sources of Java applets that will execute on your system. To allow these applets free rein with your files could be disastrous. A more subtle situation occurs with limiting the machines to which executing applets can connect. To build truly collaborative applications, we would ideally like to have our applets communicate with machines almost anywhere. The Java security manager in a Web browser often restricts an applet so that it can communicate only with the machine from which it was originally downloaded. These restrictions might seem too harsh. However, the Java Security API now provides capabilities for signed applets that will enable browsers to determine whether an applet is downloaded from a trusted source. In cases where an applet is trusted, the applet can be given additional access to the computer on which the applet is executing. The features of the Java Security API and additional networking capabilities are discussed in our text Advanced Java 2 Platform How to Program. 17.10 DeitelMessenger Chat Server and Client Chat rooms have become quite common on the Internet. Chat rooms provide a central location where users can chat with each other through short text messages. Each participant in a chat room can see all messages that other users post, and each user can post messages in the chat room. This section presents our capstone networking case study that integrates many of the Java networking, multithreading and Swing GUI features we have learned thus far to build an online chat system. We also introduce multicast, which enables an application to send DatagramPackets to groups of clients. After reading this section, you will be able to build significant networking applications. #BREAK# Chapter 17 Networking 1027 17.10.1 DeitelMessengerServer and Supporting Classes The DeitelMessengerServer(Fig. 17.11) is the heart of the online chat system. Chat clients can participate in a chat by connecting to the DeitelMessengerServer. Method startServer(lines 19 54) launches DeitelMessengerServer. Lines 25 26 create a ServerSocketto accept incoming network connections. Recall that the ServerSocketconstructor takes as its first argument the port on which the server should listen for incoming connections. Interface SocketMessengerConstants (Fig. 17.12) defines the port value as the constant SERVER_PORTto ensure that the server and the clients uses the correct port number. Class DeitelMessengerServer implements interface SocketMessengerConstantsto facilitate referencing the constants defined in that interface. 1 // DeitelMessengerServer.java 2 // DeitelMessengerServer is a multi-threaded, socket-and 3 // packet-based chat server. 4 package com.deitel.messenger.sockets.server; 5 6 // Java core packages 7 import java.util.*; 8 import java.net.*; 9 import java.io.*; 10 11 // Deitel packages 12 import com.deitel.messenger.*; 13 import com.deitel.messenger.sockets.*; 14 15 public class DeitelMessengerServer implements MessageListener, 16 SocketMessengerConstants { 17 18 // start chat server 19 public void startServer() 20 { 21 // create server and manage new clients 22 try { 23 24 // create ServerSocket for incoming connections 25 ServerSocket serverSocket = 26 new ServerSocket( SERVER_PORT, 100 ); 27 28 System.out.println( "Server listening on port " + 29 SERVER_PORT + " ..." ); 30 31 // listen for clients constantly 32 while ( true ) { 33 34 // accept new client connection 35 Socket clientSocket = serverSocket.accept(); 36 37 // create new ReceivingThread for receiving 38 // messages from client Fig. 17.11 DeitelMessengerServerapplication for managing a chat room (part 1 of 2). #BREAK# 1028 Networking Chapter 17 39 new ReceivingThread( this, clientSocket ).start(); 40 41 // print connection information 42 System.out.println( "Connection received from: " + 43 clientSocket.getInetAddress() ); 44 45 } // end while 46 47 } // end try 48 49 // handle exception creating server and connecting clients 50 catch ( IOException ioException ) { 51 ioException.printStackTrace(); 52 } 53 54 } // end method startServer 55 56 // when new message is received, broadcast message to clients 57 public void messageReceived( String from, String message ) 58 { 59 // create String containing entire message 60 String completeMessage = from + MESSAGE_SEPARATOR + message; 61 62 // create and start MulticastSendingThread to broadcast 63 // new messages to all clients 64 new MulticastSendingThread( 65 completeMessage.getBytes() ).start(); 66 } 67 68 // start the server 69 public static void main ( String args[] ) 70 { 71 new DeitelMessengerServer().startServer(); 72 } 73 } Server listening on port 5000 ... Connection received from: SEANSANTRY/XXX.XXX.XXX.XXX Connection received from: PJD/XXX.XXX.XXX.XXX Fig. 17.11 DeitelMessengerServerapplication for managing a chat room (part 2 of 2). Lines 32 45 listen continuously for new client connections. Line 35 invokes method acceptof class ServerSocketto wait for and accept a new client connection. Line 39 creates and starts a new ReceivingThread for the client. Class ReceivingThread (Fig. 17.14) is a Threadsubclass that listens for new incoming messages from a particular client. The first argument to the ReceivingThread constructor is a Message- Listener (Fig. 17.13), to which messages from the client should be delivered. Class DeitelMessengerServer implements interface MessageListener (line 15) and therefore can pass the thisreference to the ReceivingThreadconstructor. #BREAK# Chapter 17 Networking 1029 Method messageReceived (lines 57 66) is required by interface Message- Listener. When each ReceivingThread receives a new message from a client, the ReceivingThread passes the message to a MessageListener through method messageReceived. Line 60 concatenates the from String with the separator >>> and the message body. Lines 64 65 create and start a new MulticastSending- Thread to deliver completeMessage to all listening clients. Class Multicast- SendingThread (Fig. 17.15) uses multicast as an efficient mechanism for sending one message to multiple clients. We discuss the details of multicasting shortly. Method main (lines 69 72) creates a new DeitelMessengerServerinstance and starts the server. Interface SocketMessengerConstants (Fig. 17.12) declares constants for use in the various classes that make up the Deitel messenger system. Classes can access these static constants either by referencing the constants through interface SocketMessengerConstants (e.g., SocketMessengerConstants.SERVER_PORT) or by implementing the interface and referencing the constants directly. Line 9 defines the String constant MULTICAST_ADDRESS, which contains the address to which a MulticastSendingThread (Fig. 17.15) should send messages. This address is one of the addresses reserved for multicast, which we will describe soon. Line 12 defines the integer constant MULTICAST_LISTENING_PORT the port on which clients should listen for new messages. Line 15 defines the integer constant MULTICAST_SENDING_PORT the port to which a MulticastSendingThread should post new messages at the MULTICAST_ADDRESS. Line 18 defines the integer constant SERVER_PORT the port on which DeitelMessengerServer listens for incoming client connections. Line 21 defines Stringconstant DISCONNECT_STRING, which is the String that a client sends to DeitelMessengerServer when the user wishes to leave the chat room. Line 24 defines String constant MESSAGE_SEPARATOR, which separates the user name from the message body. Line 27 specifies the maximum message size in bytes. 1 // SocketMessengerConstants.java 2 // SocketMessengerConstants defines constants for the port numbers 3 // and multicast address in DeitelMessenger 4 package com.deitel.messenger.sockets; 5 6 public interface SocketMessengerConstants { 7 8 // address for multicast datagrams 9 public static final String MULTICAST_ADDRESS = “230.0.0.1″; 10 11 // port for listening for multicast datagrams 12 public static final int MULTICAST_LISTENING_PORT = 5555; 13 14 // port for sending multicast datagrams 15 public static final int MULTICAST_SENDING_PORT = 5554; 16 17 // port for Socket connections to DeitelMessengerServer 18 public static final int SERVER_PORT = 5000; Fig. 17.12 SocketMessengerConstantsdeclares constants for use throughout the DeitelMessengerServerand DeitelMessenger applications (part 1 of 2). #BREAK# 1030 Networking Chapter 17 19 20 // String that indicates disconnect 21 public static final String DISCONNECT_STRING = “DISCONNECT”; 22 23 // String that separates the user name from the message body 24 public static final String MESSAGE_SEPARATOR = “>>>”; 25 26 // message size (in bytes) 27 public static final int MESSAGE_SIZE = 512; 28 } Fig. 17.12 SocketMessengerConstantsdeclares constants for use throughout the DeitelMessengerServerand DeitelMessenger applications (part 2 of 2). Many different classes in the Deitel messenger system receive messages. For example, DeitelMessengerServer receives messages from clients and delivers those messages to all chat room participants. As we will see, the user interface for each client also receives messages and displays those messages to the users. Each of the classes that receives messages implements interface MessageListener(Fig. 17.13). The interface declares method messageReceived, which allows an implementing class to receive chat messages. Method messageReceived takes two String arguments representing the name of the user who sent the message and the message body, respectively. DeitelMessengerServer uses instances of class ReceivingThread (Fig. 17.14) to listen for new messages from each client. Class ReceivingThread extends class Thread. This enables DeitelMessengerServer to create an object of class ReceivingThread for each client, to handle messages from multiple clients at once. When DeitelMessengerServer receives a new client connection, DeitelMessengerServer creates a new ReceivingThread for the client, then continues listening for new client connections. The ReceivingThreadlistens for new messages from a single client and passes those messages back to the DeitelMessengerServer through method messageReceived. 1 // MessageListener.java 2 // MessageListener is an interface for classes that wish to 3 // receive new chat messages. 4 package com.deitel.messenger; 5 6 public interface MessageListener { 7 8 // receive new chat message 9 public void messageReceived( String from, String message ); 10 } Fig. 17.13 MessageListenerinterface that defines method messageReceivedfor receiving new chat messages. #BREAK# Chapter 17 Networking 1031 // ReceivingThread.java // ReceivingThread is a Thread that listens for messages // from a particular client and delivers messages to a // MessageListener. package com.deitel.messenger.sockets.server; // Java core packages import java.io.*; import java.net.*; import java.util.StringTokenizer; // Deitel packages import com.deitel.messenger.*; import com.deitel.messenger.sockets.*; public class ReceivingThread extends Thread implements SocketMessengerConstants { private BufferedReader input; private MessageListener messageListener; private boolean keepListening = true; // ReceivingThread constructor public ReceivingThread( MessageListener listener, Socket clientSocket ) { // invoke superclass constructor to name Thread super( “ReceivingThread: ” + clientSocket ); // set listener to which new messages should be sent messageListener = listener; // set timeout for reading from clientSocket and create // BufferedReader for reading incoming messages try { clientSocket.setSoTimeout( 5000 ); input = new BufferedReader( new InputStreamReader( clientSocket.getInputStream() ) ); } // handle exception creating BufferedReader catch ( IOException ioException ) { ioException.printStackTrace(); } } // end ReceivingThread constructor // listen for new messages and deliver them to MessageListener public void run() { String message; Fig. 17.14 ReceivingThreadfor listening for new messages from DeitelMessengerServerclients in separate Threads (part 1 of 3). #BREAK# 1032 Networking Chapter 17 53 54 // listen for messages until stopped 55 while ( keepListening ) { 56 57 // read message from BufferedReader 58 try { 59 message = input.readLine(); 60 } 61 62 // handle exception if read times out 63 catch ( InterruptedIOException interruptedIOException ) { 64 65 // continue to next iteration to keep listening 66 continue; 67 } 68 69 // handle exception reading message 70 catch ( IOException ioException ) { 71 ioException.printStackTrace(); 72 break; 73 } 74 75 // ensure non-null message 76 if ( message != null ) { 77 78 // tokenize message to retrieve user name 79 // and message body 80 StringTokenizer tokenizer = 81 new StringTokenizer( message, MESSAGE_SEPARATOR ); 82 83 // ignore messages that do not contain a user 84 // name and message body 85 if ( tokenizer.countTokens() == 2 ) { 86 87 // send message to MessageListener 88 messageListener.messageReceived( 89 tokenizer.nextToken(), // user name 90 tokenizer.nextToken() ); // message body 91 } 92 93 else 94 95 // if disconnect message received, stop listening 96 if ( message.equalsIgnoreCase( MESSAGE_SEPARATOR + 97 DISCONNECT_STRING ) ) { 98 99 stopListening(); 100 } 101 102 } // end if 103 104 } // end while Fig. 17.14 ReceivingThreadfor listening for new messages from DeitelMessengerServerclients in separate Threads (part 2 of 3). #BREAK# Chapter 17 Networking 1033 105 106 // close BufferedReader (also closes Socket) 107 try { 108 input.close(); 109 } 110 111 // handle exception closing BufferedReader 112 catch ( IOException ioException ) { 113 ioException.printStackTrace(); 114 } 115 116 } // end method run 117 118 // stop listening for incoming messages 119 public void stopListening() 120 { 121 keepListening = false; 122 } 123 } Fig. 17.14 ReceivingThreadfor listening for new messages from DeitelMessengerServerclients in separate Threads (part 3 of 3). The ReceivingThread constructor (lines 24 47) takes as its first argument a MessageListener. The ReceivingThreadwill deliver new messages to this MessageListenerby invoking method messageReceivedof interface MessageListener. The Socketargument to the ReceivingThreadconstructor is the connection to a particular client. Line 28 invokes the Thread constructor to provide a unique name for each ReceivingThreadinstance. Naming the ReceivingThread this way can be useful when debugging the application. Line 31 sets the MessageListenerto which the ReceivingThreadshould deliver new messages. Line 36 invokes method setSo- Timeoutof class Socketwith an integer argument of 5000milliseconds. Reading data from a Socketis a blocking call the current thread is put in the blocked state (Fig. 15.1) while the thread waits for the read operation to complete. Method setSoTimeoutspecifies that, if no data is received in the given number of milliseconds, the Socket should issue an InterruptedIOException, which the current thread can catch, then continue executing. This technique prevents the current thread from deadlocking if no more data is available from the Socket. Lines 38 39 create a new BufferedReaderfor the clientSocket s InputStream. The ReceivingThread uses this Buffered- Readerto read new messages from the client. Method run (lines 50 116) listens continuously for new messages from the client. Lines 55 104 loop as long as the booleanvariable keepListeningis true. Line 59 invokes BufferedReader method readLineto read a line of data from the client. If more than 5000 milliseconds pass without reading any data, method readLine throws an InterruptedIOException, which indicates that the timeout set on line 36 has expired. Line 66 uses keyword continue to go to the next iteration of the while loop to continue listening for messages. Lines 70 73 catch an IOException, which indicates a more severe problem from method readLine. Line 71 prints a stack trace to aid in debugging the application, and line 72 uses keyword breakto terminate the whileloop. #BREAK# 1034 Networking Chapter 17 When a client sends a message to the server, the client separates the user s name from the message body with the String MESSAGE_SEPARATOR. If there are no exceptions thrown when reading data from the client and the message is not null (line 76), lines 80 81 create a new StringTokenizer. This StringTokenizer separates each message into two tokens delimited by MESSAGE_SEPARATOR. The first token is the sender s user name; the second token is the message. Line 85 checks for the proper number of tokens, and lines 88 90 invoke method messageReceived of interface Message- Listener to deliver the new message to the registered MessageListener. If the StringTokenizer does not produce two tokens, lines 96 97 check the message to see whether it matches the constant DISCONNECT_STRING, which would indicate that the user wishes to leave the chat room. If the Strings match, line 99 invokes Receiving- Thread method stopListeningto terminate the ReceivingThread. Method stopListening (lines 119 122) sets boolean variable keepListening to false. This causes the while loop condition on line 55 to fail and causes the ReceivingThread to close the client Socket (line 108). Then, method run returns, which terminates the ReceivingThread s execution. MulticastSendingThread (Fig. 17.15) delivers DatagramPackets containing chat messages to a group of clients. Multicast is an efficient way to send data to many clients without the overhead of broadcasting that data to every host on the Internet. To understand multicast, let us look at a real-world analogy the relationship between a magazine publisher and that magazine s subscribers. The magazine publisher produces a magazine and provides the magazine to a distributor. Customers interested in that magazine obtain a subscription and begin receiving the magazine in the mail from the distributor. This communication is quite different from a television broadcast. When a television station produces a television program, the station broadcasts that television show throughout a geographical region or perhaps throughout the world by using satellites. Broadcasting a television show for 10,000 viewers is no more expensive to the television station than broadcasting a television show for 100 viewers the radio signal carrying the broadcast reaches a wide area. However, printing and delivering a magazine to 10,000 readers would be much more expensive than printing and delivering the magazine to 100 readers. Most magazine publishers could not stay in business if they had to broadcast their magazines to everyone, so magazine publishers multicast their magazines to a group of subscribers instead. Using multicast, an application can publish DatagramPackets to be delivered to other applications the subscribers. An application multicasts DatagramPackets by sending the DatagramPackets to a multicast address, which is an IP address reserved for multicast in the range from 224.0.0.0to 239.255.255.255. Clients that wish to receive these DatagramPackets can connect to the appropriate multicast address to join the group of subscribers the multicast group. When an application sends a Datagram- Packet to the multicast address, each client in the multicast group receives the DatagramPacket. Multicast DatagramPackets, like unicast DatagramPackets (Fig. 17.7), are not reliable packets are not guaranteed to reach any destination. Also, the order in which the particular clients receive the datagrams is not guaranteed. Class MulticastSendingThread extends class Thread to enable Deitel- MessengerServer to send multicast messages in a separate thread. Each time DeitelMessengerServer needs to multicast a message, the server creates a new MulticastSendingThread with the contents of the message and starts the thread. #BREAK# Chapter 17 Networking 1035 The MulticastSendingThread constructor (lines 20 26) takes as an argument an array of bytes containing the message. 1 // MulticastSendingThread.java 2 // MulticastSendingThread is a Thread that broadcasts a chat 3 // message using a multicast datagram. 4 package com.deitel.messenger.sockets.server; 5 6 // Java core packages 7 import java.io.*; 8 import java.net.*; 9 10 // Deitel packages 11 import com.deitel.messenger.sockets.*; 12 13 public class MulticastSendingThread extends Thread 14 implements SocketMessengerConstants { 15 16 // message data 17 private byte[] messageBytes; 18 19 // MulticastSendingThread constructor 20 public MulticastSendingThread( byte[] bytes ) 21 { 22 // invoke superclass constructor to name Thread 23 super( “MulticastSendingThread” ); 24 25 messageBytes = bytes; 26 } 27 28 // deliver message to MULTICAST_ADDRESS over DatagramSocket 29 public void run() 30 { 31 // deliver message 32 try { 33 34 // create DatagramSocket for sending message 35 DatagramSocket socket = 36 new DatagramSocket( MULTICAST_SENDING_PORT ); 37 38 // use InetAddress reserved for multicast group 39 InetAddress group = InetAddress.getByName( 40 MULTICAST_ADDRESS ); 41 42 // create DatagramPacket containing message 43 DatagramPacket packet = new DatagramPacket( 44 messageBytes, messageBytes.length, group, 45 MULTICAST_LISTENING_PORT ); 46 47 // send packet to multicast group and close socket 48 socket.send( packet ); Fig. 17.15 MulticastSendingThreadfor delivering outgoing messages to a multicast group via DatagramPackets. #BREAK# 1036 Networking Chapter 17 49 socket.close(); 50 } 51 52 // handle exception delivering message 53 catch ( IOException ioException ) { 54 ioException.printStackTrace(); 55 } 56 57 } // end method run 58 } Fig. 17.15 MulticastSendingThreadfor delivering outgoing messages to a multicast group via DatagramPackets. Method run(lines 29 57) delivers the message to the multicast address. Lines 35 36 create a new DatagramSocket. Recall from the packet-networking example that we used DatagramSockets to send unicast DatagramPackets packets sent from one host directly to another host. Delivering DatagramPackets by using multicast is exactly the same, except the address to which the DatagramPackets are sent is a multicast address in the range from 224.0.0.0 to 239.255.255.255. Lines 39 40 create an InetAddressobject for the multicast address, which is defined as a constant in interface SocketMessengerConstants. Lines 43 45 create the DatagramPacket containing the message. The first argument to the DatagramPacketconstructor is the byte array containing the message. The second argument is the length of the byte array. The third argument specifies the InetAddress to which the packet should be sent, and the last argument specifies the port number through which the packet should be delivered to the multicast address. Line 48 invokes method sendof class DatagramSocketto send the DatagramPacket. When the DatagramPacket is delivered to the multicast address, all clients listening to that multicast address on the proper port receive the DatagramPacket. Line 49 closes the DatagramSocket, and the run method returns, terminating the MulticastSendingThread. 17.10.2 DeitelMessenger Client and Supporting Classes The client for the DeitelMessengerServer consists of several pieces. A class that implements interface MessageManager (Fig. 17.16) manages communication with the server. A Threadsubclass listens for messages at DeitelMessengerServer s multicast address. Another Thread subclass sends messages from the client to Deitel- MessengerServer. A JFramesubclass provides a GUI for the client. Interface MessageManager (Fig. 17.16) defines methods for managing communication with DeitelMessengerServer. We define this interface to abstract the base functionality a client needs to interact with a chat server from the underlying communication mechanism needed to communicate with that chat server. This abstraction enables us to provide MessageManager implementations that use other network protocols to implement the communication details. For example, if we want to connect to a different chat server that does not use multicast DatagramPackets, we could implement the MessageManager interface with the appropriate network protocols for this alternate messaging server. We would not need to modify any other code in the client, because the #BREAK# Chapter 17 Networking 1037 other components of the client refer only to interface MessageManager, and not to some particular MessageManager implementation. Likewise, the MessageManagerinterface methods refer to other components of the client only through interface Message- Listener. Therefore, other components of the client can change without requiring changes in the MessageManager or its implementations.Method connect (line 10) connects MessageManager to DeitelMessengerServer and routes incoming messages to the appropriate MessageListener. Method disconnect (line 14) disconnects the MessageManager from the DeitelMessengerServer and stops delivering messages to the given MessageListener. Method sendMessage(line 17) sends a new message to DeitelMessengerServer. Class SocketMessageManager (Fig. 17.17) implements interface Message- Manager (line 16), using Sockets and MulticastSockets to communicate with DeitelMessengerServer and receive incoming messages. Line 20 declares the Socketthat SocketMessageManageruses to connect and send messages to Deitel- MessengerServer. Line 26 declares the PacketReceivingThread(Fig. 17.19) that listens for new incoming messages. The boolean flag connected (line 29) indicates whether the SocketMessageManageris connected to DeitelMessengerServer. 1 // MessageManager.java 2 // MessageManager is an interface for objects capable of managing 3 // communications with a message server. 4 package com.deitel.messenger; 5 6 public interface MessageManager { 7 8 // connect to message server and route incoming messages 9 // to given MessageListener 10 public void connect( MessageListener listener ); 11 12 // disconnect from message server and stop routing 13 // incoming messages to given MessageListener 14 public void disconnect( MessageListener listener ); 15 16 // send message to message server 17 public void sendMessage( String from, String message ); 18 } Fig. 17.16 MessageManagerinterface that defines methods for communicating with a DeitelMessengerServer. 1 // SocketMessageManager.java 2 // SocketMessageManager is a MessageManager implementation for 3 // communicating with a DeitelMessengerServer using Sockets 4 // and MulticastSockets. 5 package com.deitel.messenger.sockets.client; Fig. 17.17 SocketMessageManagerimplementation of interface MessageManagerfor communicating via Sockets and multicast DatagramPackets (part 1 of 4). #BREAK# 1038 Networking Chapter 17 7 // Java core packages 8 import java.util.*; 9 import java.net.*; 10 import java.io.*; 11 12 // Deitel packages 13 import com.deitel.messenger.*; 14 import com.deitel.messenger.sockets.*; 15 16 public class SocketMessageManager implements MessageManager, 17 SocketMessengerConstants { 18 19 // Socket for outgoing messages 20 private Socket clientSocket; 21 22 // DeitelMessengerServer address 23 private String serverAddress; 24 25 // Thread for receiving multicast messages 26 private PacketReceivingThread receivingThread; 27 28 // flag indicating connection status 29 private boolean connected = false; 30 31 // SocketMessageManager constructor 32 public SocketMessageManager( String address ) 33 { 34 serverAddress = address; 35 } 36 37 // connect to server and send messages to given MessageListener 38 public void connect( MessageListener listener ) 39 { 40 // if already connected, return immediately 41 if ( connected ) 42 return; 43 44 // open Socket connection to DeitelMessengerServer 45 try { 46 clientSocket = new Socket( 47 InetAddress.getByName( serverAddress ), SERVER_PORT ); 48 49 // create Thread for receiving incoming messages 50 receivingThread = new PacketReceivingThread( listener ); 51 receivingThread.start(); 52 53 // update connected flag 54 connected = true; 55 56 } // end try 57 Fig. 17.17 SocketMessageManagerimplementation of interface MessageManagerfor communicating via Sockets and multicast DatagramPackets (part 2 of 4). #BREAK# Chapter 17 Networking 1039 58 // handle exception connecting to server 59 catch ( IOException ioException ) { 60 ioException.printStackTrace(); 61 } 62 63 } // end method connect 64 65 // disconnect from server and unregister given MessageListener 66 public void disconnect( MessageListener listener ) 67 { 68 // if not connected, return immediately 69 if ( !connected ) 70 return; 71 72 // stop listening thread and disconnect from server 73 try { 74 75 // notify server that client is disconnecting 76 Thread disconnectThread = new SendingThread( 77 clientSocket, “”, DISCONNECT_STRING ); 78 disconnectThread.start(); 79 80 // wait 10 seconds for disconnect message to be sent 81 disconnectThread.join( 10000 ); 82 83 // stop receivingThread and remove given MessageListener 84 receivingThread.stopListening(); 85 86 // close outgoing Socket 87 clientSocket.close(); 88 89 } // end try 90 91 // handle exception disconnecting from server 92 catch ( IOException ioException ) { 93 ioException.printStackTrace(); 94 } 95 96 // handle exception joining disconnectThread 97 catch ( InterruptedException interruptedException ) { 98 interruptedException.printStackTrace(); 99 } 100 101 // update connected flag 102 connected = false; 103 104 } // end method disconnect 105 106 // send message to server 107 public void sendMessage( String from, String message ) 108 { Fig. 17.17 SocketMessageManagerimplementation of interface MessageManagerfor communicating via Sockets and multicast DatagramPackets (part 3 of 4). #BREAK# 1040 Networking Chapter 17 109 // if not connected, return immediately 110 if ( !connected ) 111 return; 113 // create and start new SendingThread to deliver message 114 new SendingThread( clientSocket, from, message).start(); 115 } 116 } Fig. 17.17 SocketMessageManagerimplementation of interface MessageManagerfor communicating via Sockets and multicast DatagramPackets (part 4 of 4). The SocketMessageManager constructor (lines 32 35) receives the address of the DeitelMessengerServer to which SocketMessageManager should connect. Method connect (lines 38 63) connects SocketMessageManager to DeitelMessengerServer. If SocketMessageManager was connected previously, line 42 returns from method connect. Lines 46 47 create a new Socketto communicate with DeitelMessengerServer. Line 47 creates an InetAddressobject for the server s address and uses the constant SERVER_PORTto specify the port on which the client should connect. Line 50 creates a new PacketReceivingThread, which listens for incoming multicast messages from DeitelMessengerServer. Line 51 starts PacketReceivingThread. Line 54 updates booleanvariable connectedto indicate that SocketMessageManageris connected to the server. Method disconnect (lines 66 104) terminates the SocketMessageManager s connection to DeitelMessengerServer. If SocketMessageManager is not connected, line 70 returns from method disconnect. Lines 76 77 create a new Sending- Thread (Fig. 17.18) to send DISCONNECT_STRING to DeitelMessengerServer. Class SendingThread delivers a message to DeitelMessengerServer over the SocketMessageManager s Socket connection. Line 78 starts the SendingThread to deliver the message. Line 81 invokes SendingThread method join (inherited from Thread) to wait for the disconnect message to be delivered. The integer argument 10000 specifies that the current thread should wait only 10 seconds to jointhe SendingThread before continuing. Once the disconnect message has been delivered, line 84 invokes method stopListeningof class PacketReceivingThreadto stop receiving incoming chat messages. Line 87 closes the Socketconnection to DeitelMessengerServer. Method sendMessage (lines 107 115) sends an outgoing message to Deitel- MessengerServer. If SocketMessageManager is not connected, line 111 returns from method sendMessage. Line 114 creates and starts a new SendingThread instance (Fig. 17.18) to deliver the new message in a separate thread of execution. Class SendingThread (Fig. 17.18) extends class Thread to deliver outgoing messages to the DeitelMessengerServer in a separate thread of execution. Sending- Thread s constructor (lines 21 31) takes as arguments the Socketover which to send the message, the userName from whom the message came and the message body. Line 30 concatenates userName, MESSAGE_SEPARATORand messageto build messageTo- Send. Constant MESSAGE_SEPARATOR enables the message recipient to parse the message into two parts the sending user s name and the message body by using a StringTokenizer. #BREAK# Chapter 17 Networking 1041 // SendingThread.java // SendingThread sends a message to the chat server in a // separate Thread. package com.deitel.messenger.sockets.client; // Java core packages import java.io.*; import java.net.*; // Deitel packages import com.deitel.messenger.sockets.*; public class SendingThread extends Thread implements SocketMessengerConstants { // Socket over which to send message private Socket clientSocket; private String messageToSend; // SendingThread constructor public SendingThread( Socket socket, String userName, String message ) { // invoke superclass constructor to name Thread super( “SendingThread: ” + socket ); clientSocket = socket; // build the message to be sent messageToSend = userName + MESSAGE_SEPARATOR + message; } // send message and exit Thread public void run() { // send message and flush PrintWriter try { PrintWriter writer = new PrintWriter( clientSocket.getOutputStream() ); writer.println( messageToSend ); writer.flush(); } // handle exception sending message catch ( IOException ioException ) { ioException.printStackTrace(); } } // end method run } Fig. 17.18 SendingThreadfor delivering outgoing messages to DeitelMessengerServer. #BREAK# 1042 Networking Chapter 17 Method run (lines 34 49) delivers the complete message to DeitelMessengerServer, using the Socket provided to the SendingThread constructor. Lines 38 39 create a new PrintWriter for the clientSocket s OutputStream. Line 40 invokes method println of class PrintWriter to send the message. Line 41 invokes method flushof class PrintWriterto ensure that the message is sent immediately. Note that class SendingThread does not close the clientSocket. Class SocketMessageManager uses a new instance of class SendingThread for each message the client sends, so the clientSocketmust remain open until the user disconnects from DeitelMessengerServer. Class PacketReceivingThread extends class Thread to enable Socket- MessageManager to listen for incoming messages in a separate thread of execution. Line 19 declares the MessageListener to which PacketReceivingThread will deliver incoming messages. Line 22 declares a MulticastSocket, which enables PacketReceivingThread to receive multicast DatagramPackets. Line 25 declares an InetAddress reference for the multicast address to which Deitel- MessengerServer posts new chat messages. The MulticastSocket connects to this InetAddressto listen for incoming chat messages. 1 // PacketReceivingThread.java 2 // PacketReceivingThread listens for DatagramPackets containing 3 // messages from a DeitelMessengerServer. 4 package com.deitel.messenger.sockets.client; 5 6 // Java core packages 7 import java.io.*; 8 import java.net.*; 9 import java.util.*; 10 11 // Deitel packages 12 import com.deitel.messenger.*; 13 import com.deitel.messenger.sockets.*; 14 15 public class PacketReceivingThread extends Thread 16 implements SocketMessengerConstants { 17 18 // MessageListener to whom messages should be delivered 19 private MessageListener messageListener; 20 21 // MulticastSocket for receiving broadcast messages 22 private MulticastSocket multicastSocket; 23 24 // InetAddress of group for messages 25 private InetAddress multicastGroup; 26 27 // flag for terminating PacketReceivingThread 28 private boolean keepListening = true; 29 Fig. 17.19 PacketReceivingThreadfor listening for new multicast messages from DeitelMessengerServerin a separate Thread(part 1 of 4). #BREAK# Chapter 17 Networking 1043 30 // PacketReceivingThread constructor 31 public PacketReceivingThread( MessageListener listener ) 32 { 33 // invoke superclass constructor to name Thread 34 super( “PacketReceivingThread” ); 35 36 // set MessageListener 37 messageListener = listener; 38 39 // connect MulticastSocket to multicast address and port 40 try { 41 multicastSocket = 42 new MulticastSocket( MULTICAST_LISTENING_PORT ); 43 44 multicastGroup = 45 InetAddress.getByName( MULTICAST_ADDRESS ); 46 47 // join multicast group to receive messages 48 multicastSocket.joinGroup( multicastGroup ); 49 50 // set 5 second time-out when waiting for new packets 51 multicastSocket.setSoTimeout( 5000 ); 52 } 53 54 // handle exception connecting to multicast address 55 catch ( IOException ioException ) { 56 ioException.printStackTrace(); 57 } 58 59 } // end PacketReceivingThread constructor 60 61 // listen for messages from multicast group 62 public void run() 63 { 64 // listen for messages until stopped 65 while ( keepListening ) { 66 67 // create buffer for incoming message 68 byte[] buffer = new byte[ MESSAGE_SIZE ]; 69 70 // create DatagramPacket for incoming message 71 DatagramPacket packet = new DatagramPacket( buffer, 72 MESSAGE_SIZE ); 73 74 // receive new DatagramPacket (blocking call) 75 try { 76 multicastSocket.receive( packet ); 77 } 78 79 // handle exception when receive times out 80 catch ( InterruptedIOException interruptedIOException ) { 81 Fig. 17.19 PacketReceivingThreadfor listening for new multicast messages from DeitelMessengerServerin a separate Thread(part 2 of 4). #BREAK# 1044 Networking Chapter 17 82 // continue to next iteration to keep listening 83 continue; 84 } 85 86 // handle exception reading packet from multicast group 87 catch ( IOException ioException ) { 88 ioException.printStackTrace(); 89 break; 90 } 91 92 // put message data in a String 93 String message = new String( packet.getData() ); 94 95 // ensure non-null message 96 if ( message != null ) { 97 98 // trim extra whitespace from end of message 99 message = message.trim(); 100 101 // tokenize message to retrieve user name 102 // and message body 103 StringTokenizer tokenizer = 104 new StringTokenizer( message, MESSAGE_SEPARATOR ); 105 106 // ignore messages that do not contain a user 107 // name and message body 108 if ( tokenizer.countTokens() == 2 ) { 109 110 // send message to MessageListener 111 messageListener.messageReceived( 112 tokenizer.nextToken(), // user name 113 tokenizer.nextToken() ); // message body 114 } 115 116 } // end if 117 118 } // end while 119 120 // leave multicast group and close MulticastSocket 121 try { 122 multicastSocket.leaveGroup( multicastGroup ); 123 multicastSocket.close(); 124 } 125 126 // handle exception reading packet from multicast group 127 catch ( IOException ioException ) { 128 ioException.printStackTrace(); 129 } 130 131 } // end method run 132 Fig. 17.19 PacketReceivingThreadfor listening for new multicast messages from DeitelMessengerServerin a separate Thread(part 3 of 4). #BREAK# Chapter 17 Networking 1045 133 // stop listening for new messages 134 public void stopListening() 135 { 136 // terminate Thread 137 keepListening = false; 138 } 139 } Fig. 17.19 PacketReceivingThreadfor listening for new multicast messages from DeitelMessengerServerin a separate Thread(part 4 of 4). The PacketReceivingThread constructor (lines 31 59) takes as an argument the MessageListener to which the PacketReceivingThread should deliver incoming messages. Recall that interface MessageListener defines a single method messageReceived. When the PacketReceivingThreadreceives a new chat message over the MulticastSocket, PacketReceivingThread invokes method messageReceived to deliver the new message to the MessageListener. Lines 41 42 create a new MulticastSocketand pass to the MulticastSocket constructor the constant MULTICAST_LISTENING_PORT from interface SocketMessengerConstants. This argument specifies the port on which the MulticastSocket should listen for incoming chat messages. Lines 44 45 create an InetAddressobject for the MULTICAST_ADDRESS, to which DeitelMessengerServermulticasts new chat messages. Line 48 invokes method joinGroup of class MulticastSocket to register the MulticastSocket to receive messages sent to MULTICAST_ADDRESS. Line 51 invokes MulticastSocket method setSoTime-out to specify that, if no data is received in 5000milliseconds, the MulticastSocket should issue an InterruptedIOException, which the current thread can catch, then continue executing. This approach prevents PacketReceivingThread from deadlocking when waiting for incoming data. Also, if the MulticastSocket did not ever time out, the while loop would not be able to check the keepListening variable and would therefore prevent PacketReceivingThreadfrom stopping if keepListeningwere set to false. Method run (lines 62 131) listens for incoming multicast messages. Line 68 creates a bytearray in which to store the incoming DatagramPacketdata. Lines 71 72 create a DatagramPacketto store the incoming message. Line 76 invokes method receive of class MulticastSocket with the DatagramPacket packet as an argument. This is a blocking call that reads an incoming packet from the multicast address. If 5000milliseconds pass without receipt of a packet, method receive throws an Interrupted- IOException, because we previously set a 5000 millisecond time-out (line 51). Line 83 uses keyword continue to proceed to the next while loop iteration to continue listening for incoming messages. For other IOExceptions, line 89 breaks the while loop to terminate the PacketReceivingThread. Line 93 invokes method getData of class DatagramPacketto retrieve the message data. Line 99 invokes method trim of class String to remove extra whitespace from the end of the message. Recall that DatagramPackets are a fixed size 512bytes in this example so, if the message is shorter than 512 bytes, there will be extra whitespace after the message. Lines 103 104 create a StringTokenizer to separate the message body from the name of the user who sent the message. Line 108 checks for the correct number of tokens. Lines 111 113 invoke method messageReceived of inter #BREAK# 1046 Networking Chapter 17 face MessageListenerto deliver the incoming message to the PacketReceiving- Thread s MessageListener. If the program invokes method stopListening (lines 134 138), the while loop in method run (lines 62 118) terminates. Line 122 invokes method leaveGroup of class MulticastSocket to stop receiving messages from the multicast address. Line 123 invokes method close of class MulticastSocket to close the Multicast- Socket. PacketReceivingThread then terminates when method runreturns. Class ClientGUI (Fig. 17.20) extends class JFrame to create a GUI for a user to send and receive chat messages. The GUI consists of a JTextArea for displaying incoming messages (line 22), a JTextAreafor entering new messages (line 23), JButtons and JMenuItems for connecting to and disconnecting from the server (lines 26 29) and a JButtonfor sending messages (line 32). The GUI also contains a JLabelthat displays whether the client is connected or disconnected. 1 // ClientGUI.java 2 // ClientGUI provides a user interface for sending and receiving 3 // messages to and from the DeitelMessengerServer. 4 package com.deitel.messenger; 5 6 // Java core packages 7 import java.io.*; 8 import java.net.*; 9 import java.awt.*; 10 import java.awt.event.*; 11 12 // Java standard extensions 13 import javax.swing.*; 14 import javax.swing.border.*; 15 16 public class ClientGUI extends JFrame { 17 18 // JMenu for connecting/disconnecting server 19 private JMenu serverMenu; 20 21 // JTextAreas for displaying and inputting messages 22 private JTextArea messageArea; 23 private JTextArea inputArea; 24 25 // JButtons and JMenuItems for connecting and disconnecting 26 private JButton connectButton; 27 private JMenuItem connectMenuItem; 28 private JButton disconnectButton; 29 private JMenuItem disconnectMenuItem; 30 31 // JButton for sending messages 32 private JButton sendButton; 33 34 // JLabel for displaying connection status 35 private JLabel statusBar; Fig. 17.20 ClientGUIsubclass of JFramefor presenting a GUI for viewing and sending chat messages (part 1 of 6). #BREAK# Chapter 17 Networking 1047 36 37 // userName to add to outgoing messages 38 private String userName; 39 40 // MessageManager for communicating with server 41 private MessageManager messageManager; 42 43 // MessageListener for receiving incoming messages 44 private MessageListener messageListener; 45 46 // ClientGUI constructor 47 public ClientGUI( MessageManager manager ) 48 { 49 super( “Deitel Messenger” ); 50 51 // set the MessageManager 52 messageManager = manager; 53 54 // create MyMessageListener for receiving messages 55 messageListener = new MyMessageListener(); 56 57 // create File JMenu 58 serverMenu = new JMenu ( “Server” ); 59 serverMenu.setMnemonic( ‘S’ ); 60 JMenuBar menuBar = new JMenuBar(); 61 menuBar.add( serverMenu ); 62 setJMenuBar( menuBar ); 63 64 // create ImageIcon for connect buttons 65 Icon connectIcon = new ImageIcon( 66 getClass().getResource( “images/Connect.gif” ) ); 67 68 // create connectButton and connectMenuItem 69 connectButton = new JButton( “Connect”, connectIcon ); 70 connectMenuItem = new JMenuItem( “Connect”, connectIcon ); 71 connectMenuItem.setMnemonic( ‘C’ ); 72 73 // create ConnectListener for connect buttons 74 ActionListener connectListener = new ConnectListener(); 75 connectButton.addActionListener( connectListener ); 76 connectMenuItem.addActionListener( connectListener ); 77 78 // create ImageIcon for disconnect buttons 79 Icon disconnectIcon = new ImageIcon( 80 getClass().getResource( “images/Disconnect.gif” ) ); 81 82 // create disconnectButton and disconnectMenuItem 83 disconnectButton = new JButton( “Disconnect”, 84 disconnectIcon ); 85 disconnectMenuItem = new JMenuItem( “Disconnect”, 86 disconnectIcon ); 87 disconnectMenuItem.setMnemonic( ‘D’ ); Fig. 17.20 ClientGUIsubclass of JFramefor presenting a GUI for viewing and sending chat messages (part 2 of 6). #BREAK# 1048 Networking Chapter 17 88 89 // disable disconnect buttons 90 disconnectButton.setEnabled( false ); 91 disconnectMenuItem.setEnabled( false ); 92 93 // create DisconnectListener for disconnect buttons 94 ActionListener disconnectListener = 95 new DisconnectListener(); 96 disconnectButton.addActionListener( disconnectListener ); 97 disconnectMenuItem.addActionListener( disconnectListener ); 98 99 // add connect and disconnect JMenuItems to fileMenu 100 serverMenu.add( connectMenuItem ); 101 serverMenu.add( disconnectMenuItem ); 102 103 // add connect and disconnect JButtons to buttonPanel 104 JPanel buttonPanel = new JPanel(); 105 buttonPanel.add( connectButton ); 106 buttonPanel.add( disconnectButton ); 107 108 // create JTextArea for displaying messages 109 messageArea = new JTextArea(); 110 111 // disable editing and wrap words at end of line 112 messageArea.setEditable( false ); 113 messageArea.setWrapStyleWord( true ); 114 messageArea.setLineWrap( true ); 115 116 // put messageArea in JScrollPane to enable scrolling 117 JPanel messagePanel = new JPanel(); 118 messagePanel.setLayout( new BorderLayout( 10, 10 ) ); 119 messagePanel.add( new JScrollPane( messageArea ), 120 BorderLayout.CENTER ); 121 122 // create JTextArea for entering new messages 123 inputArea = new JTextArea( 4, 20 ); 124 inputArea.setWrapStyleWord( true ); 125 inputArea.setLineWrap( true ); 126 inputArea.setEditable( false ); 127 128 // create Icon for sendButton 129 Icon sendIcon = new ImageIcon( 130 getClass().getResource( “images/Send.gif” ) ); 131 132 // create sendButton and disable it 133 sendButton = new JButton( “Send”, sendIcon ); 134 sendButton.setEnabled( false ); 135 136 // create ActionListener for sendButton 137 sendButton.addActionListener( 138 new ActionListener() { 139 Fig. 17.20 ClientGUIsubclass of JFramefor presenting a GUI for viewing and sending chat messages (part 3 of 6). #BREAK# Chapter 17 Networking 1049 140 // send new message when user activates sendButton 141 public void actionPerformed( ActionEvent event ) 142 { 143 messageManager.sendMessage( userName, 144 inputArea.getText()); 145 146 // clear inputArea 147 inputArea.setText( “” ); 148 } 149 } // end ActionListener 150 ); 151 152 // lay out inputArea and sendButton in BoxLayout and 153 // add Box to messagePanel 154 Box box = new Box( BoxLayout.X_AXIS ); 155 box.add( new JScrollPane( inputArea ) ); 156 box.add( sendButton ); 157 messagePanel.add( box, BorderLayout.SOUTH ); 158 159 // create JLabel for statusBar with a recessed border 160 statusBar = new JLabel( “Not Connected” ); 161 statusBar.setBorder( 162 new BevelBorder( BevelBorder.LOWERED ) ); 163 164 // lay out components in JFrame 165 Container container = getContentPane(); 166 container.add( buttonPanel, BorderLayout.NORTH ); 167 container.add( messagePanel, BorderLayout.CENTER ); 168 container.add( statusBar, BorderLayout.SOUTH ); 169 170 // add WindowListener to disconnect when user quits 171 addWindowListener ( 172 new WindowAdapter () { 173 174 // disconnect from server and exit application 175 public void windowClosing ( WindowEvent event ) 176 { 177 messageManager.disconnect( messageListener ); 178 System.exit( 0 ); 179 } 180 } 18 ); 18 18 } // end ClientGUI constructor 18 18 // ConnectListener listens for user requests to connect to 18 // DeitelMessengerSever 18 private class ConnectListener implements ActionListener { 18 18 // connect to server and enable/disable GUI components 190 public void actionPerformed( ActionEvent event ) 191 { Fig. 17.20 ClientGUIsubclass of JFramefor presenting a GUI for viewing and sending chat messages (part 4 of 6). #BREAK# 1050 Networking Chapter 17 192 // connect to server and route messages to 193 // messageListener 194 messageManager.connect( messageListener ); 195 196 // prompt for userName 197 userName = JOptionPane.showInputDialog( 198 ClientGUI.this, “Enter user name:” ); 199 200 // clear messageArea 201 messageArea.setText( “” ); 202 203 // update GUI components 204 connectButton.setEnabled( false ); 205 connectMenuItem.setEnabled( false ); 206 disconnectButton.setEnabled( true ); 207 disconnectMenuItem.setEnabled( true ); 208 sendButton.setEnabled( true ); 209 inputArea.setEditable( true ); 210 inputArea.requestFocus(); 211 statusBar.setText( “Connected: ” + userName ); 212 } 213 214 } // end ConnectListener inner class 215 216 // DisconnectListener listens for user requests to disconnect 217 // from DeitelMessengerServer 218 private class DisconnectListener implements ActionListener { 219 220 // disconnect from server and enable/disable GUI components 221 public void actionPerformed( ActionEvent event ) 222 { 223 // disconnect from server and stop routing messages 224 // to messageListener 225 messageManager.disconnect( messageListener ); 226 227 // update GUI componets 228 sendButton.setEnabled( false ); 229 disconnectButton.setEnabled( false ); 230 disconnectMenuItem.setEnabled( false ); 231 inputArea.setEditable( false ); 232 connectButton.setEnabled( true ); 233 connectMenuItem.setEnabled( true ); 234 statusBar.setText( “Not Connected” ); 235 } 236 237 } // end DisconnectListener inner class 238 239 // MyMessageListener listens for new messages from the 240 // MessageManager and displays the messages in messageArea 241 // using a MessageDisplayer. 242 private class MyMessageListener implements MessageListener { 243 Fig. 17.20 ClientGUIsubclass of JFramefor presenting a GUI for viewing and sending chat messages (part 5 of 6). #BREAK# Chapter 17 Networking 1051 244 // when received, display new messages in messageArea 245 public void messageReceived( String from, String message ) 246 { 247 // append message using MessageDisplayer and 248 // invokeLater, ensuring thread-safe access messageArea 249 SwingUtilities.invokeLater( 250 new MessageDisplayer( from, message ) ); 251 252 } // end method messageReceived 253 254 } // end MyMessageListener inner class 255 256 // MessageDisplayer displays a new messaage by 257 // appending the message to the messageArea JTextArea. This 258 // Runnable object should be executed only on the Event 259 // thread, because it modifies a live Swing component. 260 private class MessageDisplayer implements Runnable { 261 262 private String fromUser; 263 private String messageBody; 264 265 // MessageDisplayer constructor 266 public MessageDisplayer( String from, String body ) 267 { 268 fromUser = from; 269 messageBody = body; 270 } 271 272 // display new message in messageArea 273 public void run() 274 { 275 // append new message 276 messageArea.append( “n” + fromUser + “> ” + 277 messageBody ); 278 279 // move caret to end of messageArea to ensure new 280 // message is visible on screen 281 messageArea.setCaretPosition( 282 messageArea.getText().length() ); 283 } 284 285 } // end MessageDisplayer inner class 286 } Fig. 17.20 ClientGUIsubclass of JFramefor presenting a GUI for viewing and sending chat messages (part 6 of 6). ClientGUI uses a MessageManager(line 41) to handle all communication with the chat server. Recall that MessageManageris an interface and therefore allows ClientGUI to use any MessageManager implementation without the need to change any code in ClientGUI. Class ClientGUI also uses a MessageListener (line 44) to receive incoming messages from the MessageManager. The ClientGUI constructor (lines 47 183) takes as an argument the Message- Managerfor communicating with DeitelMessengerServer. Line 52 sets the Cli #BREAK# 1052 Networking Chapter 17 entGUI s MessageManager. Line 55 creates an instance of MyMessageListener, which implements interface MessageListener. Lines 58 62 create a Server menu that contains JMenuItems for connecting to and disconnecting from the chat server. Lines 65 66 create an ImageIconfor connectButton and connectMenuItem. Line 66 invokes method getClass (inherited from class Object) to retrieve the Class object that represents the ClientGUI class definition. Line 66 then invokes method getResource of class Class to load the connect image. The Java virtual machine loads class definitions into memory, using a class loader. Method getResource of Class uses the Class object s class loader to specify the location of a resource, such as an image file. Specifying resource locations in this manner enables programs to avoid hard-coded or absolute paths, which can make programs more difficult to deploy. Using the techniques described here enables an applet or application to load files from locations that are relative to the location of the .class file for a given class. Lines 69 70 create connectButton and connectMenuItem, each with the label “Connect” and the IconconnectIcon. Line 71 invokes method setMnemonicof class JMenuItem to set the mnemonic character for keyboard access to connectMenu- Item. Line 74 creates an instance of private inner class ConnectListener, which implements interface ActionListener to handle ActionEvents from connect- Button and connectMenuItem. Lines 75 76 add connectListener as an ActionListener for connectButton and connectMenuItem. Lines 79 80 create an ImageIcon for the disconnectButton and disconnectMenuItem components. Lines 83 86 create disconnectButton and disconnectMenuItem, each with the label “Disconnect” and the IcondisconnectIcon. Line 87 invokes method setMnemonic of class JMenuItem to enable keyboard access to disconnectMenuItem. Lines 90 91 invoke method setEnabledof class JButton and class JMenuItem with a false argument to disable disconnectButton and disconnectMenuItem. This prevents the user from attempting to disconnect from the server because the client is not yet connected. Lines 94 95 create an instance of private inner class DisconnectListener, which implements interface ActionListener to handle ActionEvents from disconnectButton and disconnectMenuItem. Lines 96 97 add disconnectListener as an Action- Listener for the disconnectButton and disconnectMenuItem components. Lines 100 101 add connectMenuItem and disconnectMenuItem to the Server JMenu. Lines 104 106 create a JPanel and add connectButton and disconnectButton to that JPanel. Line 109 creates the JTextArea messageArea, in which the client displays incoming messages. Line 112 invokes method setEnabled with a false argument, to disable editing ot the text in messageArea. Lines 113 114 invoke methods setWrapStyleWord and setLineWrap of class JTextArea to enable word wrapping in messageArea. If a message is longer than the width of the messageArea, the messageArea will wrap the text after the last word that fits on each line, making longer messages easier to read. Lines 117 120 create a JPanel for the messageArea and add the messageArea to the JPanel in a JScrollPane. The JScrollPane adds scroll bars to the messageArea to enable the user to scroll through messages that exceed the size of messageArea. Line 123 creates the inputAreaJTextArea for entering new messages. The arguments to the JTextArea constructor specify a four-line JTextArea that is twenty char #BREAK# Chapter 17 Networking 1053 acters wide. Lines 124 125 enable word and line wrapping, and line 126 disables editing the inputArea. When the client connects to the chat server, ConnectListener enables the inputArea to allow the user to type new messages. Line 129 creates an ImageIcon for sendButton. Line 133 creates sendButton, which the user can click to send a message the user has typed. Line 134 disables send- Button; the ConnectListener enables the sendButton when the client connects to the chat server. Lines 137 150 add an ActionListener to sendButton. Lines 143 144 invoke method sendMessage of interface MessageManager with the userName and inputArea text as arguments. This statement sends the user s name and whatever text the user entered in inputAreato DeitelMessengerServer as a new chat message. Line 147 invokes method setText of class JTextArea with an empty String argument to clear the inputArea for the next message. Lines 154 157 use a BoxLayout to arrange the inputArea and sendButton. Line 155 places inputArea in a JScrollPane to enable scrolling of long messages. Line 157 adds the Box containing inputArea and sendButton to the SOUTH region of messagePanel. Lines 160 162 create the statusBarJLabel. This JLabeldisplays whether the client is connected to or disconnected from the chat server. Lines 161 162 invoke method setBorder of class JLabel and create a new BevelBorder of type BevelBorder.LOWERED. This border makes the JLabel appear recessed, as is common with status bars in many applications. Lines 165 168 lay out buttonPanel, messagePanel and statusBarin the ClientGUIJFrame. Lines 171 181 add a WindowListener to the ClientGUI JFrame. Line 177 invokes method disconnect of interface MessageManager to disconnect from the chat server in case the user quits while still connected. Inner class ConnectListener (lines 187 214) handles events from connect- Button and connectMenuItem. Line 194 invokes method connect of class MessageManager to connect to the chat server. Line 194 passes as an argument to method connect the MessageListener to which new messages should be delivered. Lines 197-198 prompt the user for a user name, and line 201 clears the messageAreaJTextArea. Lines 204 209 enable the components for disconnecting from the server and for sending messages and disable components for connecting to the server. Line 210 invokes method requestFocus of class JTextArea to place the text-input cursor in the inputArea so the user can begin typing a message more easily. Inner class DisconnectListener (lines 218 237) handles events from disconnectButton and disconnectMenuItem. Line 225 invokes method disconnect of class MessageManagerto disconnect from the chat server. Lines 228 234 disable the components for sending messages and the components for disconnecting then enable the components for connecting to the chat server. Inner class MyMessageListener (lines 242 254) implements interface MessageListener to receive incoming messages from the MessageManager. When a new message is received, the program invokes method messageReceived (lines 242 252) with the user name of the sender and the message body. Lines 249 250 invoke static method invokeLater of class SwingUtilties with a new instance of MessageDisplayer to append the new message to messageArea. Recall, from Chapter 15, that Swing components should be accessed only from the event dispatching thread. Method messageReceivedis invoked by the PacketReceivingThreadin #BREAK# 1054 Networking Chapter 17 class SocketMessageManagerand therefore cannot append the message text to messageAreadirectly, as this would occur in PacketReceivingThread, not the event- dispatch thread. Inner class MessageDisplayer(lines 260 285) implements interface Runnable to provide a thread-safe way to append text to the messageArea JTextArea. The MessageDisplayerconstructor (lines 266 270) takes as arguments the user name and message to send. Method run (lines 273 283) appends the user name, “>” and messageBodyto messageArea. Lines 281 282 invoke method setCaretPositionof class JTextArea to scroll messageArea to the bottom to display the most recently received message. Instances of class MessageDisplayer should execute only as part of the event-dispatching thread, to ensure thread-safe access to the messageAreaSwing component. Class DeitelMessenger (Fig. 17.21) launches the client for the DeitelMessengerServer. Lines 18 21 create a new SocketMessageManager to connect to the DeitelMessengerServer with the IP address specified as a command-line argument to the application. Lines 24 27 create a ClientGUI for the MessageManager, set the ClientGUIsize and make the ClientGUIvisible. 1 // DeitelMessenger.java 2 // DeitelMessenger is a chat application that uses a ClientGUI 3 // and SocketMessageManager to communicate with 4 // DeitelMessengerServer. 5 package com.deitel.messenger.sockets.client; 6 7 // Deitel packages 8 import com.deitel.messenger.*; 9 10 public class DeitelMessenger { 11 12 // execute application 13 public static void main( String args[] ) 14 { 15 MessageManager messageManager; 16 17 // create new DeitelMessenger 18 if ( args.length == 0 ) 19 messageManager = new SocketMessageManager( “localhost” ); 20 else 21 messageManager = new SocketMessageManager( args[ 0 ] ); 22 23 // create GUI for SocketMessageManager 24 ClientGUI clientGUI = new ClientGUI( messageManager ); 25 clientGUI.setSize( 300, 400 ); 26 clientGUI.setResizable( false ); 27 clientGUI.setVisible( true ); 28 } 29 } Fig. 17.21 DeitelMessengerapplication for participating in a DeitelMessengerServerchat session (part 1 of 3). #BREAK# Chapter 17 Networking 1055 Fig. 17.21 DeitelMessengerapplication for participating in a DeitelMessengerServerchat session (part 2 of 3). #BREAK# 1056 Networking Chapter 17 Fig. 17.21 DeitelMessengerapplication for participating in a DeitelMessengerServerchat session (part 3 of 3). The Deitel messenger case study is a significant application that uses many intermediate Java features, such as networking with Sockets, DatagramPackets and MulticastSockets, multithreading and Swing GUI. The case study also demonstrates good software engineering practices by separating interface from implementation, enabling developers to build MessageManagers for different network protocols and Message- Listeners that provide different user interfaces. You should now be able to apply these techniques to your own, more complex, Java projects. 17.11 (Optional) Discovering Design Patterns: Design Patterns Used in Packages java.ioand java.net This section introduces those design patterns associated with the Java file, streams and networking packages. 17.11.1 Creational Design Patterns We now continue our discussion of creational design patterns. Abstract Factory Like the Factory Method design pattern, the Abstract Factory design pattern allows a system to determine the subclass from which to instantiate an object at run time. Often, this subclass is unknown during development. However, Abstract Factory uses an object known as a factory that uses an interface to instantiate objects. A factory creates a product; in this case, that product is an object of a subclass determined at run time. The Java socket library in package java.netuses the Abstract Factory design pattern. A socket describes a connection, or a stream of data, between two computers. Class Socketreferences an object of a SocketImplsubclass (Section 17.5). Class Socket also contains a static reference to an object implementing interface Socket- ImplFactory. The Socket constructor invokes method createSocketImpl of interface SocketFactory to create the SocketImpl object. The object that imple #BREAK# Chapter 17 Networking 1057 ments interface SocketFactory is the factory, and an object of a SocketImpl subclass is the product of that factory. The system cannot specify the SocketImpl subclass from which to instantiate until run time, because the system has no knowledge of what type of Socket implementation is required (e.g., a socket configured to the local network s security requirements). Method createSocketImpl decides the SocketImpl subclass from which to instantiate the object at run time. 17.11.2 Structural Design Patterns This section concludes our discussion of structural design patterns. Decorator Let us reexamine class CreateSequentialFile (Fig. 16.6). Lines 127 128 of this class allow an ObjectOutputStreamobject, which writes objects to a file, to gain the responsibilities of a FileOutputStream object, which provides methods for writing bytes to files. Class CreateSequentialFile appears to chain objects a File- OutputStream object is the argument to the ObjectOutputStream s constructor. The fact that the ObjectOutputStream object can gain the behavior of a FileOutputStreamdynamically prevents the need for creating a separate class called Object- FileOutputStream, which would implement the behaviors of both classes. Lines 127 128 of class CreateSequentialFile show an example of the Decorator design pattern, which allows an object to gain additional responsibilities dynamically. Using this pattern, designers do not have to create separate, unnecessary classes to add responsibilities to objects of a given class. Let us consider a more complex example to discover how the Decorator design pattern can simplify a system s structure. Suppose we wanted to enhance the I/O-performance of the previous example by using a BufferedOutputStream. Using the Decorator design pattern, we would write output = new ObjectOutputStream( new BufferedOutputStream( new FileOutputStream( fileName ) ) ); We can chain objects in this manner, because ObjectOutputStream, Buffered- OutputStream and FileOutputStream extend abstract superclass OutputStream, and each subclass constructor takes an OutputStream object as a parameter. If the stream objects in package java.io did not use the Decorator pattern (i.e., did not satisfy these two requirements), package java.io would have to provide classes BufferedFileOutputStream, ObjectBufferedOutputStream, ObjectBufferedFileOutputStream and ObjectFileOutputStream. Consider how many classes we would have to create if we chained even more stream objects without applying the Decorator pattern. Facade When driving a car, you know that pressing the gas pedal accelerates your car, but you are unaware of exactly how the gas pedal causes your car to accelerate. This principle is the foundation of the Facade design pattern, which allows an object called a facade object to provide a simple interface for the behaviors of a subsystem an aggregate of objects that comprise collectively a major system responsibility. The gas pedal, for example, is the facade object for the car s acceleration subsystem, the steering wheel is the facade object for #BREAK# 1058 Networking Chapter 17 the car s steering subsystem and the brake is the facade object for the car s deceleration subsystem. A client object uses the facade object to access the objects behind the facade. The client remains unaware of how the objects behind the facade fulfill responsibilities, so the subsystem complexity is hidden from the client. When you press the gas pedal you act as a client object. The Facade design pattern reduces system complexity, because a client interacts with only one object (the facade). This pattern shields applications developers from subsystem complexities. Developers need to be familiar with only the operations of the facade object, rather than with the more detailed operations of the entire subsystem. In package java.net, an object of class URLis a facade object. This object contains a reference to an InetAddressobject that specifies the host computer s IP address. The URL facade object also references an object from class URLStreamHandler, which opens the URL connection. The client object that uses the URLfacade object accesses the InetAddress object and the URLStreamHandler object through the facade object. However, the client object does not know how the objects behind the URL facade object accomplish their responsibilities. 17.11.3 Architectural Patterns Design patterns allow developers to design specific parts of systems, such as abstracting object instantiations or aggregating classes into larger structures. Design patterns also promote loose coupling among objects. Architectural patterns promote loose coupling among subsystems. These patterns specify all subsystems in the system and how they interact with each other.1 We introduce the popular Model-View-Controller and Layers architectural patterns. MVC Consider the design of a simple text editor. In this program, the user inputs text from the keyboard and formats this text using the mouse. Our program stores this text and format information into a series of data structures, then displays this information on screen for the user to read what has been inputted. This program adheres to the Model-View-Controller (MVC) architectural pattern, which separates application data (contained in the model) from graphical presentation components (the view) and input-processing logic (the controller).2 Figure 17.22 shows the relationships between components in MVC. notifiesmodifies ModelController View Fig. 17.22Model-View-Controller Architecture. 17.22 1. R. Hartman. Building on Patterns. Application Development Trends May 2001: 19 26. 2. Section 13.17 also discussed Model-View-Controller architecture and its relevance to the elevator simulation case study. #BREAK# Chapter 17 Networking 1059 The controller implements logic for processing user inputs. The model contains application data, and the view presents the data stored in the model. When a user provides some input, the controller modifies the model with the given input. The model contains the application data. With regards to the text-editor example, the model might contain only the characters that make up the document. When the model changes, it notifies the view of the change so the view can update its presentation with the changed data. The view in a word processor might display characters using a particular font, with a particular size, etc. MVC does not restrict an application to a single view and a single controller. In a more sophisticated program (e.g., a word processor), there might be two views of a document model. One view might display an outline of the document and the other might display the complete document. The word processor also might implement multiple controllers one for handling keyboard input and another for handling mouse selections. If either controller makes a change in the model, both the outline view and the print-preview window will show the change immediately when the model notifies all views of changes. Another key benefit to the MVC architectural pattern is that developers can modify each component individually without having to modify the other components. For example, developers could modify the view that displays the document outline, but the developers would not have to modify either the model or other views or controllers. Layers Consider the design in Fig. 17.23, which presents the basic structure of a three-tier application, in which each tier contains a unique system component. ApplicationMiddle tier Information tier (Bottom tier) Client tier (Top tier) Database Fig. 17.23Three-tier application model. 17.23 #BREAK# 1060 Networking Chapter 17 The information tier (also called the bottom tier ) maintains data for the application, typically storing the data in a database. The information tier for an online store may contain product information, such as descriptions, prices and quantities in stock and customer information, such as user names, billing addresses and credit-card numbers. The middle tier acts as an intermediary between the information tier and the client tier. The middle tier processes client-tier requests, reads data from and writes data to the database. The middle tier then processes data from the information tier and presents the content to the client tier. This processing is the application s business logic, which handles such tasks as retrieving data from the information tier, ensuring that data is reliable before updating the database and presenting data to the client tier. For example, the business logic associated with the middle tier for the online store can verify a customer s credit card with the credit-card issuer before the warehouse ships the customer s order. This business logic could then store (or retrieve) the credit information in the database and notify the client tier that the verification was successful. The client tier (also called the top tier ) is the application s user interface, such as a standard Web browser. Users interact directly with the application through the user interface. The client tier interacts with the middle tier to make requests and retrieve data from the information tier. The client tier then displays data retrieved from the middle tier. Figure 17.23 is an implementation of the Layers architectural pattern, which divides functionality into separate layers. Each layer contains a set of system responsibilities and depends on the services of only the next lower layer. In Fig. 17.23, each tier corresponds to a layer. This architectural pattern is useful, because a designer can modify one layer without having to modify the other layers. For example, a designer could modify the information tier in Fig. 17.23 to accommodate a particular database product, but the designer would not have to modify either the client tier or the middle tier. 17.11.4 Conclusion In this Discovering Design Patterns section, we discussed how packages java.io and java.net take advantage of specific design patterns and how developers can integrate design patterns with networking/file applications in Java. We also introduced the Model- View-Controller and Layers architectural patterns, which both assign system functionality to separate subsystems. These patterns make designing a system easier for developers. In Discovering Design Patterns Section 21.12, we conclude our presentation of design patterns by discussing those design patterns used in package java.util. SUMMARY Java provides stream sockets and datagram sockets. With stream sockets, a process establishes a connection to another process. While the connection is in place, data flows between the processes in continuous streams. Stream sockets are said to provide a connection-oriented service. The protocol used for transmission is the popular TCP (Transmission Control Protocol). With datagram sockets, individual packets of information are transmitted. This is not the right protocol for everyday users, because, unlike TCP, the protocol used, UDP the User Datagram Protocol is a connectionless service and does not guarantee that packets arrive in any particular way. In fact, packets can be lost, can be duplicated and can even arrive out of sequence. So, with UDP, significant extra programming is required on the user s part to deal with these problems (if the user chooses to do so). #BREAK# Chapter 17 Networking 1061 The HTTP protocol (Hypertext Transfer Protocol) that forms the basis of the World Wide Web uses URIs (Uniform Resource Identifiers, also called URLs or Uniform Resource Locators) to locate data on the Internet. Common URIs represent files or directories and can represent complex tasks such as database lookups and Internet searches. Web browsers often restrict an applet so that it can communicate only with the machine from which it was originally downloaded. A Hashtable stores key/value pairs. A program uses a key to store and retrieve an associated value in the Hashtable. Hashtablemethod put takes two arguments a key and its associated value and places the value in the Hashtableat a location determined by the key. Hash- table method get takes one argument a key and retrieves the value (as an Object reference) associated with the key. A Vector is a dynamically resizable array of Objects. Vectormethod addElement adds a new element to the end of the Vector. Appletmethod getAppletContext returns a reference to an AppletContext object that represents the applet s environment (i.e., the browser in which the applet is executing). AppletContextmethod showDocument receives a URL object as an argument and passes it to the AppletContext (i.e., the browser), which displays the World Wide Web resource associated with that URL. A second version of AppletContextmethod showDocument enables an applet to specify the target frame in which Web resource should be displayed. Special target frames include _blank (display the content from the specified URI in a new Web browser window), _self (display the content from the specified URI in the same frame as the applet) and _top (the browser should remove the current frames, then display the content from the specified URI in the current window). Componentmethod setCursor changes the mouse cursor when the cursor is positioned over a specific GUI component. The Cursor constructor receives an integer indicating the cursor type (such as Cursor.WAIT_CURSORor Cursor.DEFAULT_CURSOR). JEditorPane method setPage downloads the document specified by its argument and displays it in the JEditorPane. Typically, an HTML document contains hyperlinks text, images or GUI components that, when clicked, link to another document on the Web. If an HTML document is displayed in a JEditor- Pane and the user clicks a hyperlink, the JEditorPane generates a HyperlinkEvent (package javax.swing.event) and notifies all registered HyperlinkListeners (package javax.swing.event) of that event. HyperlinkEvent method getEventType determines the type of the HyperlinkEvent. Class HyperlinkEvent contains public static inner class EventType, which defines three hyperlink event types: ACTIVATED (user clicked a hyperlink), ENTERED (user moved the mouse over a hyperlink) and EXITED (user moved the mouse away from a hyperlink). HyperlinkEvent method getURL obtains the URL represented by the hyperlink. Stream-based connections are managed with Socket objects. A ServerSocket object establishes the port where a server waits for connections from clients. The second argument to the ServerSocket constructor specifies the number of clients that can wait for a connection and be processed by the server. If the queue of clients is full, client connections are refused. The ServerSocket method accept waits indefinitely (i.e., blocks) for a connection from a client and returns a Socket object when a connection is established. Socketmethod getOutputStreamgets a reference to the OutputStream associated with a Socket. Socket method getInputStream gets a reference to the InputStream associated with the Socket. #BREAK# 1062 Networking Chapter 17 When transmission over a Socket connection is complete, the server closes the connection by invoking the Socket s closemethod. A Socket object connects a client to a server by specifying the server name and port number when creating the Socket object. A failed connection attempt throws an IOException. When the InputStream method read returns 1, the stream detects that the end-of-stream has been reached. An EOFException occurs when a ObjectInputStream attempts to read a value from a stream on which end-of-stream is detected. InetAddress method getByName returns an InetAddress object containing the host name of the computer for which the String host name or String Internet address is specified as an argument. InetAddressmethod getLocalHost returns an InetAddress object containing the host name of the local computer executing the program. The port at which a client connects to a server is sometimes called the handshake point. Connection-oriented transmission is like the telephone system you dial and are given a connection to the telephone of the person with whom you wish to communicate. The connection is maintained for the duration of your phone call, even when you are not talking. Connectionless transmission with datagrams is similar to mail carried via the postal service. A large message that will not fit in one envelope can be broken into separate message pieces that are placed in separate, sequentially numbered envelopes. Each of the letters is then mailed at once. The letters could arrive in order, out of order or not at all. DatagramPacketobjects store packets of data for sending or store packets of data received by an application. DatagramSockets send and receive DatagramPackets. The DatagramSocket constructor that takes no arguments binds the application to a port chosen by the computer on which the program executes. The DatagramSocket constructor that takes an integer port number argument binds the application to the specified port. If a DatagramSocket constructor fails to bind the application to a port, a SocketException occurs. DatagramSocketmethod receive blocks (waits) until a packet arrives, then stores the packet in its argument. DatagramPacket method getAddress returns an InetAddressobject containing information about the host computer from which the packet was sent. DatagramPacket method getPort returns an integer specifying the port number through which the host computer sent the DatagramPacket. DatagramPacket method getLengthreturns an integer representing the number of bytes of data in a DatagramPacket. DatagramPacketmethod getData returns a byte array containing the data in a Datagram- Packet. The DatagramPacket constructor for a packet to be sent takes four arguments the byte array to be sent, the number of bytes to be sent, the client address to which the packet will be sent and the port number where the client is waiting to receive packets. DatagramSocket method send sends a DatagramPacket out over the network. If an error occurs when receiving or sending a DatagramPacket, an IOException occurs. Reading data from a Socket is a blocking call the current thread is put in the blocked state while the thread waits for the read operation to complete. Method setSoTimeout specifies that, if no data is received in the given number of milliseconds, the Socket should issue an Inter #BREAK# Chapter 17 Networking 1063 ruptedIOException, which the current thread can catch, then continue executing. This prevents the current thread from deadlocking if there is no more data available from the Socket. Multicast is an efficient way to send data to many clients without the overhead of broadcasting that data to every host on the Internet. Using multicast, an application can publish DatagramPackets to be delivered to other applications the subscribers. An application multicasts DatagramPackets by sending the DatagramPackets to a multicast address an IP address in the range from 224.0.0.0 to 239.255.255.255, reserved for multicast. Clients that wish to receive DatagramPackets can connect to the multicast address to join the multicast group that will receive the published DatagramPackets. Multicast DatagramPackets are not reliable packets are not guaranteed to reach any destination. Also, the order in which clients receive the datagrams is not guaranteed. The MulticastSocket constructor takes as an argument the port to which the Multicast- Socket should connect to receive incoming DatagramPackets. Method joinGroup of class MulticastSocket takes as an argument the InetAddress of the multicast group to join. Method receive of class MulticastSocket reads an incoming DatagramPacket from a multicast address. TERMINOLOGY accept a connection deny a connection accept method of ServerSocket class duplicated packets addElement method of class Vector get method of class Hashtable AppletContext interface getAddress method of DatagramPacket bind to a port getAppletContext method of class Applet BindException class getByNamemethod of InetAddress client getData method of class DatagramPacket client connects to a server getEventTypemethod client/server relationship getInputStream method of class Socket client-side socket getLengthmethod of DatagramPacket close a connection getLocalHostmethod close method of class Socket getLocalHost method of InetAddress collaborative computing getOutputStream method of class Socket computer networking getPort method of class DatagramPacket connect to a port getPredefinedCursor method of Cursor connect to a World Wide Web site getURL method of class HyperlinkEvent ConnectException class handshake point connection Hashtable class connection request heterogeneous computer systems connectionless service host connectionless transmission with datagrams Hyperlink.EventType class connection-oriented service Hyperlink.EventType.ACTIVATED Cursor class Hyperlink.EventType.ENTERED Cursor.DEFAULT_CURSOR Hyperlink.EventType.EXITED Cursor.WAIT_CURSOR HyperlinkEvent class datagram HyperlinkListener interface datagram socket hyperlinkUpdate method DatagramPacket class InetAddress class DatagramSocket class Internet #BREAK# 1064 Networking Chapter 17 Internet address register an available port number InterruptedIOException class send method of class DatagramSocket IOException class server Java Security API server-side socket java.net package ServerSocket class JEditorPane class setCursor method of class Component joinGroupmethod of MulticastSocket setPage method of class JEditorPane key/value pair setSoTimeout method of class Socket leaveGroup method of MulticastSocket showDocument method of AppletContext lost packets socket MalformedURLException class Socket class multicast socket-based communications multicast address SocketException multicast group stream socket MulticastSocket class TCP (Transmission Control Protocol) multithreaded server UDP (User Datagram Protocol) network programming UnknownHostException networking URI (Uniform Resource Identifier) open a socket URL (Uniform Resource Locator) out-of-sequence packets URL class packet Vector class packet length wait for a connection port wait for a packet port number on a server Web browser put method of class Hashtable Web server read from a socket World Wide Web receive method of class DatagramSocket SELF-REVIEW EXERCISES 17.1 Fill in the blanks in each of the following statements: a) Exception occurs when an input/output error occurs when closing a socket. b) Exception occurs when a server address indicated by a client cannot be re solved. c) If a DatagramSocket constructor fails to set up a DatagramSocket properly, an exception of type occurs. d) Many of Java s networking classes are contained in package . e) Class binds the application to a port for datagram transmission. f) An object of class contains an Internet address. g) The two types of sockets we discussed in this chapter are sockets and sockets. h) The acronym URL stands for . i) The acronym URI stands for . j) The key protocol that forms the basis of the World Wide Web is . k) AppletContextmethod receives a URL object as an argument and dis plays in a browser the World Wide Web resource associated with that URL. l) InetAddressmethod getLocalHost returns an object containing the local host name of the computer on which the program is executing. m) Method of class MulticastSocket subscribes the MulticastSocket to a multicast group. #BREAK# Chapter 17 Networking 1065 n) The URL constructor determines whether the String passed as an argument represents a valid Uniform Resource Identifier. If so, the URL object is initialized to contain the URI; otherwise, an exception of type occurs. 17.2 State whether each of the following is true or false. If false, explain why. a) An application that uses multicast broadcasts DatagramPackets to every host on the Internet. b) UDP is a connection-oriented protocol. c) With stream sockets a process establishes a connection to another process. d) A server waits at a port for connections from a client. e) Datagram packet transmission over a network is reliable packets are guaranteed to ar rive in sequence. f) For security reasons, many Web browsers such as Netscape Communicator allow Java applets to do file processing only on the machines on which they execute. g) Web browsers often restrict an applet so that it can only communicate with the machine from which it was originally downloaded. h) IP addresses in the range from 224.0.0.0 to 239.255.255.255 are reserved for multicast. ANSWERS TO SELF-REVIEW EXERCISES 17.1 a) IOException. b) UnknownHostException. c) SocketException. d) java.net. e) DatagramSocket. f) InetAddress. g) stream, datagram. h) Uniform Resource Locator. i) Universal Resource Identifier. j) http. k) showDocument. l) InetAddress. m) joinGroup. n) MalformedURLException. 17.2 a) False; multicast sends DatagramPackets only to hosts that have joined the multicast group. b) False; UDP is a connectionless protocol and TCP is a connection-oriented protocol. c) True. d) True. e) False; packets could be lost and packets can arrive out of order. f) False; most browsers prevent applets from doing file processing on the client machine. g) True. h) True. EXERCISES 17.3 Distinguish between connection-oriented and connectionless network services. 17.4 How does a client determine the host name of the client computer? 17.5 Under what circumstances would a SocketException be thrown? 17.6 How can a client get a line of text from a server? 17.7 Describe how a client connects to a server. 17.8 Describe how a server sends data to a client. 17.9 Describe how to prepare a server to receive a stream-based connection request from a single client. 17.10 Describe how to prepare a server to receive connection requests from multiple clients if each client that connects should be processed in parallel with all other connected clients. 17.11 How does a server listen for connections at a port? 17.12 What determines how many connect requests from clients can wait in a queue to connect to a server? 17.13 As described in the text, what reasons might cause a server to refuse a connection request from a client? #BREAK# 1066 Networking Chapter 17 17.14 Use a socket connection to allow a client to specify a file name and have the server send the contents of the file or indicate that the file does not exist. 17.15 Modify Exercise 17.14 to allow the client to modify the contents of the file and send the file back to the server for storage. The user can edit the file in a JTextArea, then click a save changes button to send the file back to the server. 17.16 Modify program of Fig. 17.2 to allow users to add their own sites to the list and remove sites from the list. 17.17 Multithreaded servers are quite popular today, especially because of the increasing use of multiprocessing servers. Modify the simple server application presented in Section 17.6 to be a multi- threaded server. Then use several client applications and have each of them connect to the server simultaneously. Use a Vector to store the client threads. Vector provides several methods of use in this exercise. Method size determines the number of elements in a Vector. Method elementAt returns the element in the specified location (as an Object reference). Method add places a new element at the end of the Vector. Method removedeletes its argument from the Vector. Method lastElement returns an Objectreference to the last object you inserted in the Vector. 17.18 In the text, we presented a tic-tac-toe program controlled by a multithreaded server. Develop a checkers program modeled after the tic-tac-toe program. The two users should alternate making moves. Your program should mediate the players moves, determining whose turn it is and allowing only valid moves. The players themselves will determine when the game is over. 17.19 Develop a chess-playing program modeled after the checkers program in the Exercise 17.18. 17.20 Develop a Blackjack card game program in which the server application deals cards to each of the client applets. The server should deal additional cards (as per the rules of the game) to each player as requested. 17.21 Develop a Poker card game in which the server application deals cards to each of the client applets. The server should deal additional cards (as per the rules of the game) to each player as requested. 17.22 (Modifications to the Multithreaded Tic-Tac-Toe Program) The programs of Fig. 17.8 and Fig. 17.9 implemented a multithreaded, client/server version of the game Tic-Tac-Toe. Our goal in developing this game was to demonstrate a multithreaded server that could process multiple connections from clients at the same time. The server in the example is really a mediator between the two client applets it makes sure that each move is valid and that each client moves in the proper order. The server does not determine who won or lost or if there was a draw. Also, there is no capability to allow a new game to be played or to terminate an existing game. The following is a list of suggested modifications to the multithreaded Tic-Tac-Toe application and applet. a) Modify the TicTacToeServer class to test for a win, loss or draw on each move in the game. Send a message to each client applet that indicates the result of the game when the game is over. b) Modify the TicTacToeClient class to display a button that when clicked allows the client to play another game. The button should be enabled only when a game completes. Note that both class TicTacToeClient and class TicTacToeServer must be modified to reset the board and all state information. Also, the other TicTacToeClient should be notified that a new game is about to begin so its board and state can be reset. c) Modify the TicTacToeClient class to provide a button that allows a client to terminate the program at any time. When the user clicks the button, the server and the other client should be notified. The server should then wait for a connection from another client so a new game can begin. d) Modify the TicTacToeClient class and the TicTacToeServer class so the winner of a game can choose game piece X or O for the next game. Remember: X always goes first. #BREAK# Chapter 17 Networking 1067 e) If you would like to be ambitious, allow a client to play against the server while the server waits for a connection from another client. 17.23 (3-D Multithreaded Tic-Tac-Toe) Modify the multithreaded, client/server Tic-Tac-Toe program to implement a three-dimensional 4-by-4-by-4 version of the game. Implement the server application to mediate between the two clients. Display the three-dimensional board as four boards containing four rows and four columns each. If you would like to be ambitious, try the following modifications: a) Draw the board in a three-dimensional manner. b) Allow the server to test for a win, loss or draw. Beware! There are many possible ways to win on a 4-by-4-by-4 board! 17.24 (Networked Morse Code) Modify your solution to Exercise 10.27 to enable two applets to send Morse Code messages to each other through a multithreaded server application. Each applet should allow the user to type normal characters in JTextAreas, translate the characters into Morse Code and send the coded message through the server to the other client. When messages are received, they should be decoded and displayed as normal characters and as Morse Code. The applet should have two JTextAreas: one for displaying the other client s messages and one for typing. #BREAK# 18 Multimedia: Images, Animation, Audio and Video Objectives To understand how to get and display images. To create animations from sequences of images. To customize an animation applet with applet parameters specified in the applet s HTML document. To create image maps. To be able to get, play, loop and stop sounds using an AudioClip. The wheel that squeaks the loudest gets the grease. John Billings (Henry Wheeler Shaw) We ll use a signal I have tried and found far-reaching and easy to yell. Waa-hoo! Zane Grey There is a natural hootchy-kootchy motion to a goldfish. Walt Disney Between the motion and the act falls the shadow. Thomas Stearns Eliot, The Hollow Men #BREAK# Chapter 18 Multimedia: Images, Animation, Audio and Video 1069 Outline 18.1 Introduction 18.2 Loading, Displaying and Scaling Images 18.3 Animating a Series of Images 18.4 Customizing LogoAnimator via Applet Parameters 18.5 Image Maps 18.6 Loading and Playing Audio Clips 18.7 Internet and World Wide Web Resources Summary Terminology Self-Review Exercises Answers to Self-Review Exercises Exercises 18.1 Introduction Welcome to what may be the largest revolution in the history of the computer industry. Those of us who entered the field decades ago were interested in using computers primarily to perform arithmetic calculations at high speed. As the computer field evolves, we are beginning to realize that the data-manipulation capabilities of computers are now equally important. The sizzle of Java is multimedia, the use of sound, images, graphics and video to make applications come alive. Today, many people consider two-dimensional color video to be the ultimate in multimedia. Within the decade, we expect all kinds of exciting new three-dimensional applications. Java programmers already can use the Java3D API to create substantial 3D graphics applications. We discuss the Java3D API in our book Advanced Java 2 Platform How to Program. Multimedia programming offers many new challenges. The field is already enormous and will grow rapidly. People are rushing to equip their computers for multimedia. Most new computers sold today are multimedia ready, with CD or DVD drives, audio boards and sometimes with special video capabilities. Among users who want graphics, two-dimensional graphics no longer suffice. Many people now want three-dimensional, high-resolution, color graphics. True three-dimensional imaging may become available within the next decade. Imagine having ultra-high-resolution, theater-in-the-round, three-dimensional television. Sporting and entertainment events will seem to take place on your living room floor! Medical students worldwide will see operations being performed thousands of miles away, as if they were occurring in the same room. People will be able to learn how to drive with extremely realistic driving simulators in their homes before they get behind the wheel. The possibilities are exciting and endless. Multimedia demands extraordinary computing power. Until recently, affordable computers with this kind of power were not available. Today s ultrafast processors, like the SPARC Ultra from Sun Microsystems, the Pentium and Itanium from Intel, the Alpha from Compaq Computer Corporation and the processors from MIPS/Silicon Graphics (among others) make effective multimedia possible. The computer and communications industries will be primary beneficiaries of the multimedia revolution. Users will be willing to pay for the faster processors, larger memories and wider communications bandwidths that support demanding multimedia applications. Ironically, users may not have to pay more as fierce competition in these industries drives prices down. #BREAK# 1070 Multimedia: Images, Animation, Audio and Video Chapter 18 We need programming languages that make creating multimedia applications easy. Most programming languages do not have built-in multimedia capabilities. However, Java provides extensive multimedia facilities that enable you to start developing powerful multimedia applications immediately. This chapter presents a series of live-code examples that cover several interesting multimedia features you will need to build useful applications, including: 1. the basics of manipulating images 2. creating smooth animations 3. customizing an animation applet via parameters supplied from the HTML file that invokes an applet 4. playing audio files with the AudioClipinterface 5. creating image maps that can sense when the cursor is over them even without a mouse click We will continue our coverage of Java s multimedia capabilities in Chapter 22, where we discuss the Java Media Framework (JMF) and the Java Sound APIs. JMF and Java Sound enable Java programs to play and record audio and video. The JMF even enables Java programs to send audio and video streams so-called streaming media across a network or the Internet. The chapter exercises for this chapter and Chapter 22 suggest dozens of challenging and interesting projects and even mention some million-dollar ideas that could help you make your fortune! When we were creating these exercises, it seemed that the ideas just kept flowing. Multimedia seems to leverage creativity in ways that we have not experienced with conventional computer capabilities. 18.2 Loading, Displaying and Scaling Images Java s multimedia capabilities include graphics, images, animations, sounds and video. We begin our multimedia discussion with images. The applet of Fig. 18.1 demonstrates loading an Image (package java.awt) and loading an ImageIcon (package javax.swing). The applet displays the Imagein its original size and scaled to a larger size, using two versions of Graphicsmethod draw- Image. The applet also draws the ImageIcon using its method paintIcon. Class ImageIcon is easier than Image to use, because its constructor can receive arguments of several different formats, including a byte array containing the bytes of an image, an Imagealready loaded in memory, a Stringrepresenting the location of an image and a URLrepresenting the location of an image. 1 // Fig. 18.1: LoadImageAndScale.java 2 // Load an image and display it in its original size 3 // and scale it to twice its original width and height. 4 // Load and display the same image as an ImageIcon. 6 // Java core packages 7 import java.applet.Applet; 8 import java.awt.*; Fig. 18.1Loading and displaying an image in an applet (part 1 of 2). Fig. 18.1 #BREAK# Chapter 18 Multimedia: Images, Animation, Audio and Video 1071 9 10 // Java extension packages 11 import javax.swing.*; 12 13 public class LoadImageAndScale extends JApplet { 14 private Image logo1; 15 private ImageIcon logo2; 16 17 // load image when applet is loaded 18 public void init() 19 { 20 logo1 = getImage( getDocumentBase(), “logo.gif” ); 21 logo2 = new ImageIcon( “logo.gif” ); 22 } 23 24 // display image 25 public void paint( Graphics g ) 26 { 27 // draw original image 28 g.drawImage( logo1, 0, 0, this ); 29 30 // draw image scaled to fit width of applet 31 // and height of applet minus 120 pixels 32 g.drawImage( logo1, 0, 120, 33 getWidth(), getHeight() -120, this ); 34 35 // draw icon using its paintIcon method 36 logo2.paintIcon( this, g, 180, 0 ); 37 } 38 39 } // end class LoadImageAndScale Fig. 18.1Loading and displaying an image in an applet (part 2 of 2). Fig. 18.1 #BREAK# 1072 Multimedia: Images, Animation, Audio and Video Chapter 18 Lines 14 and 15 declare an Image reference and an ImageIcon reference, respectively. Class Image is an abstract class; therefore, the applet cannot create an object of class Imagedirectly. Rather, the applet must call a method that causes the applet container to load and return the Image for use in the program. Class Applet (the superclass of JApplet) provides a method that does just that. Line 20 in method init uses Applet method getImage to load an Image into the applet. This version of getImage takes two arguments the location of the image file and the file name of the image. In the first argument, Applet method getDocumentBase returns a URL representing the location of the image on the Internet (or on your computer if the applet was loaded from your computer). The program assumes that the image is stored in the same directory as the HTML file that invoked the applet. Method getDocumentBase returns the location of the HTML file on the Internet as an object of class URL. The second argument specifies an image file name. Java supports several image formats, including Graphics Interchange Format (GIF), Joint Photographic Experts Group (JPEG) and Portable Network Graphics (PNG). File names for each of these types end with .gif, .jpg (or .jpeg) and .png, respectively. Portability Tip 18.1 Class Image is an abstract class and, as a result, programs cannot instantiate Image objects. To achieve platform independence, the Java implementation on each platform provides its own subclass of Image to store image information. When line 20 invokes method getImageto set up loading of the image from the local computer (or downloading of the image from the Internet). When the image is required by the program, the image is loaded in a separate thread of execution. This enables the program to continue execution while the image loads. [Note: If the requested file is not available, method getImage does not indicate an error.] Class ImageIcon is not an abstract class; therefore, a program can create an ImageIcon object. Line 21 in method init creates an ImageIcon object that loads the same logo.gif image. Class ImageIconprovides several constructors that enable programs to initialize ImageIcon objects with images from the local computer or with images stored on the Internet. The applet s paint method (lines 25 37) displays the images. Line 28 uses Graphics method drawImageto display an Image. Method drawImage receives four arguments. The first argument is a reference to the Image object to display (logo1). The second and third arguments are the x- and y-coordinates at which to display the image on the applet; the coordinates indicate the upper-left corner of the image. The last argument is a reference to an ImageObserver object. Normally, the ImageObserveris the object on which the program displays the image. An ImageObserver can be any object that implements interface ImageObserver. Class Component (one of class Applet s indirect superclasses) implements interface ImageObserver. Therefore, all Components (including our applet) are ImageObservers. The ImageObserver argument is important when displaying large images that require a long time to download from the Internet. It is possible that a program will execute the code that displays the image before the image downloads completely. The ImageObserver is notified to update the displayed image as the remainder of the image loads. When executing this applet, watch carefully as pieces of the image display while the image loads. [Note: On faster computers, you might not notice this effect.] Lines 32 33 use another version of Graphics method drawImage to output a scaled version of the image. The fourth and fifth arguments specify the width and height of #BREAK# Chapter 18 Multimedia: Images, Animation, Audio and Video 1073 the image for display purposes. Method drawImage scales the image to fit the specified width and height. In this example, the fourth argument indicates that the width of the scaled image should be the width of the applet, and the fifth argument indicates that the height should be 120 pixels less than the height of the applet. Line 33 determines the width and height of the applet by calling methods getWidthand getHeight(inherited from class Component). Line 36 uses ImageIcon method paintIcon to display the image. The method requires four arguments a reference to the Componenton which to display the image, a reference to the Graphicsobject that will render the image, the x-coordinate of the upper- left corner of the image and the y-coordinate of the upper-left corner of the image. If you compare the two techniques for loading and displaying images in this example, you can see that using ImageIcon is simpler. You can create objects of class Image- Icon directly, and there is no need to use an ImageObserver reference when displaying the image. For this reason, we use class ImageIcon for the remainder of the chapter. [Note: Class ImageIcon s paintIcon method does not allow scaling of an image. However, the class provides method getImage, which returns an Image reference that Graphicsmethod drawImagecan use to display a scaled image.] 18.3 Animating a Series of Images The next example demonstrates animating a series of images that are stored in an array. The application uses the same techniques to load and display ImageIcons as shown in Fig. 18.1. The animation presented in Fig. 18.2 is designed as a subclass of JPanel(called LogoAnimator) that can be attached to an application window or possibly to a JApplet. Class LogoAnimator also defines a main method (defined at lines 96 117) to execute the animation as an application. Method main defines an instance of class JFrameand attaches a LogoAnimatorobject to the JFrameto display the animation. 1 // Fig. 18.2: LogoAnimator.java 2 // Animation a series of images 3 4 // Java core packages 5 import java.awt.*; 6 import java.awt.event.*; 7 8 // Java extension packages 9 import javax.swing.*; 10 11 public class LogoAnimator extends JPanel 12 implements ActionListener { 13 14 protected ImageIcon images[]; // array of images 15 16 protected int totalImages = 30, // number of images 17 currentImage = 0, // current image index 18 animationDelay = 50, // millisecond delay 19 width, // image width 20 height; // image height Fig. 18.2Animating a series of images (part 1 of 3). Fig. 18.2 #BREAK# 1074 Multimedia: Images, Animation, Audio and Video Chapter 18 21 22 protected String imageName = “deitel”; // base image name 23 protected Timer animationTimer; // Timer drives animation 24 25 // initialize LogoAnimator by loading images 26 public LogoAnimator() 27 { 28 initializeAnimation(); 29 } 30 31 // initialize animation 32 protected void initializeAnimation() 33 { 34 images = new ImageIcon[ totalImages ]; 35 36 // load images 37 for ( int count = 0; count < images.length; ++count ) 38 images[ count ] = new ImageIcon( getClass().getResource( 39 "images/" + imageName + count + ".gif" ) ); 40 41 width = images[ 0 ].getIconWidth(); // get icon width 42 height = images[ 0 ].getIconHeight(); // get icon height 43 } 44 45 // display current image 46 public void paintComponent( Graphics g ) 47 { 48 super.paintComponent( g ); 49 50 images[ currentImage ].paintIcon( this, g, 0, 0 ); 51 currentImage = ( currentImage + 1 ) % totalImages; 52 } 53 54 // respond to Timer's event 55 public void actionPerformed( ActionEvent actionEvent ) 56 { 57 repaint(); // repaint animator 58 } 59 60 // start or restart animation 61 public void startAnimation() 62 { 63 if ( animationTimer == null ) { 64 currentImage = 0; 65 animationTimer = new Timer( animationDelay, this ); 66 animationTimer.start(); 67 } 68 else // continue from last image displayed 69 if ( ! animationTimer.isRunning() ) 70 animationTimer.restart(); 71 } 72 Fig. 18.2Fig. 18.2 Animating a series of images (part 2 of 3). #BREAK# Chapter 18 Multimedia: Images, Animation, Audio and Video 1075 73 // stop animation timer 74 public void stopAnimation() 75 { 76 animationTimer.stop(); 77 } 78 79 // return minimum size of animation 80 public Dimension getMinimumSize() 81 { 82 return getPreferredSize(); 83 } 84 85 // return preferred size of animation 86 public Dimension getPreferredSize() 87 { 88 return new Dimension( width, height ); 89 } 90 91 // execute animation in a JFrame 92 public static void main( String args[] ) 93 { 94 // create LogoAnimator 95 LogoAnimator animation = new LogoAnimator(); 96 97 // set up window 98 JFrame window = new JFrame( "Animator test" ); 99 100 Container container = window.getContentPane(); 101 container.add( animation ); 102 103 window.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE ); 104 105 // size and display window 106 window.pack(); 107 Insets insets = window.getInsets(); 108 109 window.setSize( animation.getPreferredSize().width + 110 insets.left + insets.right, 111 animation.getPreferredSize().height + 112 insets.top + insets.bottom ); 113 114 window.setVisible( true ); 115 animation.startAnimation(); // begin animation 116 117 } // end method main 118 119 } // end class LogoAnimator Fig. 18.2Animating a series of images (part 3 of 3). Fig. 18.2 #BREAK# 1076 Multimedia: Images, Animation, Audio and Video Chapter 18 Class LogoAnimator maintains an array of ImageIcons that are loaded in method initializeAnimation (lines 32 43), which is called from the constructor. As the for structure (lines 37 39) creates each ImageIcon object, the ImageIcon constructor loads one of the animation s 30 images. The constructor argument uses String concatenation to assemble the file name from the pieces "images/", imageName, count and ".gif". Each of the images in the animation is in a file called deitel#.gif, where # is a value in the range 0 29 specified by the loop s control variable count. Lines 41 42 determine the width and height of the animation from the size of the first image in array images. Performance Tip 18.1 It is more efficient to load the frames of the animation as one image than to load each image separately. (A painting program can be used to combine the frames of the animation into one image. If the images are being loaded from the Web, every image loaded requires a separate connection to the site containing the images. Performance Tip 18.2 Loading all the frames of an animation as one large image can force your program to wait to begin displaying the animation. After the LogoAnimator constructor loads the images, method main sets up the window in which the animation will appear and calls startAnimation (defined at lines 61 71) to begin the animation. The animation is driven by an instance of class Timer (package javax.swing). A Timer generates ActionEvents at a fixed interval in milliseconds (normally specified as an argument to the Timer s constructor) and notifies all of its ActionListeners that the event occurred. Lines 63 67 determine whether the Timer reference animationTimeris null. If so, line 64 sets currentImage to 0, to indicate that the animation should begin with the image in the first element of array images. Line 65 assigns a new Timer object to animationTimer. The Timer constructor receives two arguments the delay in milliseconds (animationDelay is 50 in this example) and the ActionListener that will respond to the Timer s ActionEvents. Class Logo- Animator implements ActionListenerso line 65 specifies this as the listener. Line 66 starts the Timer object. Once started, animationTimer will generate an Action- Event every 50 milliseconds. Lines 69 70 enable a program to restart an animation that the program stopped previously. For example, to make an animation browser friendly in an applet, the animation should stop when the user switches Web pages. If the user returns to the Web page with the animation, method startAnimation can be called to restart the animation. The if condition at line 73 uses Timer method isRunning to determine whether the Timer is running (i.e., generating events). If it is not running, line 70 calls Timer method restart to indicate that the Timershould start generating events again. In response to every Timer event in this example, the program calls method actionPerformed (lines 55 58). Line 57 calls LogoAnimator s repaint method to schedule a call to the LogoAnimator s update method (inherited from class JPanel) which, in turn, calls LogoAnimator s paintComponent method (lines 46 52). Remember that any subclass of JComponent that performs drawing should do so in its paintComponent method. As mentioned in Chapter 13, the first statement in any paintComponent method should be a call to the superclass s paintComponent method to ensure that Swing components are displayed correctly. #BREAK# Chapter 18 Multimedia: Images, Animation, Audio and Video 1077 Lines 50 51 paint the ImageIcon at element currentImagein the array and prepare for the next image to be displayed by incrementing currentImage by 1. Notice the modulus calculation to ensure that the value of currentImageis set to 0 when it is incremented past 29 (the last element subscript in the array). Method stopAnimation (lines 74 77) stops the animation with line 76, which calls Timer method stop to indicate that the Timershould stop generating events. This prevents actionPerformed from calling repaint to initiate the painting of the next image in the array. Software Engineering Observation 18.1 When creating an animation for use in an applet, provide a mechanism for disabling the animation when the user browses a new Web page separate from the page on which the animation applet resides. Methods getMinimumSize (lines 80 83) and getPreferredSize (lines 86 89) override the corresponding methods inherited from class Component and enable layout managers to determine the appropriate size of a LogoAnimator in a layout. In this example, the images are 160 pixels wide and 80 pixels tall, so method getPreferred- Size returns a Dimension object containing the numbers 160 and 80. Method get- MinimumSize simply calls getPreferredSize (a common programming practice). Lines 107 112 of main size the application window based on the LogoAnimator s preferred size and the window s insets. The insets specify the number of pixels for the window s top, bottom, left and right borders. Using the insets of the window ensures that the window s client area is large enough to display the LogoAnimatorcorrectly. The client area is the part of a window in which the window displays GUI components. The program obtains the window s insets by calling method getInsets at line 107. The method returns an Insets object that contains public data members top, bottom, left and right. 18.4 Customizing LogoAnimator via Applet Parameters When browsing the World Wide Web, you often will come across applets that are in the public domain you can use them free of charge on your own Web pages (normally in exchange for crediting the applet s creator). One common feature of such applets is the ability to be customized via parameters that are supplied from the HTML file that invokes the applet. For example, the HTML from file LogoApplet.html, invokes the applet LogoApplet (Fig. 18.4) and specifies three parameters. The param tag lines must appear between the starting and ending applet tags. Each parameter has a name and a value. Applet method getParameter returns a String representing the value associated with a specific parameter name. The argument to getParameter is a String containing the name of the parameter in the param tag. For example, the statement #BREAK# 1078 Multimedia: Images, Animation, Audio and Video Chapter 18 parameter = getParameter( “animationdelay” ); gets the value 200 associated with parameter animationdelay and assigns it to String reference parameter. If there is not a param tag containing the specified parameter name, getParameterreturns null. Class LogoAnimator2(Fig. 18.3) extends class LogoAnimatorand adds a constructor that takes three arguments the total number of images in the animation, the delay between displaying images and the base image name. Class LogoApplet (Fig. 18.4) allows Web page designers to customize the animation to use their own images. Three parameters are provided in the applet s HTML document. Parameter animationdelay is the number of milliseconds to sleep between displaying images. This value will be converted to an integer and used as the value for instance variable sleepTime. Parameter imagename is the base name of the images to be loaded. This Stringwill be assigned to instance variable imageName. The applet assumes that the images are in a subdirectory named images that can be found in the same directory as the applet. The applet also assumes that the image file names are numbered from 0 (as in Fig. 18.2). Parameter totalimages represents the total number of images in the animation. Its value will be converted to an integer and assigned to instance variable totalImages. 1 // Fig. 18.3: LogoAnimator2.java 2 // Animating a series of images 3 4 // Java core packages 5 import java.awt.*; 6 7 // Java extension packages 8 import javax.swing.*; 9 10 public class LogoAnimator2 extends LogoAnimator { 11 12 // default constructor 13 public LogoAnimator2() 14 { 15 super(); 16 } 17 18 // new constructor to support customization 19 public LogoAnimator2( int count, int delay, String name ) 20 { 21 totalImages = count; 22 animationDelay = delay; 23 imageName = name; 24 25 initializeAnimation(); 26 } 27 Fig. 18.3 LogoAnimator2subclass of LogoAnimator(Fig. 18.2) adds a constructor for customizing the number of images, animation delay and base image name (part 1 of 2). #BREAK# Chapter 18 Multimedia: Images, Animation, Audio and Video 1079 28 // start animation as application in its own window 29 public static void main( String args[] ) 30 { 31 // create LogoAnimator 32 LogoAnimator2 animation = new LogoAnimator2(); 33 34 // set up window 35 JFrame window = new JFrame( “Animator test” ); 36 37 Container container = window.getContentPane(); 38 container.add( animation ); 39 40 window.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE ); 41 42 // size and display window 43 window.pack(); 44 Insets insets = window.getInsets(); 45 46 window.setSize( animation.getPreferredSize().width + 47 insets.left + insets.right, 48 animation.getPreferredSize().height + 49 insets.top + insets.bottom ); 50 51 window.setVisible( true ); 52 animation.startAnimation(); // begin animation 53 54 } // end method main 55 56 } // end class LogoAnimator2 Fig. 18.3 LogoAnimator2subclass of LogoAnimator(Fig. 18.2) adds a constructor for customizing the number of images, animation delay and base image name (part 2 of 2). 1 // Fig. 18.4: LogoApplet.java 2 // Customizing an applet via HTML parameters. 3 // 4 // HTML parameter “animationdelay” is an int indicating 5 // milliseconds to sleep between images (default 50). 6 // 7 // HTML parameter “imagename” is the base name of the images 8 // that will be displayed (i.e., “deitel” is the base name 9 // for images “deitel0.gif,” “deitel1.gif,” etc.). The applet 10 // assumes that images are in an “images” subdirectory of 11 // the directory in which the applet resides. 12 // 13 // HTML parameter “totalimages” is an integer representing the 14 // total number of images in the animation. The applet assumes 15 // images are numbered from 0 to totalimages - 1 (default 30). 17 // Java core packages 18 import java.awt.*; Fig. 18.4Customizing an animation applet via the paramHTML tag (part 1 of 2). Fig. 18.4 #BREAK# 1080 Multimedia: Images, Animation, Audio and Video Chapter 18 19 20 // Java extension packages 21 import javax.swing.*; 22 23 public class LogoApplet extends JApplet { 24 25 // obtain parameters from HTML and customize applet 26 public void init() 27 { 28 String parameter; 29 30 // get animation delay from HTML document 31 parameter = getParameter( “animationdelay” ); 32 33 int animationDelay = ( parameter == null ? 34 50 : Integer.parseInt( parameter ) ); 35 36 // get base image name from HTML document 37 String imageName = getParameter( “imagename” ); 38 39 // get total number of images from HTML document 40 parameter = getParameter( “totalimages” ); 41 42 int totalImages = ( parameter == null ? 43 0 : Integer.parseInt( parameter ) ); 44 45 // create instance of LogoAnimator 46 LogoAnimator2 animator; 47 48 if ( imageName == null || totalImages == 0 ) 49 animator = new LogoAnimator2(); 50 else 51 animator = new LogoAnimator2( totalImages, 52 animationDelay, imageName ); 53 54 // attach animator to applet and start animation 55 getContentPane().add( animator ); 56 animator.startAnimation(); 57 58 } // end method init 59 60 } // end class LogoApplet Fig. 18.4Customizing an animation applet via the paramHTML tag (part 2 of 2). Fig. 18.4 #BREAK# Chapter 18 Multimedia: Images, Animation, Audio and Video 1081 Class LogoApplet (Fig. 18.4) defines an init method in which the three HTML parameters are read with Applet method getParameter (lines 31, 37 and 40). After the applet reads these parameters and the two integer parameters are converted to int values, the if/elsestructure at lines 48 through 51 creates a LogoAnimator2and calls its three-argument constructor. If the imageName is null or totalImages is 0, the applet calls the default LogoAnimator2constructor and uses the default animation. Otherwise, the applet passes totalImages, animationDelay and imageName to the three-argument LogoAnimator2 constructor, and the constructor uses those arguments to customize the animation. LogoAnimator2 s three-argument constructor invokes LogoAnimator s initalizeAnimation method to load the images and determine the width and height of the animation. 18.5 Image Maps Image maps are a common technique used to create interactive Web pages. An image map is an image that has hot areas that the user can click to accomplish a task, such as loading a different Web page into a browser. When the user positions the mouse pointer over a hot area, normally a descriptive message appears in the status area of the browser or in a popup window. Figure 18.5 loads an image containing several of the common tip icons used throughout this book. The program allows the user to position the mouse pointer over an icon and display a descriptive message for the icon. Event handler mouseMoved (lines 42 46) takes the mouse coordinates and passes them to method translateLocation (lines 63 76). Method translateLocationtests the coordinates to determine the icon over which the mouse was positioned when the mouseMoved event occurred. Method translateLocationthen returns a message indicating what the icon represents. This message is displayed in the appletviewer s (or browser s) status bar. 1 // Fig. 18.5: ImageMap.java 2 // Demonstrating an image map. 3 4 // Java core packages 5 import java.awt.*; 6 import java.awt.event.*; 7 8 // Java extension packages 9 import javax.swing.*; 10 11 public class ImageMap extends JApplet { 12 private ImageIcon mapImage; 13 14 private String captions[] = { “Common Programming Error”, 15 “Good Programming Practice”, 16 “Graphical User Interface Tip”, “Performance Tip”, 17 “Portability Tip”, “Software Engineering Observation”, 18 “Testing and Debugging Tip” }; 19 Fig. 18.5Demonstrating an image map (part 1 of 4). Fig. 18.5 #BREAK# 1082 Multimedia: Images, Animation, Audio and Video Chapter 18 20 // set up mouse listeners 21 public void init() 22 { 23 addMouseListener( 24 25 new MouseAdapter() { 26 27 // indicate when mouse pointer exits applet area 28 public void mouseExited( MouseEvent event ) 29 { 30 showStatus( “Pointer outside applet” ); 31 } 32 33 } // end anonymous inner class 34 35 ); // end addMouseListener method call 36 37 addMouseMotionListener( 38 39 new MouseMotionAdapter() { 40 41 // determine icon over which mouse appears 42 public void mouseMoved( MouseEvent event ) 43 { 44 showStatus( translateLocation( 45 event.getX(), event.getY() ) ); 46 } 47 48 } // end anonymous inner class 49 50 ); // end addMouseMotionListener method call 51 52 mapImage = new ImageIcon( “icons.png” ); 53 54 } // end method init 55 56 // display mapImage 57 public void paint( Graphics g ) 58 { 59 mapImage.paintIcon( this, g, 0, 0 ); 60 } 61 62 // return tip caption based on mouse coordinates 63 public String translateLocation( int x, int y ) 64 { 65 // if coordinates outside image, return immediately 66 if ( x >= mapImage.getIconWidth() || 67 y >= mapImage.getIconHeight() ) 68 return “”; 69 70 // determine icon number (0 - 6) 71 int iconWidth = mapImage.getIconWidth() / 7; 72 int iconNumber = x / iconWidth; Fig. 18.5Fig. 18.5 Demonstrating an image map (part 2 of 4). #BREAK# Chapter 18 Multimedia: Images, Animation, Audio and Video 1083 73 74 // return appropriate icon caption 75 return captions[ iconNumber ]; 76 } 77 78 } // end class ImageMap Fig. 18.5Demonstrating an image map (part 3 of 4). Fig. 18.5 #BREAK# 1084 Multimedia: Images, Animation, Audio and Video Chapter 18 Fig. 18.5Demonstrating an image map (part 4 of 4). Fig. 18.5 Clicking in this applet will not cause any action. In Chapter 17, Networking, we discussed the techniques required to load another Web page into a browser via URLs and the AppletContextinterface. Using those techniques, this applet could associate each icon with a URLthat the browser would display when the user clicks the icon. 18.6 Loading and Playing Audio Clips Java programs can manipulate and play audio clips. It is easy for users to capture their own audio clips, and there are many clips available in software products and over the Internet. Your system needs to be equipped with audio hardware (speakers and a sound board) to be able to play the audio clips. Java provides several mechanisms for playing sounds in an applet. The two simplest methods are the Applet s play method and the play method from the AudioClip interface. Additional audio capabilities are discussed in Chapter 22. If you would like to play a sound once in a program, the Applet method play loads the sound and plays it once; the sound is marked for garbage collection after it plays. The Appletmethod play has two forms: public void play( URL location, String soundFileName ); public void play( URL soundURL ); The first version loads the audio clip stored in file soundFileName from location and plays the sound. The first argument is normally a call to the applet s getDocument #BREAK# Chapter 18 Multimedia: Images, Animation, Audio and Video 1085 Baseor getCodeBase method. Method getDocumentBaseindicates the location of the HTML file that loaded the applet (if the applet is in a package, this indicates the location of the package or JAR file containing the package). Method getCodeBaseindicates the location of the applet s .classfile. The second version of method playtakes a URLthat contains the location and the file name of the audio clip. The statement play( getDocumentBase(), “hi.au” ); loads the audio clip in file hi.auand plays it once. The sound engine that plays the audio clips supports several audio file formats, including Sun Audio file format (.au extension), Windows Wave file format (.wav extension), Macintosh AIFF file format (.aif or .aiff extension) and Musical Instrument Digital Interface (MIDI) file format (.mid or .rmi extensions). The Java Media Framework (JMF) and Java Sound APIs support additional formats. The program of Fig. 18.6 demonstrates loading and playing an AudioClip (package java.applet). This technique is more flexible than Appletmethod play. An applet can use an AudioClip to store audio for repeated use throughout the program s execution. Appletmethod getAudioClip has two forms that take the same arguments as method play described previously. Method getAudioClip returns a reference to an Audio- Clip. An AudioCliphas three methods play, loop and stop. Method playplays the audio once. Method loopcontinuously loops the audio clip in the background. Method stop terminates an audio clip that is currently playing. In the program, each of these methods is associated with a button on the applet. Lines 62 63 in the applet s init method use getAudioClip to load two audio files a Windows Wave file (welcome.wav) and a Sun Audio file (hi.au). The user can select which audio clip to play from JComboBox chooseSound. Notice that the applet s stop method is overridden at lines 69 72. When the user switches Web pages, the applet container calls the applet s stopmethod. This enables the applet to stop playing the audio clip. Otherwise, the audio clip continues to play in the background even if the applet is not displayed in the browser. This is not really a problem, but it can be annoying to the user if the audio clip is looping. The stopmethod is provided here as a convenience to the user. Good Programming Practice 18.1 When playing audio clips in an applet or application, provide a mechanism for the user to disable the audio. 1 // Fig. 18.6: LoadAudioAndPlay.java 2 // Load an audio clip and play it. 3 4 // Java core packages 5 import java.applet.*; 6 import java.awt.*; 7 import java.awt.event.*; 8 9 // Java extension packages 10 import javax.swing.*; Fig. 18.6 Loading and playing an AudioClip(part 1 of 3). #BREAK# 1086 Multimedia: Images, Animation, Audio and Video Chapter 18 11 12 public class LoadAudioAndPlay extends JApplet { 13 private AudioClip sound1, sound2, currentSound; 14 private JButton playSound, loopSound, stopSound; 15 private JComboBox chooseSound; 16 17 // load the image when the applet begins executing 18 public void init() 19 { 20 Container container = getContentPane(); 21 container.setLayout( new FlowLayout() ); 22 23 String choices[] = { “Welcome”, “Hi” }; 24 chooseSound = new JComboBox( choices ); 25 26 chooseSound.addItemListener( 27 28 new ItemListener() { 29 30 // stop sound and change to sound to user’s selection 31 public void itemStateChanged( ItemEvent e ) 32 { 33 currentSound.stop(); 34 35 currentSound = 36 chooseSound.getSelectedIndex() == 0 ? 37 sound1 : sound2; 38 } 39 40 } // end anonymous inner class 41 42 ); // end addItemListener method call 43 44 container.add( chooseSound ); 45 46 // set up button event handler and buttons 47 ButtonHandler handler = new ButtonHandler(); 48 49 playSound = new JButton( “Play” ); 50 playSound.addActionListener( handler ); 51 container.add( playSound ); 52 53 loopSound = new JButton( “Loop” ); 54 loopSound.addActionListener( handler ); 55 container.add( loopSound ); 56 57 stopSound = new JButton( “Stop” ); 58 stopSound.addActionListener( handler ); 59 container.add( stopSound ); 60 61 // load sounds and set currentSound 62 sound1 = getAudioClip( getDocumentBase(), “welcome.wav” ); 63 sound2 = getAudioClip( getDocumentBase(), “hi.au” ); Fig. 18.6 Loading and playing an AudioClip(part 2 of 3). #BREAK# Chapter 18 Multimedia: Images, Animation, Audio and Video 1087 64 currentSound = sound1; 65 66 } // end method init 67 68 // stop the sound when the user switches Web pages 69 public void stop() 70 { 71 currentSound.stop(); 72 } 73 74 // private inner class to handle button events 75 private class ButtonHandler implements ActionListener { 76 77 // process play, loop and stop button events 78 public void actionPerformed( ActionEvent actionEvent ) 79 { 80 if ( actionEvent.getSource() == playSound ) 81 currentSound.play(); 82 83 else if ( actionEvent.getSource() == loopSound ) 84 currentSound.loop(); 85 86 else if ( actionEvent.getSource() == stopSound ) 87 currentSound.stop(); 88 } 89 } 90 91 } // end class LoadAudioAndPlay Fig. 18.6 Loading and playing an AudioClip(part 3 of 3). 18.7 Internet and World Wide Web Resources This section presents several Internet and Web resources for multimedia-related sites. (Additional resources are provided in Chapter 22.) www.nasa.gov/gallery/index.html The NASA multimedia gallery contains a wide variety of images, audio clips and video clips that you can download and use to test your Java multimedia programs. sunsite.sut.ac.jp/multimed/ The Sunsite Japan Multimedia Collection also provides a wide variety of images, audio clips and video clips that you can download for educational purposes. www.anbg.gov.au/anbg/index.html The Australian National Botanic Gardens Web site provides links to sounds of many animals. Try, for example, the Common Birds link. #BREAK# 1088 Multimedia: Images, Animation, Audio and Video Chapter 18 www.thefreesite.com TheFreeSite.com has links to free sounds and clip art. www.soundcentral.com SoundCentral provides audio clips in WAV, AU, AIFF and MIDI formats. www.animationfactory.com The Animation Factory provides thousands of free GIF animations for personal use. www.clipart.com ClipArt.com contains links to Web sites that provide free art. www.pngart.com PNGART.com provides over 50,000 free images in PNG format, in an effort to help this newer image format gain popularity. developer.java.sun.com/developer/techDocs/hi/repository The Java Look-and-Feel Graphics Repository provides standard images for use in a Swing GUI. SUMMARY Applet method getImage loads an Image. One version of getImage takes two arguments a location where the image is stored, and the file name of the image. Applet method getDocumentBase returns the location of the applet s HTML file on the Internet as an object of class URL (package java.net). Java supports several image formats, including Graphics Interchange Format (GIF), Joint Photographic Experts Group (JPEG) and Portable Network Graphics (PNG). File names for each of these types end with .gif, .jpg(or .jpeg) or .png, respectively. Class ImageIcon provides constructors that allow an ImageIconobject to be initialized with an image from the local computer or with an image stored on a Web server on the Internet. Graphics method drawImage receives four arguments a reference to the Image object in which the image is stored, the x-and y-coordinates where the image should be displayed and a reference to an ImageObserverobject. Another version of Graphics method drawImage outputs a scaled image. The fourth and fifth arguments specify the width and height of the image for display purposes. Interface ImageObserver is implemented by class Component(an indirect superclass of Applet). ImageObservers are notified to update an image that was displayed as the remainder of the image is loaded. ImageIcon method paintIcon displays the ImageIcon s image. The method requires four arguments a reference to the Component on which the image will be displayed, a reference to the Graphics object used to render the image, the x-coordinate of the upper-left corner of the image and the y-coordinate of the upper-left corner of the image. Class ImageIcon s paintIconmethod does not allow scaling of an image. The class provides method getImage, which returns an Image reference that can be used with Graphicsmethod drawImageto display a scaled version of an image. Timer objects generate ActionEvents at fixed intervals in milliseconds and notify their registered ActionListeners that the events occurred. The Timerconstructor receives two arguments the delay in milliseconds and the ActionListener. Timermethod start indicates that the Timer should start generating events. Timer method stop indicates that the Timer should stop generating events. Timer method restart indicates that the Timer should start generating events again. #BREAK# Chapter 18 Multimedia: Images, Animation, Audio and Video 1089 Applets can be customized via parameters (thetag) that are supplied from the HTML file that invokes the applet. The
tag lines must appear between the starting applet tag and the ending applet tag. Each parameter has a name and a value. Applet method getParameter gets the value associated with a specific parameter and returns the value as a String. The argument passed to getParameter is a String containing the name of the parameter in the param tag. If there is no param tag containing the specified parameter, getParameter returns null. An image map is an image that has hot areas that the user can click to accomplish a task, such as loading a different Web page into a browser. Applet method play has two forms: public void play( URL location, String soundFileName ); public void play( URL soundURL ); One version loads the audio clip stored in file soundFileName from location and plays the sound; the other takes a URLthat contains the location and the file name of the audio clip. Applet method getDocumentBase indicates the location of the HTML file that loaded the applet. Method getCodeBaseindicates where the .class file for an applet is located. The sound engine that plays audio clips supports several audio file formats, including Sun Audio file format (.au extension), Windows Wave file format (.wav extension), Macintosh AIFF file format (.aifor .aiff extension) and Musical Instrument Digital Interface (MIDI) file format (.midor .rmi extensions). The Java Media Framework (JMF) supports other additional formats. Applet method getAudioClip has two forms that take the same arguments as the play method. Method getAudioClip returns a reference to an AudioClip. AudioClips have three methods play, loop and stop. Method play plays the audio once. Method loopcontinuously loops the audio clip. Method stop terminates an audio clip that is currently playing. TERMINOLOGY .aiffile name extension .aiff file name extension .au file name extension .giffile name extension .jpeg file name extension .jpgfile name extension .mid file name extension .rmifile name extension .wavfile name extension animating a series of images animation audio clip customize an applet drawImagemethod of Graphics getAudioClip method of Applet getCodeBase method of Applet getDocumentBase method of Applet getHeightmethod of Component getIconHeightmethod of ImageIcon getIconWidth method of ImageIcon getImage method of Applet getImage method of ImageIcon getParameter method of Applet getWidth method of Component graphics Graphics Interchange Format (GIF) height of an image hot area of an image map Image class image map ImageIcon class ImageObserver interface images information button Joint Photographic Experts Group (JPEG) loop method of interface AudioClip Macintosh AIFF file (.aifor .aiff) multimedia Musical Instrument Digital Interface (MIDI) mute button name attribute of param tag paintIcon method of class ImageIcon #BREAK# 1090 Multimedia: Images, Animation, Audio and Video Chapter 18 paramtag stop method of class Timer play method of class Applet stop method of interface AudioClip play method of interface AudioClip Sun Audio file format (.au) restart method of class Timer Timer class scaling an image update method of class Component sound value attribute of param tag sound engine volume control space/time trade-off width of an image start method of class Timer Windows Wave file (.wav) SELF-REVIEW EXERCISES 18.1 Fill in the blanks in each of the following statements: a) Appletmethod loads an image into an applet. b) Applet method returns as an object of class URL the location on the Inter net of the HTML file that invoked the applet. c) Graphicsmethod displays an image on an applet. d) Java provides two mechanisms for playing sounds in an applet the Applet s play method and the playmethod from the interface. e) An is an image that has hot areas that the user can click to accomplish a task such as loading a different Web page. f) Method of class ImageIcon displays the ImageIcon s image. g) Java supports several image formats, including , and . 18.2 State whether each of the following is true or false. If false, explain why. a) A sound will be garbage collected as soon as it has finished playing. b) Class ImageIcon provides constructors that allow an ImageIcon object to be initial ized only with an image from the local computer. c) Applet method getParameter gets the value associated with a specific HTML parameter and returns the valueas a String. ANSWERS TO SELF-REVIEW EXERCISES 18.1 a) getImage. b) getDocumentBase. c) drawImage. d) AudioClip. e) image map. f) paintIcon. g) Graphics Interchange Format (GIF), Joint Photographic Experts Group (JPEG) Portable Network Graphics (PNG). 18.2 a) False. The sound will be marked for garbage collection (if it is not referenced by an AudioClip) and will be garbage collected when the garbage collector is able to run. b) False. ImageIcon can load images from the Internet as well. c) True. EXERCISES 18.3 Describe how to make an animation browser friendly. 18.4 Describe the Java methods for playing and manipulating audio clips. 18.5 How can Java applets be customized with information from an HTML file? 18.6 Explain how image maps are used. List 10 examples in which image maps are used. 18.7 (Randomly Erasing an Image) Suppose an image is displayed in a rectangular screen area. One way to erase the image is simply to set every pixel to the same color immediately, but this is a dull visual effect. Write a Java program that displays an image and then erases it by using random #BREAK# Chapter 18 Multimedia: Images, Animation, Audio and Video 1091 number generation to select individual pixels to erase. After most of the image is erased, erase all of the remaining pixels at once. You can refer to individual pixels by having a line that starts and ends at the same point. You might try several variants of this problem. For example, you might display lines randomly or display shapes randomly to erase regions of the screen. 18.8 (Text Flasher) Create a Java program that repeatedly flashes text on the screen. Do this by alternating the text with a plain background-color image. Allow the user to control the blink speed and the background color or pattern. 18.9 (Image Flasher) Create a Java program that repeatedly flashes an image on the screen. Do this by alternating the image with a plain background-color image. 18.10 (Digital Clock) Implement a program that displays a digital clock on the screen. You might add options to scale the clock; display day, month and year; issue an alarm; play certain audios at designated times and the like. 18.11 (Calling Attention to an Image) If you want to emphasize an image, you might place a row of simulated light bulbs around your image. You can let the light bulbs flash in unison, or you can let them fire on and off in sequence one after the other. 18.12 (Image Zooming) Create a program that enables you to zoom in on, or away from, an image. SPECIAL SECTION: CHALLENGING MULTIMEDIA PROJECTS The preceding exercises are keyed to the text and designed to test the reader s understanding of fundamental multimedia concepts. This section includes a collection of advanced multimedia projects. The reader should find these problems challenging, yet entertaining. The problems vary considerably in difficulty. Some require an hour or two of program writing and implementation. Others are useful for lab assignments that might require two or three weeks of study and implementation. Some are challenging term projects. [Note: Solutions are not provided for these exercises.]. 18.13 (Animation) Create a a general purpose Java animation program. Your program should allow the user to specify the sequence of frames to be displayed, the speed at which the images are displayed, audios that should be played while the animation is running and so on. 18.14 (Limericks) Modify the limerick-writing program you wrote in Exercise 10.10 to sing the limericks your program creates. 18.15 (Random Inter-Image Transition) This provides a nice visual effect. If you are displaying one image in a given area on the screen and you would like to transition to another image in the same screen area, store the new screen image in an off-screen buffer and randomly copy pixels from the new image to the display area, overlaying the previous pixels at those locations. When the vast majority of the pixels have been copied, copy the entire new image to the display area to be sure you are displaying the complete new image. To implement this program, you may need to use the Pixel- Grabberand MemoryImageSource classes (see the Java API documentation for descriptions of these classes). You might try several variants of this problem. For example, try selecting all the pixels in a randomly selected straight line or shape in the new image, and overlay those pixels above the corresponding positions of the old image. 18.16 (Background Audio) Add background audio to one of your favorite applications by using the loop method of class AudioClip to play the sound in the background while you interact with your application in the normal way. 18.17 (Scrolling Marquee Sign) Create a Java program that scrolls dotted characters from right to left (or from left to right if that is appropriate for your language) across a Marquee-like display sign. As an option, display the text in a continuous loop, so that after the text disappears at one end it reappears at the other end. #BREAK# 1092 Multimedia: Images, Animation, Audio and Video Chapter 18 18.18 (Scrolling Image Marquee) Create a Java program that scrolls an image across a Marquee screen. 18.19 (Analog Clock) Create a Java program that displays an analog clock with hour, minute and second hands that move appropriately as the time changes. 18.20 (Dynamic Audio and Graphical Kaleidoscope) Write a kaleidoscope program that displays reflected graphics to simulate the popular children s toy. Incorporate audio effects that mirror your program s dynamically changing graphics. 18.21 (Automatic Jigsaw Puzzle Generator) Create a Java jigsaw puzzle generator and manipulator. Your user specifies an image. Your program loads and displays the image. Your program then breaks the image into randomly selected shapes and shuffles the shapes. The user then uses the mouse to move the puzzle pieces around to solve the puzzle. Add appropriate audio sounds as the pieces are being moved around and snapped back into place. You might keep tabs on each piece and where it really belongs and then use audio effects to help the user get the pieces into the correct positions. 18.22 (Maze Generator and Walker) Develop a multimedia-based maze generator and traverser program based on the maze programs you wrote in Exercise 7.38 Exercise 7.40. Let the user customize the maze by specifying the number of rows and columns and by indicating the level of difficulty. Have an animated mouse walk the maze. Use audio to dramatize the movement of your mouse character. 18.23 (One-Armed Bandit) Develop a multimedia simulation of a one-armed bandit. Have three spinning wheels. Place various fruits and symbols on each wheel. Use true random-number generation to simulate the spinning of each wheel and the stopping of each wheel on a symbol. 18.24 (Horse Race) Create a Java simulation of a horse race. Have multiple contenders. Use audios for a race announcer. Play the appropriate audios to indicate the correct status of each of the contenders throughout the race. Use audios to announce the final results. You might try to simulate the kinds of horse-racing games that are often played at carnivals. The players get turns at the mouse and have to perform some skill-oriented manipulation with the mouse to advance their horses. 18.25 (Shuffleboard) Develop a multimedia-based simulation of the game of shuffleboard. Use appropriate audio and visual effects. 18.26 (Game of Pool) Create a multimedia-based simulation of the game of pool. Each player takes turns using the mouse to position a pool stick and to hit the stick against the ball at the appropriate angle to try to get the pool balls to fall into the pockets. Your program should keep score. 18.27 (Artist) Design a Java art program that will give an artist a great variety of capabilities to draw, use images, use animations, etc., to create a dynamic multimedia art display. 18.28 (Fireworks Designer) Create a Java program that someone might use to create a fireworks display. Create a variety of fireworks demonstrations. Then orchestrate the firing of the fireworks for maximum effect. 18.29 (Floor Planner) Develop a Java program that will help someone arrange furniture in his or her home. Add features that enable the person to achieve the best possible arrangement. 18.30 (Crossword) Crossword puzzles are among the most popular pastimes. Develop a multimedia-based crossword-puzzle program. Your program should enable the player to place and erase words easily. Tie your program to a large computerized dictionary. Your program also should be able to suggest words based on which letters have already been filled in. Provide other features that will make the crossword-puzzle enthusiast s job easier. 18.31 (15 Puzzle) Write a multimedia-based Java program that enables the user to play the game of 15. There is a 4-by-4 board for a total of 16 slots. One of the slots is empty. The other slots are occupied by 15 tiles numbered 1 through 15. Any tile next to the currently empty slot can be moved into #BREAK# Chapter 18 Multimedia: Images, Animation, Audio and Video 1093 that slot by clicking on the tile. Your program should create the board with the tiles out of order. The goal is to arrange the tiles into sequential order, row by row. 18.32 (Reaction Time/Reaction Precision Tester) Create a Java program that moves a randomly created shape around the screen. The user moves the mouse to catch and click on the shape. The shape s speed and size can be varied. Keep statistics on how much time the user typically takes to catch a shape of a given size. The user will probably have more difficulty catching faster moving, smaller shapes. 18.33 (Calendar/Tickler File) Using both audio and images create a general purpose calendar and tickler file. For example, the program should sing Happy Birthday when you use it on your birthday. Have the program display images and play audios associated with important events. Also, have the program remind you in advance of these important events. It would be nice, for example, to have the program give you a week s notice so you can pick up an appropriate greeting card for that special person. 18.34 (Rotating Images) Create a Java program that lets you rotate an image through some number of degrees (out of a maximum of 360 degrees). The program should let you specify that you want to spin the image continuously. The program should let you adjust the spin speed dynamically. 18.35 (Coloring Black and White Photographs and Images) Create a Java program that lets you paint a black and white photograph with color. Provide a color palette for selecting colors. Your program should let you apply different colors to different regions of the image. 18.36 (Multimedia-Based Simpletron Simulator) Modify the Simpletron simulator that you developed in the exercises in the previous chapters to include multimedia features. Add computer-like sounds to indicate that the Simpletron is executing instructions. Add a breaking glass sound when a fatal error occurs. Use flashing lights to indicate which cells of memory and/or which registers are currently being manipulated. Use other multimedia techniques, as appropriate, to make your Simpletron simulator more valuable to its users as an educational tool. #BREAK# 19 Data Structures Objectives To be able to form linked data structures using references, self-referential classes and recursion. To be able to create and manipulate dynamic data structures, such as linked lists, queues, stacks and binary trees. To understand various important applications of linked data structures. To understand how to create reusable data structures with classes, inheritance and composition. Much that I bound, I could not free; Much that I freed returned to me. Lee Wilson Dodd Will you walk a little faster? said a whiting to a snail, There s a porpoise close behind us, and he s treading on my tail. Lewis Carroll There is always room at the top. Daniel Webster Push on keep moving. Thomas Morton I think that I shall never see A poem lovely as a tree. Joyce Kilmer #BREAK# Chapter 19 Data Structures 1095 Outline 19.1 Introduction 19.2 Self-Referential Classes 19.3 Dynamic Memory Allocation 19.4 Linked Lists 19.5 Stacks 19.6 Queues 19.7 Trees Summary Terminology Self-Review Exercises Answers to Self-Review Exercises Exercises Special Section: Building Your Own Compiler 19.1 Introduction We have studied such fixed-size data structures as single- and double-subscripted arrays. This chapter introduces dynamic data structures that grow and shrink at execution time. Linked lists are collections of data items lined up in a row insertions and deletions can be made anywhere in a linked list. Stacks are important in compilers and operating systems; insertions and deletions are made only at one end of a stack its top. Queues represent waiting lines; insertions are made at the back (also referred to as the tail) of a queue and deletions are made from the front (also referred to as the head) of a queue. Binary trees facilitate high-speed searching and sorting of data, eliminating of duplicate data items efficiently, representing file system directories, compiling expressions into machine language and many other interesting applications. We will discuss each of the major types of data structures and implement programs that create and manipulate them. We use classes, inheritance and composition to create and package these data structures for reusability and maintainability. In Chapter 20, Java Utilities Package and Bit Manipulation, and Chapter 21, Collections, we discuss Java s predefined classes that implement the data structures discussed in this chapter. The chapter examples are practical programs that can be used in more advanced courses and in industrial applications. The exercises include a rich collection of useful applications. We encourage you to attempt the major project described in the special section entitled Building Your Own Compiler. You have been using a Java compiler to translate your Java programs to bytecodes so that you could execute these programs on your computer. In this project, you will actually build your own compiler. It will read a file of statements written in a simple, yet powerful high-level language similar to early versions of the popular language Basic. Your compiler will translate these statements into a file of Simpletron Machine Language (SML) instructions SML is the language you learned in the Chapter 7 special section, Building Your Own Computer. Your Simpletron Simulator program will then execute the SML program produced by your compiler! Implementing this project by using an object-oriented approach will give you a wonderful opportunity to exercise most of what you have learned in this book. The special section carefully walks you through the specifications of the high-level language and describes the algorithms you will need to con #BREAK# 1096 Data Structures Chapter 19 vert each type of high-level language statement into machine language instructions. If you enjoy being challenged, you might attempt the many enhancements to both the compiler and the Simpletron Simulator suggested in the exercises. 19.2 Self-Referential Classes A self-referential class contains an instance variable that refers to another object of the same class type. For example, the definition class Node { private int data; private Node nextNode; public Node( int data ) { /* constructor body */ } public void setData( int data ) { /* method body */ } public int getData() { /* method body */ } public void setNext( Node next ) { /* method body */ } public Node getNext() { /* method body */ } } defines class Node. This type has two private instance variables integer data and Node reference nextNode. Member nextNode references an object of type Node, an object of the same type as the one being declared here hence, the term self-referential class. Member nextNode is a link nextNode links an object of type Node to another object of the same type. Type Nodealso has five methods: a constructor that receives an integer to initialize data, a setData method to set the value data, a getData method to return the value of data, a setNext method to set the value of nextNode and a getNext method to return the value of member nextNode. Programs can link self-referential objects together to form such useful data structures as lists, queues, stacks and trees. Figure 19.1 illustrates two self-referential objects linked together to form a list. A backslash representing a null reference is placed in the link member of the second self-referential object to indicate that the link does not refer to another object. The backslash is for illustration purposes; it does not correspond to the backslash character in Java. Normally, a null reference indicates the end of a data structure. Common Programming Error 19.1 Not setting the link in the last node of a list to null is a logic error. 19.3 Dynamic Memory Allocation Creating and maintaining dynamic data structures requires dynamic memory allocation the ability for a program to obtain more memory space at execution time to hold new nodes and to release space no longer needed. As we have already learned, Java programs do not explicitly release dynamically allocated memory. Rather, Java performs automatic garbage collection on objects that are no longer referenced in a program. The limit for dynamic memory allocation can be as large as the amount of available physical memory in the computer or the amount of available disk space in a virtual-memory system. Often, the limits are much smaller, because the computer s available memory must be shared among many applications. #BREAK# Chapter 19 Data Structures 1097 15 10 Fig. 19.1Two self-referential class objects linked together. 19. Operator new is essential to dynamic memory allocation. Operator new takes as an operand the type of the object being dynamically allocated and returns a reference to a newly created object of that type. For example, the statement Node nodeToAdd = new Node( 10 ); allocates the appropriate amount of memory to store a Nodeobject and places a reference to this object in nodeToAdd. If no memory is available, newthrows an OutOfMemory- Error. The 10 is the Nodeobject s data. The following sections discuss lists, stacks, queues and trees that use dynamic memory allocation and self-referential classes to create dynamic data structures. 19.4 Linked Lists A linked list is a linear collection (i.e., a sequence) of self-referential class objects, called nodes, connected by reference links hence, the term linked list. A program accesses a linked list via a reference to the first node of the list. The program accesses each subsequent node via the link reference stored in the previous node. By convention, the link reference in the last node of a list is set to nullto mark the end of the list. Data are stored in a linked list dynamically the list creates each node as necessary. A node can contain data of any type, including objects of other classes. Stacks and queues are also linear data structures and, as we will see, are constrained versions of linked lists. Trees are nonlinear data structures. Lists of data can be stored in arrays, but linked lists provide several advantages. A linked list is appropriate when the number of data elements to be represented in the data structure is unpredictable. Linked lists are dynamic, so the length of a list can increase or decrease as necessary. The size of a conventional Java array, however, cannot be altered, because the array size is fixed at the time the program creates the array. Conventional arrays can become full. Linked lists become full only when the system has insufficient memory to satisfy dynamic storage allocation requests. Package java.util contains class LinkedList for implementing and manipulating linked lists that grow and shrink during program execution. We discuss class LinkedListin Chapter 21, Collections. Performance Tip 19.1 An array can be declared to contain more elements than the number of items expected, but this can waste memory. Linked lists can provide better memory utilization in these situations. Linked lists allow the program to adapt at runtime. Performance Tip 19.2 Insertion into a linked list is fast only two references have to be modified (after you have located the place to do the insertion). All existing nodes remain at their current locations in memory. #BREAK# 1098 Data Structures Chapter 19 Linked lists can be maintained in sorted order simply by inserting each new element at the proper point in the list (it does, of course, take time to locate the proper insertion point). Existing list elements do not need to be moved. Linked list nodes normally are not stored contiguously in memory. Rather, they are logically contiguous. Figure 19.2 illustrates a linked list with several nodes. This diagram presents a singly-linked list each node contains one reference to the next node in the list. Often, linked lists are implemented as doubly-linked lists each node contains a reference to the next node in the list and a reference to the previous node in the list. Java s LinkedList class (see Chapter 21) is a doubly-linked list implementation. Performance Tip 19.3 Insertion and deletion in a sorted array can be time consuming all the elements following the inserted or deleted element must be shifted appropriately. Performance Tip 19.4 The elements of an array are stored contiguously in memory. This allows immediate access to any array element, because the address of any element can be calculated directly as its offset from the beginning of the array. Linked lists do not afford such immediate access to their elements an element can be accessed only by traversing the list from the front. Performance Tip 19.5 Using dynamic memory allocation (instead of arrays) for data structures that grow and shrink at execution time can save memory. Keep in mind, however, that references occupy space, and that dynamic memory allocation incurs the overhead of method calls. The program of Fig. 19.3 Fig. 19.5 uses a List class to manipulate a list of miscellaneous object types. The main method of class ListTest (Fig. 19.5) creates a list of objects, inserts objects at the beginning of the list using method insertAtFront, inserts objects at the end of the list using method insertAtBack, deletes objects from the front of the list using method removeFromFront and deletes objects from the end of the list using method removeFromBack. After each insert and remove operation, ListTest calls List method printto display the current list contents. A detailed discussion of the program follows. If an attempt is made to remove an item from an empty list, an Empty- ListException (Fig. 19.4) occurs. The program of Fig. 19.3 Fig. 19.5 consists of four classes ListNode (Fig. 19.3), List (Fig. 19.3), EmptyListException (Fig. 19.4) and ListTest (Fig. 19.5). The List and ListNode classes are placed in package com.deitel.jhtp4.ch19, so they can be reused throughout this chapter. Encapsulated in each List object is a linked list of ListNode objects. The ListNode class (lines 6 38 of Fig. 19.3) consists of package-access members data and nextNode. ListNode member data can refer to any Object. ListNode member nextNode stores a reference to the next ListNode object in the linked list. [Note: Many of the classes in this chapter are defined in the package com.deitel.jhtp4.ch19. Each such class should be compiled with the -d command-line option to javac. When compiling the classes that are not in this package and when running the programs, be sure to use the -classpathoption to javac and java, respectively.] #BREAK# Chapter 19 Data Structures 1099 H D Q firstNode lastNode … Fig. 19.2A graphical representation of a linked list. 19. 1 // Fig. 19.3: List.java 2 // Class ListNode and class List definitions 3 package com.deitel.jhtp4.ch19; 4 5 // class to represent one node in a list 6 class ListNode { 7 8 // package access members; List can access these directly 9 Object data; 10 ListNode nextNode; 11 12 // constructor to create a ListNode that refers to object 13 ListNode( Object object ) 14 { 15 this( object, null ); 16 } 17 18 // constructor to create ListNode that refers to Object 19 // and to next ListNode in List 20 ListNode( Object object, ListNode node ) 21 { 22 data = object; 23 nextNode = node; 24 } 25 26 // return Object in this node 27 Object getObject() 28 { 29 return data; 30 } 31 32 // get next node 33 ListNode getNext() 34 { 35 return nextNode; 36 } 37 38 } // end class ListNode 39 Fig. 19.3 Definitions of class ListNodeand class List(part 1 of 4). #BREAK# 1100 Data Structures Chapter 19 40 // class List definition 41 public class List { 42 private ListNode firstNode; 43 private ListNode lastNode; 44 private String name; // String like “list” used in printing 45 46 // construct an empty List with a name 47 public List( String string ) 48 { 49 name = string; 50 firstNode = lastNode = null; 51 } 52 53 // construct empty List with “list” as the name 54 public List() 55 { 56 this( “list” ); 57 } 58 59 // Insert Object at front of List. If List is empty, 60 // firstNode and lastNode will refer to same object. 61 // Otherwise, firstNode refers to new node. 62 public synchronized void insertAtFront( Object insertItem ) 63 { 64 if ( isEmpty() ) 65 firstNode = lastNode = new ListNode( insertItem ); 66 67 else 68 firstNode = new ListNode( insertItem, firstNode ); 69 } 70 71 // Insert Object at end of List. If List is empty, 72 // firstNode and lastNode will refer to same Object. 73 // Otherwise, lastNode’s nextNode refers to new node. 74 public synchronized void insertAtBack( Object insertItem ) 75 { 76 if ( isEmpty() ) 77 firstNode = lastNode = new ListNode( insertItem ); 78 79 else 80 lastNode = lastNode.nextNode = 81 new ListNode( insertItem ); 82 } 83 84 // remove first node from List 85 public synchronized Object removeFromFront() 86 throws EmptyListException 87 { 88 Object removeItem = null; 89 90 // throw exception if List is empty 91 if ( isEmpty() ) 92 throw new EmptyListException( name ); Fig. 19.3 Definitions of class ListNodeand class List(part 2 of 4). #BREAK# Chapter 19 Data Structures 1101 93 94 // retrieve data being removed 95 removeItem = firstNode.data; 96 97 // reset the firstNode and lastNode references 98 if ( firstNode == lastNode ) 99 firstNode = lastNode = null; 100 101 else 102 firstNode = firstNode.nextNode; 103 104 // return removed node data 105 return removeItem; 106 } 107 108 // Remove last node from List 109 public synchronized Object removeFromBack() 110 throws EmptyListException 111 { 112 Object removeItem = null; 113 114 // throw exception if List is empty 115 if ( isEmpty() ) 116 throw new EmptyListException( name ); 117 118 // retrieve data being removed 119 removeItem = lastNode.data; 120 121 // reset firstNode and lastNode references 122 if ( firstNode == lastNode ) 123 firstNode = lastNode = null; 124 125 else { 126 127 // locate new last node 128 ListNode current = firstNode; 129 130 // loop while current node does not refer to lastNode 131 while ( current.nextNode != lastNode ) 132 current = current.nextNode; 133 134 // current is new lastNode 135 lastNode = current; 136 current.nextNode = null; 137 } 138 139 // return removed node data 140 return removeItem; 141 } 142 143 // return true if List is empty 144 public synchronized boolean isEmpty() 145 { Fig. 19.3 Definitions of class ListNodeand class List(part 3 of 4). #BREAK# 1102 Data Structures Chapter 19 146 return firstNode == null; 147 } 148 149 // output List contents 150 public synchronized void print() 151 { 152 if ( isEmpty() ) { 153 System.out.println( “Empty ” + name ); 154 return; 155 } 156 157 System.out.print( “The ” + name + ” is: ” ); 158 159 ListNode current = firstNode; 160 161 // while not at end of list, output current node’s data 162 while ( current != null ) { 163 System.out.print( current.data.toString() + ” ” ); 164 current = current.nextNode; 165 } 166 167 System.out.println( “n” ); 168 } 169 170 } // end class List Fig. 19.3 Definitions of class ListNodeand class List(part 4 of 4). Class List contains private members firstNode (a reference to the first ListNode in a List) and lastNode (a reference to the last ListNode in a List). The constructors (lines 47 51 and 54 57) initialize both references to null. The most important methods of class List are the synchronized methods insertAtFront (lines 62 69), insertAtBack (lines 74 82), removeFromFront (lines 85 106) and removeFromBack (lines 109 141). These methods are declared synchronized so Listobjects can be multithread safe when used in a multithreaded program. If one thread is modifying the contents of a List, no other thread can modify the same List object at the same time. Method isEmpty (lines 144 147) is a predicate method that determines whether the list is empty (i.e., the reference to the first node of the list is null). Predicate methods typically test a condition and do not modify the object on which they are called. If the list is empty, method isEmpty returns true; otherwise, it returns false. Method print (lines 150 168) displays the list s contents. Both isEmpty and print are also synchronizedmethods. 1 // Fig. 19.4: EmptyListException.java 2 // Class EmptyListException definition 3 package com.deitel.jhtp4.ch19; 4 5 public class EmptyListException extends RuntimeException { 6 Fig. 19.4 Definition of class EmptyListException(part 1 of 2). #BREAK# Chapter 19 Data Structures 1103 7 // initialize an EmptyListException 8 public EmptyListException( String name ) 9 { 10 super( “The ” + name + ” is empty” ); 11 } 12 13 } // end class EmptyListException Fig. 19.4 Definition of class EmptyListException(part 2 of 2). 1 // Fig. 19.5: ListTest.java 2 // Class ListTest 3 4 // Deitel packages 5 import com.deitel.jhtp4.ch19.List; 6 import com.deitel.jhtp4.ch19.EmptyListException; 7 8 public class ListTest { 9 10 // test class List 11 public static void main( String args[] ) 12 { 13 List list = new List(); // create the List container 14 15 // create objects to store in List 16 Boolean bool = Boolean.TRUE; 17 Character character = new Character( ‘$’ ); 18 Integer integer = new Integer( 34567 ); 19 String string = “hello”; 20 21 // use List insert methods 22 list.insertAtFront( bool ); 23 list.print(); 24 list.insertAtFront( character ); 25 list.print(); 26 list.insertAtBack( integer ); 27 list.print(); 28 list.insertAtBack( string ); 29 list.print(); 30 31 // use List remove methods 32 Object removedObject; 33 34 // remove objects from list; print after each removal 35 try { 36 removedObject = list.removeFromFront(); 37 System.out.println( 38 removedObject.toString() + ” removed” ); 39 list.print(); 40 Fig. 19.5Manipulating a linked list. Fig. 19.5 #BREAK# 1104 Data Structures Chapter 19 41 removedObject = list.removeFromFront(); 42 System.out.println( 43 removedObject.toString() + ” removed” ); 44 list.print(); 45 46 removedObject = list.removeFromBack(); 47 System.out.println( 48 removedObject.toString() + ” removed” ); 49 list.print(); 50 51 removedObject = list.removeFromBack(); 52 System.out.println( 53 removedObject.toString() + ” removed” ); 54 list.print(); 55 } 56 57 // process exception if List is empty when attempt is 58 // made to remove an item 59 catch ( EmptyListException emptyListException ) { 60 emptyListException.printStackTrace(); 61 } 62 63 } // end method main 64 65 } // end class ListTest The list is: true The list is: $ true The list is: $ true 34567 The list is: $ true 34567 hello $ removed The list is: true 34567 hello true removed The list is: 34567 hello hello removed The list is: 34567 34567 removed Empty list Fig. 19.5Manipulating a linked list. Fig. 19.5 Over the next several pages, we discuss each of the methods of class List(Fig. 19.3) in detail. Method insertAtFront (lines 62 69 of Fig. 19.3) places a new node at the front of the list. The method consists of several steps (Fig. 19.6 illustrates the operation): 1. Call isEmptyto determine whether the list is empty (line 64). #BREAK# Chapter 19 Data Structures 1105 7 11 firstNode 12 new ListNode (a) 7 11 firstNode 12 new ListNode (b) Fig. 19.6 The insertAtFrontoperation. 2. If the list is empty, both firstNodeand lastNodeare set to the ListNode allocated with newand initialized with insertItem(line 65). The ListNode constructor at lines 13 16 calls the ListNode constructor at lines 20 24 to set instance variable datato refer to the insertItempassed as an argument and to set reference nextNodeto null. 3. If the list is not empty, the new node is threaded (not to be confused with multithreading) or linked into the list by setting firstNodeto a new ListNodeobject and initializing that object with insertItem and firstNode (line 68). When the ListNode constructor (lines 20 24) executes, it sets instance variable datato refer to the insertItempassed as an argument and performs the insertion by setting the nextNodereference to the ListNodepassed as an argument. In Fig. 19.6, part (a) shows the list and the new node during the insertAtFront operation and before the program threads the new node into the list. The dotted arrows in part (b) illustrate step 3 of the insertAtFront operation that enables the node containing 12to become the new list front. Method insertAtBack(lines 74 82 of Fig. 19.3) places a new node at the back of the list. The method consists of several steps (Fig. 19.7 illustrates the operation): 1. Call isEmptyto determine whether the list is empty (line 76). 2. If the list is empty, both firstNodeand lastNodeare set to the ListNode allocated with newand initialized with insertItem(line 77). The ListNode constructor at lines 13 16 calls the ListNode constructor at lines 20 24 to set instance variable datato refer to the insertItempassed as an argument and to set reference nextNodeto null. 3. If the list is not empty, the new node is threaded into the list by setting LastNode and lastNode.nextNodeto the ListNodethat was allocated with newand initialized with insertItem (lines 80 81). When the ListNode constructor (lines 13 16) executes, it sets instance variable data to refer to the insert- Itempassed as an argument and sets reference nextNodeto null. #BREAK# 1106 Data Structures Chapter 19 12 7 11 firstNode lastNode(a) 5 new ListNode (b) firstNode lastNode new ListNode 12 11 57 Fig. 19.7A graphical representation of the insertAtBackoperation. 19. In Fig. 19.7, part (a) shows the list and the new node during the insertAtBack operation and before the program threads the new node into the list. The dotted arrows in part (b) illustrate the steps of method insertAtBackthat enable a new node to be added to the end of a list that is not empty. Method removeFromFront (lines 85 106 of Fig. 19.3) removes the front node of the list and returns a reference to the removed data. The method throws an EmptyList- Exception(lines 91 92) if the list is empty when the program calls this method. Otherwise, the method returns a reference to the removed data. The method consists of several steps (illustrated in Fig. 19.8): 1. Assign firstNode.data (the data being removed from the list) to reference removeItem(line 95). 2. If the objects to which firstNodeand lastNoderefer are equal (line 98), the list has only one element prior to the removal attempt. In this case, the method sets firstNodeand lastNodeto null(line 99) to dethread (remove) the node from the list (leaving the list empty). 3. If the list has more than one node prior to removal, then the method leaves reference lastNodeas is and simply assigns firstNode.nextNodeto reference firstNode(line 102). Thus, firstNodereferences the node that was the second node prior to the removeFromFrontcall. 4. Return the removeItemreference (line 105). In Fig. 19.8, part (a) illustrates the list before the removal operation. Part (b) shows actual reference manipulations. #BREAK# Chapter 19 Data Structures 1107 firstNode lastNode(a) 12 7 511 (b) firstNode lastNode 11 removeItem 7 512 Fig. 19.8A graphical representation of the removeFromFrontoperation. 19. Method removeFromBack (lines 109 141 of Fig. 19.3) removes the last node of a list and returns a reference to the removed data. The method throws an Empty- ListException (lines 94 and 95) if the list is empty when the program calls this method. The method consists of several steps (Fig. 19.9 illustrates the operation): 1. Assign lastNode.data (the data being removed from the list) to reference removeItem(line 119). 2. If the objects to which firstNode and lastNode refer are equal (line 122), the list has only one element prior to the removal attempt. In this case, the method sets firstNode and lastNode to null(line 123) to dethread (remove) that node from the list (leaving the list empty). 3. If the list has more than one node prior to removal, then create the ListNodereference currentand assign it firstNode. 4. Now walk the list with current until it references the node before the last node. The whileloop (lines 131 132) assigns current.nextNodeto currentas long as current.nextNodeis not lastNode. 5. After locating the second-to-last node, assign currentto lastNode(line 135) to dethread the last node from the list. 6. Set the current.nextNodeto null(line 136) to terminate the list at the current node. 7. Return the removeItemreference (liner 140). In Fig. 19.9, part (a) illustrates the list before the removal operation. Part (b) shows the actual reference manipulations. #BREAK# 1108 Data Structures Chapter 19 511712 firstNode lastNode(a) (b) firstNode current lastNode 5712 11 removeItem Fig. 19.9A graphical representation of the removeFromBackoperation. 19. Method print(lines 150 168) first determines whether the list is empty (lines 152 155). If so, printdisplays a message indicating that the list is empty and returns control to the calling method. Otherwise, print outputs the data in the list. Line 159 creates ListNodereference current and initializes it with firstNode. While currentis not null, there are more items in the list. therefore, line 163 outputs a Stringrepresentation of current.data. Line 164 moves to the next node in the list by assigning the value of current.nextNode to current. This printing algorithm is identical for linked lists, stacks and queues. 19.5 Stacks A stack is a constrained version of a linked list new nodes can be added to a stack and removed from a stack only at the top. For this reason, a stack is referred to as a last-in, first- out (LIFO) data structure. The link member in the bottom (i.e., last) node of the stack is set to nullto indicate the bottom of the stack. Common Programming Error 19.2 Not setting the link in the bottom node of a stack to null is a common logic error. The primary methods used to manipulate a stack are push and pop. Method push adds a new node to the top of the stack. Method popremoves a node from the top of the stack and returns the data from the popped node. Stacks have many interesting applications. For example, when a program calls a method, the called method must know how to return to its caller, so the return address of the calling method is pushed onto the program execution stack. If a series of method calls #BREAK# Chapter 19 Data Structures 1109 occurs, the successive return addresses are pushed onto the stack in last-in, first-out order so that each method can return to its caller. Stacks support recursive method calls in the same manner as they do conventional nonrecursive method calls. The program execution stack contains the memory for local variables on each invocation of a method during a program s execution. When the method returns to its caller, the memory for that method s local variables is popped off the stack and those variables are no longer known to the program. Compilers use stacks to evaluate arithmetic expressions and generate machine language code to process the expressions. The exercises in this chapter explore several applications of stacks, including using them to develop a complete working compiler. The java.utilpackage of the Java API contains class Stackfor implementing and manipulating stacks that can grow and shrink during program execution. We will discuss class Stackin Chapter 20. We take advantage of the close relationship between lists and stacks to implement a stack class by reusing a list class. We demonstrate two different forms of reusability. First, we implement the stack class by extending class Listof Fig. 19.3. Then we implement an identically performing stack class through composition by including a List object as a privatemember of a stack class. The list, stack and queue data structures in this chapter are implemented to store Object references to encourage further reusability. Thus, any object type can be stored in a list, stack or queue. The application of Fig. 19.10 and Fig. 19.11 creates a stack class by extending class Listof Fig. 19.3. We want the stack to have methods push, pop, isEmptyand print. Essentially, these are the methods insertAtFront, removeFromFront, isEmpty and print of class List. Of course, class List contains other methods (such as insertAtBack and removeFromBack) that we would rather not make accessible through the public interface to the stack class. It is important to remember that all methods in the publicinterface of class Listclass also are publicmethods of the subclass StackInheritance (Fig. 19.10). When we implement the stack s methods, we have each StackInheritance method call the appropriate List method method push calls insertAtFront and method pop calls removeFromFront. Class StackInheritance is defined as part of package com.deitel.jhtp4.ch19 for reuse purposes. Note that StackInheritance does not import List, because both classes are in the same package. 1 // Fig. 19.10: StackInheritance.java 2 // Derived from class List 3 package com.deitel.jhtp4.ch19; 4 5 public class StackInheritance extends List { 6 7 // construct stack 8 public StackInheritance() 9 { 10 super( “stack” ); 11 } 12 Fig. 19.10 Class StackInheritanceextends class List(part 1 of 2). #BREAK# 1110 Data Structures Chapter 19 13 // add object to stack 14 public synchronized void push( Object object ) 15 { 16 insertAtFront( object ); 17 } 18 19 // remove object from stack 20 public synchronized Object pop() throws EmptyListException 21 { 22 return removeFromFront(); 23 } 24 25 } // end class StackInheritance Fig. 19.10 Class StackInheritanceextends class List(part 2 of 2). Class StackInheritanceTest s method main (Fig. 19.11) uses class Stack- Inheritanceto instantiate a stack of Objects called stack. The program pushes onto the stack (lines 22, 24, 26 and 28) a Boolean object containing true, a Character object containing $, an Integer object containing 34567 and a String object containing hello, then popped off stack. Lines 37 42 pop the objects from the stack in an infinite whileloop. When there are no objects left to pop, an method popthrows an EmptyListException, and the program displays the exception s stack trace, which shows the methods on the program execution stack at the time the exception occurred. Note that the program uses method print(inherited from List) to output the contents of the stack. 1 // Fig. 19.11: StackInheritanceTest.java 2 // Class StackInheritanceTest 3 4 // Deitel packages 5 import com.deitel.jhtp4.ch19.StackInheritance; 6 import com.deitel.jhtp4.ch19.EmptyListException; 7 8 public class StackInheritanceTest { 9 10 // test class StackInheritance 11 public static void main( String args[] ) 12 { 13 StackInheritance stack = new StackInheritance(); 14 15 // create objects to store in the stack 16 Boolean bool = Boolean.TRUE; 17 Character character = new Character( ‘$’ ); 18 Integer integer = new Integer( 34567 ); 19 String string = “hello”; 20 21 // use push method 22 stack.push( bool ); 23 stack.print(); 24 stack.push( character ); Fig. 19.11 A simple stack program (part 1 of 2). #BREAK# Chapter 19 Data Structures 1111 25 stack.print(); 26 stack.push( integer ); 27 stack.print(); 28 stack.push( string ); 29 stack.print(); 30 31 // remove items from stack 32 try { 33 34 // use pop method 35 Object removedObject = null; 36 37 while ( true ) { 38 removedObject = stack.pop(); 39 System.out.println( removedObject.toString() + 40 ” popped” ); 41 stack.print(); 42 } 43 } 44 45 // catch exception if stack empty when item popped 46 catch ( EmptyListException emptyListException ) { 47 emptyListException.printStackTrace(); 48 } 49 50 } // end method main 51 52 } // end class StackInheritanceTest The stack is: true The stack is: $ true The stack is: 34567 $ true The stack is: hello 34567 $ true hello popped The stack is: 34567 $ true 34567 popped The stack is: $ true $ popped The stack is: true true popped Empty stack com.deitel.jhtp4.ch19.EmptyListException: The stack is empty at com.deitel.jhtp4.ch19.List.removeFromFront(List.java:92) at com.deitel.jhtp4.ch19.StackInheritance.pop( StackInheritance.java:22) at StackInheritanceTest.main(StackInheritanceTest.java:38) Fig. 19.11 A simple stack program (part 2 of 2). #BREAK# 1112 Data Structures Chapter 19 Another way to implement a stack class is by reusing a list class through composition. The class in Fig. 19.12 uses a private object of class List (line 6) in the definition of class StackComposition. Composition enables us to hide the methods of class List that should not be in our stack s public interface by providing public interface methods only to the required List methods. This technique of implementing each stack method as a call to a List method is called delegating the stack method invoked delegates the call to the appropriate List method. In particular, StackComposition delegates calls to List methods insertAtFront, removeFromFront, isEmpty and print. In this example, we do not show class StackCompositionTest, because the only difference in this example is that we change the type of the stack from Stack- Inheritance to StackComposition. If you execute the application from the code on the CD that accompanies this book, you will see that the output is identical. 1 // Fig. 19.12: StackComposition.java 2 // Class StackComposition definition with composed List object 3 package com.deitel.jhtp4.ch19; 4 5 public class StackComposition { 6 private List stackList; 7 8 // construct stack 9 public StackComposition() 10 { 11 stackList = new List( “stack” ); 12 } 13 14 // add object to stack 15 public synchronized void push( Object object ) 16 { 17 stackList.insertAtFront( object ); 18 } 19 20 // remove object from stack 21 public synchronized Object pop() throws EmptyListException 22 { 23 return stackList.removeFromFront(); 24 } 25 26 // determine if stack is empty 27 public synchronized boolean isEmpty() 28 { 29 return stackList.isEmpty(); 30 } 31 32 // output stack contents 33 public synchronized void print() 34 { 35 stackList.print(); 36 } 37 38 } // end class StackComposition Fig. 19.12 Fig. 19.12 A simple stack class using composition. #BREAK# Chapter 19 Data Structures 1113 19.6 Queues Another common data structure is the queue. A queue is similar to a checkout line in a supermarket the cashier services the person at the beginning of the line first. Other customers enter the line only at the end and wait for service. Queue nodes are removed only from the head (or front) of the queue and are inserted only at the tail (or end) of the queue. For this reason, a queue is a first-in, first-out (FIFO) data structure. The insert and remove operations are known as enqueue and dequeue. Queues have many applications in computer systems. Most computers have only a single processor, so only one application at a time can be serviced. Entries for the other applications are placed in a queue. The entry at the front of the queue is the next to receive service. Each entry gradually advances to the front of the queue as applications receive service. Queues are also used to support print spooling. A multiuser environment may have only a single printer. Many users may send print jobs to the printer. Even when the printer is busy, other people may still send print jobs. These are spooled to disk (much as thread is wound onto a spool) where they wait in a queue until the printer becomes available. Information packets also wait in queues in computer networks. Each time a packet arrives at a network node, it must be routed to the next node on the network along the path to the packet s final destination. The routing node routes one packet at a time, so additional packets are enqueued until the router can route them. A file server in a computer network handles file-access requests from many clients throughout the network. Servers have a limited capacity to service requests from clients. When that capacity is exceeded, client requests wait in queues. The application of Fig. 19.13 and Fig. 19.14 creates a queue by extending class List (Fig. 19.3). We want class QueueInheritance (Fig. 19.13) to have methods enqueueand dequeue, isEmptyand print. Note that these are essentially the List methods insertAtBack and removeFromFront. Of course, class List contains other methods (i.e., insertAtFrontand removeFromBack) that we would rather not make accessible through the public interface of the queue. Remember that all methods in the public interface of the List class are also public methods of the subclass QueueInheritance. In the queue s implementation, each method of class QueueInheritancecalls the appropriate List method method enqueuecalls insertAt- Back and method dequeue calls removeFromFront. Class QueueInheritance is defined in package com.deitel.jhtp4.ch19for reuse purposes. Common Programming Error 19.3 Not setting the link in the last node of a queue to null is a common logic error. 1 // Fig. 19.13: QueueInheritance.java 2 // Class QueueInheritance extends class List 3 4 // Deitel packages 5 package com.deitel.jhtp4.ch19; 6 7 public class QueueInheritance extends List { 8 Fig. 19.13 Class QueueInheritanceextends class List(part 1 of 2). #BREAK# 1114 Data Structures Chapter 19 9 // construct queue 10 public QueueInheritance() 11 { 12 super( “queue” ); 13 } 14 15 // add object to queue 16 public synchronized void enqueue( Object object ) 17 { 18 insertAtBack( object ); 19 } 20 21 // remove object from queue 22 public synchronized Object dequeue() throws EmptyListException 23 { 24 return removeFromFront(); 25 } 26 27 } // end class QueueInheritance Fig. 19.13 Class QueueInheritanceextends class List(part 2 of 2). Class QueueInheritanceTestmethod main(Fig. 19.14) uses class QueueInheritance to instantiate a queue of Objects called queue. Lines 22, 24, 26 and 28 enqueue a Boolean object containing true, a Character object containing $, an Integer object containing 34567 and a String object containing hello. Lines 37 42 use an infinite whileloop to dequeue the objects in first-in, first-out order. When there are no objects left to dequeue, method dequeue throws an EmptyListException, and the program displays the exception s stack trace. 1 // Fig. 19.14: QueueInheritanceTest.java 2 // Class QueueInheritanceTest 3 4 // Deitel packages 5 import com.deitel.jhtp4.ch19.QueueInheritance; 6 import com.deitel.jhtp4.ch19.EmptyListException; 7 8 public class QueueInheritanceTest { 9 10 // test class QueueInheritance 11 public static void main( String args[] ) 12 { 13 QueueInheritance queue = new QueueInheritance(); 14 15 // create objects to store in queue 16 Boolean bool = Boolean.TRUE; 17 Character character = new Character( ‘$’ ); 18 Integer integer = new Integer( 34567 ); 19 String string = “hello”; Fig. 19.14 Processing a queue (part 1 of 3). #BREAK# Chapter 19 Data Structures 1115 20 21 // use enqueue method 22 queue.enqueue( bool ); 23 queue.print(); 24 queue.enqueue( character ); 25 queue.print(); 26 queue.enqueue( integer ); 27 queue.print(); 28 queue.enqueue( string ); 29 queue.print(); 30 31 // remove objects from queue 32 try { 33 34 // use dequeue method 35 Object removedObject = null; 36 37 while ( true ) { 38 removedObject = queue.dequeue(); 39 System.out.println( removedObject.toString() + 40 ” dequeued” ); 41 queue.print(); 42 } 43 } 44 45 // process exception if queue empty when item removed 46 catch ( EmptyListException emptyListException ) { 47 emptyListException.printStackTrace(); 48 } 49 50 } // end method main 51 52 } // end class QueueInheritanceTest The queue is: true The queue is: true $ The queue is: true $ 34567 The queue is: true $ 34567 hello true dequeued The queue is: $ 34567 hello $ dequeued The queue is: 34567 hello 34567 dequeued The queue is: hello hello dequeued Empty queue (continued on next page) Fig. 19.14 Processing a queue (part 2 of 3). #BREAK# 1116 Data Structures Chapter 19 (continued from previous page) com.deitel.jhtp4.ch19.EmptyListException: The queue is empty at com.deitel.jhtp4.ch19.List.removeFromFront(List.java:92) at com.deitel.jhtp4.ch19.QueueInheritance.dequeue( QueueInheritance.java:24) at QueueInheritanceTest.main(QueueInheritanceTest.java:38) Fig. 19.14 Processing a queue (part 3 of 3). 19.7 Trees Linked lists, stacks and queues are linear data structures (i.e., sequences). Atree is a nonlinear, two-dimensional data structure with special properties. Tree nodes contain two or more links. This section discusses binary trees (Fig. 19.15) trees whose nodes all contain two links (none, one or both of which may be null). The root node is the first node in a tree. Each link in the root node refers to a child. The left child is the first node in the left subtree, and the right child is the first node in the right subtree. The children of a specific node are called siblings. A node with no children is called a leaf node. Computer scientists normally draw trees from the root node down exactly the opposite of the way most trees grow in nature. Common Programming Error 19.4 Not setting to null the links in leaf nodes of a tree is a common logic error. In our binary tree example, we create a special binary tree called a binary search tree. A binary search tree (with no duplicate node values) has the characteristic that the values in any left subtree are less than the value in the subtree s parent node, and the values in any right subtree are greater than the value in the subtree s parent node. Figure 19.16 illustrates a binary search tree with 12 integer values. Note that the shape of the binary search tree that corresponds to a set of data can vary, depending on the order in which the values are inserted into the tree. B A D C Fig. 19.15 19.15 A graphical representation of a binary tree. #BREAK# Chapter 19 Data Structures 1117 47 25 77 11 43 65 93 7 17 3144 68 FiFig. 19.1619.16 A binary search tree containing 12 values. The application of Fig. 19.17 and Fig. 19.18 creates a binary search tree of integers and traverses it (i.e., walks through all its nodes) three ways using recursive inorder, pre- order and postorder traversals. The program generates 10 random numbers and inserts each into the tree. Class Tree is defined in package com.deitel.jhtp4.ch19 for reuse purposes. 1 // Fig. 19.17: Tree.java 2 // Definition of class TreeNode and class Tree. 3 4 // Deitel packages 5 package com.deitel.jhtp4.ch19; 6 7 // class TreeNode definition 8 class TreeNode { 9 10 // package access members 11 TreeNode leftNode; 12 int data; 13 TreeNode rightNode; 14 15 // initialize data and make this a leaf node 16 public TreeNode( int nodeData ) 17 { 18 data = nodeData; 19 leftNode = rightNode = null; // node has no children 20 } 21 22 // insert TreeNode into Tree that contains nodes; 23 // ignore duplicate values 24 public synchronized void insert( int insertValue ) 25 { 26 // insert in left subtree 27 if ( insertValue < data ) { 28 29 // insert new TreeNode 30 if ( leftNode == null ) 31 leftNode = new TreeNode( insertValue ); 32 Fig. 19.17 Definitions of TreeNodeand Treefor a binary search tree (part 1 of 4). #BREAK# 1118 Data Structures Chapter 19 33 // continue traversing left subtree 34 else 35 leftNode.insert( insertValue ); 36 } 37 38 // insert in right subtree 39 else if ( insertValue > data ) { 40 41 // insert new TreeNode 42 if ( rightNode == null ) 43 rightNode = new TreeNode( insertValue ); 44 45 // continue traversing right subtree 46 else 47 rightNode.insert( insertValue ); 48 } 49 50 } // end method insert 51 52 } // end class TreeNode 53 54 // class Tree definition 55 public class Tree { 56 private TreeNode root; 57 58 // construct an empty Tree of integers 59 public Tree() 60 { 61 root = null; 62 } 63 64 // Insert a new node in the binary search tree. 65 // If the root node is null, create the root node here. 66 // Otherwise, call the insert method of class TreeNode. 67 public synchronized void insertNode( int insertValue ) 68 { 69 if ( root == null ) 70 root = new TreeNode( insertValue ); 71 72 else 73 root.insert( insertValue ); 74 } 75 76 // begin preorder traversal 77 public synchronized void preorderTraversal() 78 { 79 preorderHelper( root ); 80 } 81 82 // recursive method to perform preorder traversal 83 private void preorderHelper( TreeNode node ) 84 { Fig. 19.17 Definitions of TreeNodeand Treefor a binary search tree (part 2 of 4). #BREAK# Chapter 19 Data Structures 1119 85 if ( node == null ) 86 return; 87 88 // output node data 89 System.out.print( node.data + ” ” ); 90 91 // traverse left subtree 92 preorderHelper( node.leftNode ); 93 94 // traverse right subtree 95 preorderHelper( node.rightNode ); 96 } 97 98 // begin inorder traversal 99 public synchronized void inorderTraversal() 100 { 101 inorderHelper( root ); 102 } 103 104 // recursive method to perform inorder traversal 105 private void inorderHelper( TreeNode node ) 106 { 107 if ( node == null ) 108 return; 109 110 // traverse left subtree 111 inorderHelper( node.leftNode ); 112 113 // output node data 114 System.out.print( node.data + ” ” ); 115 116 // traverse right subtree 117 inorderHelper( node.rightNode ); 118 } 119 120 // begin postorder traversal 121 public synchronized void postorderTraversal() 122 { 123 postorderHelper( root ); 124 } 125 126 // recursive method to perform postorder traversal 127 private void postorderHelper( TreeNode node ) 128 { 129 if ( node == null ) 130 return; 131 132 // traverse left subtree 133 postorderHelper( node.leftNode ); 134 135 // traverse right subtree 136 postorderHelper( node.rightNode ); 137 Fig. 19.17 Definitions of TreeNodeand Treefor a binary search tree (part 3 of 4). #BREAK# 1120 Data Structures Chapter 19 138 // output node data 139 System.out.print( node.data + ” ” ); 140 } 142 } // end class Tree Fig. 19.17 Definitions of TreeNodeand Treefor a binary search tree (part 4 of 4). Let us walk through the binary tree program. Method main of class TreeTest (Fig. 19.18) begins by instantiating an empty Treeobject and storing it in reference tree (line 11). The program randomly generates 10 integers, each of which is inserted into the binary tree through a call to synchronized method insertNode (line 21). The program then performs preorder, inorder and postorder traversals (these will be explained shortly) of tree. // Fig. 19.18: TreeTest.java // This program tests class Tree. import com.deitel.jhtp4.ch19.Tree; // Class TreeTest definition public class TreeTest { // test class Tree public static void main( String args[] ) { Tree tree = new Tree(); int value; System.out.println( “Inserting the following values: ” ); // insert 10 random integers from 0-99 in tree for ( int i = 1; i <= 10; i++ ) { value = ( int ) ( Math.random() * 100 ); System.out.print( value + " " ); tree.insertNode( value ); } // perform preorder traveral of tree System.out.println ( "nnPreorder traversal" ); tree.preorderTraversal(); // perform inorder traveral of tree System.out.println ( "nnInorder traversal" ); tree.inorderTraversal(); // perform postorder traveral of tree System.out.println ( "nnPostorder traversal" ); tree.postorderTraversal(); System.out.println(); } Fig. 19.18 Fig. 19.18 Creating and traversing a binary tree (part 1 of 2). #BREAK# Chapter 19 Data Structures 1121 38 } // end class TreeTest Inserting the following values: 39 69 94 47 50 72 55 41 97 73 Preorder traversal 39 69 47 41 50 55 94 72 73 97 Inorder traversal 39 41 47 50 55 69 72 73 94 97 Postorder traversal 41 55 50 47 73 72 97 94 69 39 Fig. 19.18 Fig. 19.18 Creating and traversing a binary tree (part 2 of 2). Class Tree(lines 55 142 of Fig. 19.17) has as privatedata root a reference to the root node of the tree. The class contains publicmethod insertNode(lines 67 74) to insert a new node in the tree and publicmethods preorderTraversal(lines 77 80), inorderTraversal (lines 99 102) and postorderTraversal (lines 121 124) to begin traversals of the tree. Each of these methods calls a separate recursive utility method to perform the traversal operations on the internal representation of the tree. The Treeconstructor (lines 59 62) initializes rootto nullto indicate that the tree is empty. The Treeclass s synchronizedmethod insertNode(lines 67 74) first determines if the tree is empty. If so, it allocates a new TreeNode, initializes the node with the integer being inserted in the tree and assigns the new node to the rootreference (line 70). If the tree is not empty, insertNode calls TreeNodemethod insert(lines 24 52). This method uses recursion to determine the location for the new node in the tree and inserts the node at that location. A node can be inserted only as a leaf node in a binary search tree. The TreeNode method insertcompares the value to insert with the data value in the root node. If the insert value is less than the root node data, the program determines if the left subtree is empty (line 30). If so, line 31 allocates a new TreeNode, initializes it with the integer being inserted and assigns the new node to reference leftNode. Otherwise, line 35 recursively calls insert for the left subtree to insert the value into the left subtree. If the insert value is greater than the root node data, the program determines if the right subtree is empty (line 42). If so, line 43 allocates a new TreeNode, initializes it with the integer being inserted and assigns the new node to reference rightNode. Otherwise, line 47 recursively calls insertfor the right subtree to insert the value in the right subtree. Methods inorderTraversal, preorderTraversal and postorderTraversal call helper methods inorderHelper (lines 105 118), preorderHelper (lines 83 96) and postorderHelper(lines 127 140), respectively, to traverse the tree (Fig. 19.19) and print the node values. The purpose of the helper methods in class Treeis to allow the programmer to start a traversal without the need to obtain a reference to the root node first, then call the recursive method with that reference. Methods inorder- Traversal, preorderTraversal and postorderTraversal simply take the private root reference and pass it to the appropriate helper method to initiate a traversal of the tree. #BREAK# 1122 Data Structures Chapter 19 27 13 42 6 17 33 48 Fig. 19.19 A binary search tree. Method inorderHelper(lines 105 118) defines the steps for an inorder traversal. Those steps are as follows: 1. Traverse the left subtree with a call to inorderHelper(line 111). 2. Process the value in the node (line 114). 3. Traverse the right subtree with a call to inorderHelper(line 117). The inorder traversal does not process the value in a node until the values in that node s left subtree are processed. The inorder traversal of the tree in Fig. 19.19 is 6 13 17 27 33 42 48 Note that the inorder traversal of a binary search tree prints the node values in ascending order. The process of creating a binary search tree actually sorts the data and thus, this process is called the binary tree sort. Method preorderHelper (lines 83 96) defines the steps for a preorder traversal. Those steps are as follows: 1. Process the value in the node (line 89). 2. Traverse the left subtree with a call to preorderHelper(line 92). 3. Traverse the right subtree with a call to preorderHelper(line 95). The preorder traversal processes the value in each node as the node is visited. After processing the value in a given node, the preorder traversal processes the values in the left sub- tree, then the values in the right subtree. The preorder traversal of the tree in Fig. 19.19 is 27 13 6 17 42 33 48 Method postorderHelper (lines 127 140) defines the steps for a postorder traversal. Those steps are as follows: 1. Traverse the left subtree with a postorderHelper(line 133). 2. Traverse the right subtree with a postorderHelper(line 136). 3. Process the value in the node (line 139). The postorder traversal processes the value in each node after the values of all that node s children are processed. The postorderTraversalof the tree in Fig. 19.19 is 6 17 13 33 48 42 27 The binary search tree facilitates duplicate elimination. While building a tree, the insertion operation recognizes attempts to insert a duplicate value, because a duplicate follows #BREAK# Chapter 19 Data Structures 1123 the same go left or go right decisions on each comparison as the original value did. Thus, the insertion operation eventually compares the duplicate with a node containing the same value. At this point, the insertion operation might simply discard the duplicate value. Searching a binary tree for a value that matches a key value is fast, especially for tightly packed trees. In a tightly packed tree, each level contains about twice as many elements as the previous level. Figure 19.19 is a tightly packed binary tree. A binary search tree with n elements has a minimum of log2n levels. Thus, at most log2n comparisons are required either to find a match or to determine that no match exists. Searching a (tightly packed) 1000-element binary search tree requires at most 10 comparisons, because 210 > 1000. Searching a (tightly packed) 1,000,000-element binary search tree requires at most 20 comparisons, because 220 > 1,000,000. The chapter exercises present algorithms for several other binary tree operations, such as deleting an item from a binary tree, printing a binary tree in a two-dimensional tree format and performing a level-order traversal of a binary tree. The level-order traversal of a binary tree visits the nodes of the tree row-by-row, starting at the root node level. On each level of the tree, a level-order traversal visits the nodes from left to right. Other binary tree exercises include allowing a binary search tree to contain duplicate values, inserting string values in a binary tree and determining how many levels are contained in a binary tree. SUMMARY Dynamic data structures can grow and shrink at execution time. Linked lists are collections of data items lined up in a row insertions and deletions can be made anywhere in a linked list. Stacks are important in compilers and operating systems insertions and deletions are made only at one end of a stack, its top. Queues represent waiting lines; insertions are made at the back (also referred to as the tail) of a queue and deletions are made from the front (also referred to as the head) of a queue. Binary trees facilitate high-speed searching and sorting of data, eliminating duplicate data items efficiently, representing file system directories and compiling expressions into machine language. A self-referential class contains a reference that refers to another object of the same class type. Self-referential objects can be linked together to form useful data structures such as lists, queues, stacks and trees. Creating and maintaining dynamic data structures requires dynamic memory allocation the ability for a program to obtain more memory space at execution time to hold new nodes and to release space no longer needed. The limit for dynamic memory allocation can be as large as the available physical memory in the computer or the amount of available disk space in a virtual-memory system. Often, the limits are much smaller because the computer s available memory must be shared among many users. Operator new takes as an operand the type of the object being dynamically allocated and returns a reference to a newly created object of that type. If no memory is available, new throws an Out- OfMemoryError. A linked list is a linear collection (i.e., a sequence) of self-referential class objects, called nodes, connected by reference links. A linked list is accessed via a reference to the first node of the list. Each subsequent node is accessed via the link-reference member stored in the previous node.
We recommend you use shared web hosting services, because many users agree that it is cheap, reliable and customer-satisfying webhost.