Quick File IO Combination Reference


Random Access File I/O

The RandomAccessFile can be used to read and write bytes, characters, and primitive types — but not objects. It provides a seek() method that allows you to go to any point within the file, forward or backward.

In this section I will present a comprehensive example of the use of the RandomAccessFile class. The task at hand will be to create a Java adapter to a legacy application flat-file data store. The complete project specification is given in figure 18-27.

image from book

                                         Project Specification Objectives:   - Demonstrate your ability to conduct file I/O operations with the RandomAccessFile class   - Demonstrate your ability to implement a non-trivial interface   - Demonstrate your ability to translate low-level exceptions into higher-level, user-defined, applica     tion-specific exception abstractions   - Demonstrate your ability to coordinate file I/O operations via object synchronization Tasks:   - You are a junior programmer working in the IT department of a retail bookstore. The CEO wants to begin     migrating legacy systems to the web using Java technology. A first step in this initiative is to create     Java adapters to existing legacy data stores. Given an interface definition, example legacy data file,     and legacy data file schema definition, write a Java class that serves as an adapter object to a legacy     data file. Given:   - Java interface file specifying adapter operations   - Legacy data file schema definition   - Example legacy data file Legacy Data File Schema Definition: The legacy data file contains three sections: 1) The file identification section is a two-byte value that identifies the file as a data file. 2) The schema description section immediately follows the first section and contains the field text name     and two-byte field length for each field in the data section. 3) The data section contains fixed-field-length record data elements arranged according to the following     schema: (length is in bytes)    Field Name      Length    Description    --------------|---------|----------------------------------------------------    deleted       |    1    | numeric - 0 if valid, 1 if deleted    --------------|---------|----------------------------------------------------    title         |   50    | text - book title    --------------|---------|----------------------------------------------------    author        |   50    | text - author full name    --------------|---------|----------------------------------------------------    pub_code      |    4    | numeric - publisher code    --------------|---------|----------------------------------------------------    ISBN          |   13    | text - International Standard Book Number    --------------|---------|----------------------------------------------------    price         |    8    | text - retail price in following format: $nnnn.nn    --------------|---------|----------------------------------------------------    qoh           |    4    | numeric - quantity on hand    --------------|---------|----------------------------------------------------

image from book

Figure 18-27: Legacy Data File Adapter Project Specification

As you can see from the project specification this is a fairly complex project. A Java adapter must be written to access a legacy system’s fixed-length record data files. You are given three artifacts to assist in your effort: 1) The data file schema definition is given in the project, 2) the example legacy data file is located on the accompanying disk or can be downloaded from www.pulpfreepress.com, and 3) a Java interface that provides declarations for the methods that must be implemented in the legacy data file adapter class. The interface code is listed in example 18.22

Example 18.22: LegacyDatafileInterface.java

image from book
 1     public interface LegacyDatafileInterface { 2 3       /** 4       * Read the record indicated by the rec_no and return a string array 5       * were each element contains a field value. 6       */ 7       public String[] readRecord(long rec_no) throws RecordNotFoundException; 8 9 10      /** 11       * Update a record's fields. The record must be locked with the lockRecord() 12       * method and the lock_token must be valid. The value for field n appears in 13       * element record[n]. 14       */ 15      public void updateRecord(long rec_no, String[] record, long lock_token) throws 16      RecordNotFoundException, SecurityException; 17 18 19      /** 20       * Marks a record for deletion by setting the deleted field to 1. The lock_token 21       * must be valid otherwise a SecurityException is thrown. 22       */ 23      public void deleteRecord(long rec_no, long lock_token) throws 24      RecordNotFoundException, SecurityException; 25 26 27      /** 28       * Creates a new datafile record and returns the record number. 29       */ 30      public long createRecord(String[] record) throws FailedRecordCreationException; 31 32 33      /** 34       * Locks a record for updates and deletes and returns an integer 35       * representing a lock token. 36       */ 37      public long lockRecord(long rec_no) throws RecordNotFoundException; 38 39 40      /** 41       * Unlocks a previously locked record. The lock_token must be valid or a 42       * SecurityException is thrown. 43       */ 44      public void unlockRecord(long rec_no, long lock_token) throws SecurityException; 45 46 47      /** 48       * Searches the records in the datafile for records that match the String 49       * values of search_criteria. search_criteria[n] contains the search value 50       * applied against field n. 51       */ 52      public long[] searchRecords(String[] search_criteria); 53 54    }//end interface definition
image from book

Referring to example 18.22 — the comments preceding each method declaration explain the functionality each method is required to provide. Most of the methods declare that they may throw exceptions that do not currently exist in the Java API. As part of the project you will have to create each of these exception classes. Figure 18-28 shows the contents of the example legacy data file as viewed with a text editor.

image from book
Figure 18-28: Contents of books.dat Example Legacy Datafile Viewed with Text Editor

Towards An Approach To The Adapter Project

Given the project specification and the three supporting artifacts you may be wondering where to begin. Using the guidance offered by the project-approach strategy in chapter 1, I recommend devoting some time to studying the schema definition and comparing it with what you see in the example data file. You will note that although some of the text appears to read OK, there are a few characters here and there that seem out of place. For instance, you can make out the header information but the header appears to start with a letter ‘z’. Studying the schema definition closely you note that the data file begins with a two-byte file identifier number. But what’s the value of this number?

Start Small And Take Baby Steps

One way to find out is to write a short program that reads the first two bytes of the file and converts it to a number. The RandomAccessFile class implements the DataInput and DataOutput interfaces. The DataInput interface has a method named readShort(). A Java short is a two-byte value. The readShort() method would be an excellent method to use to read the first two bytes of the file in an effort to determine their value.

The next phase of your discovery would be to try and read the rest of the file, or at least at first try and read the complete header and one complete record using the schema definition as a guide. You may find that a more detailed analysis of the header and record lengths are in order. Figure 18-29 shows a simple analysis performed with a spreadsheet.

image from book
Figure 18-29: Header and Record Length Analysis

Referring to figure 18-29 — the simple analysis reveals that the length of the header section of the legacy data file is 54 bytes long and each record is 130 bytes long. These figures, as well as the individual field lengths, will come in handy when the adapter is written.

Armed with some knowledge about the structure of the legacy data file and having gained some experience writing a small test program that reads all or portions of the file you can begin to create the adapter class incrementally. A good method to start with is the readRecord() method specified in the LegacyDatafileInterface.

Other Project Considerations

This section briefly discusses additional issues which must be considered during the project implementation phase. These considerations include 1) record locking during updates and deletes, and 2) translating low-level I/O exceptions into higher level exceptions as specified in the interface.

Locking A Record For Updates And Deletes

The LegacyDatafileInterface specifies that a record must be locked when it is being updated or deleted. The locking is done via a lock token, which is nothing more that a primitive-type long value. How might the locking mechanism be implemented? How is the lock_token generated?

To implement the locking mechanism you must thoroughly understand threads and thread synchronization. (These topics are covered in detail in chapter 16.) Any object can be used as a synchronization point by using the synchronized keyword. The adapter must ensure that if one thread attempts to update or delete a record (by calling the updateRecord() or deleteRecord() methods) it cannot do so while another thread is in the process of calling either of those methods.

You can adopt several strategies as a means to an ends here. You can 1) apply the synchronized keyword to the entire method in question (updateRecord() and deleteRecord()) or 2) synchronize only the critical section of code within each method. Within the synchronized block you implement logic to check for a particular condition. If the condition holds you can proceed with whatever it is you need to do. If the condition does not hold, you will have to wait until it does. You do this by calling the wait() method on the object you have selected to synchronize on. The wait() method adds the current thread to a list of threads waiting to get a lock on that object.

Conversely, when a thread has obtained a lock on an object and it concludes its business, it must release its lock and notify the object upon which it is synchronized that it can wake up one of the other waiting threads. This is done by calling the notify() or notifyAll() method on the synchronized object.

Once the thread synchronization scheme is determined the lock_token can be generated with the java.util.Random class.

Translating Low-Level Exceptions Into Higher-Level Exception Abstractions

The java.io package defines several low-level exceptions that can occur when conducting file I/O operations. These exceptions must be handled in the adapter, however, the LegacyDatafileInterface specifies that several higher-level exceptions may be thrown when its methods are called.

To create custom exceptions you extend the Exception class and add any customized behavior required. (Exceptions are discussed in detail in chapter 15.) In your adapter code, you will catch and handle the low-level exceptions when they occur, and then repackage the exception within the context of a custom exception, and then throw the custom exception. Any objects utilizing the services of the adapter class must handle your custom exceptions, not the low-level I/O exceptions.

Where To Go From Here

The previous sections attempted to address some of the development issues you will typically encounter when attempting this type of project. The purpose of the project is to demonstrate the use of the RandomAccessFile class in the context of a non-trivial example. I hope also that I have sufficiently illustrated the reality that rarely can one class perform its job without the help of many other classes.

The next section gives the code for the completed project. Keep in mind that the examples listed here represent one particular approach and solution to the problem. As an exercise you will be invited to attempt a solution on your own terms using the knowledge gained here as a guide.

Explore and study the code. Compile the code and observe its operation. Experiment — make changes to areas you feel can use improvement.

Complete RandomAccessFile Legacy Datafile Adapter Source Code Listing

This section gives the complete listing for the code that satisfies the requirements of the legacy data file adapter project using the RandomAccessFile class. The LegacyDatafileInterface listing given in example 18.22 is not repeated here.

Example 18.23: DataFileAdapter.java

image from book
 1     import java.io.*; 2     import java.util.*; 3 4 5     public class DataFileAdapter implements LegacyDatafileInterface { 6 7         /** 8         * Class Constants 9         */ 10 11        private static final short FILE_IDENTIFIER = 378; 12        private static final int HEADER_LENGTH   =  54; 13        private static final int RECORDS_START   =  54; 14        private static final int RECORD_LENGTH   = 130; 15        private static final int FIELD_COUNT     =   7; 16 17        private static final short DELETED_FIELD_LENGTH  =  1; 18        private static final short TITLE_FIELD_LENGTH    = 50; 19        private static final short AUTHOR_FIELD_LENGTH   = 50; 20        private static final short PUB_CODE_FIELD_LENGTH =  4; 21        private static final short ISBN_FIELD_LENGTH     = 13; 22        private static final short PRICE_FIELD_LENGTH    =  8; 23        private static final short QOH_FIELD_LENGTH      =  4; 24 25        private static final String DELETED_STRING  = "deleted"; 26        private static final String TITLE_STRING    = "title"; 27        private static final String AUTHOR_STRING   = "author"; 28        private static final String PUB_CODE_STRING = "pub_code"; 29        private static final String ISBN_STRING     = "ISBN"; 30        private static final String PRICE_STRING    = "price"; 31        private static final String QOH_STRING      = "qoh"; 32 33        private static final int TITLE_FIELD    = 0; 34        private static final int AUTHOR_FIELD   = 1; 35        private static final int PUB_CODE_FIELD = 2; 36        private static final int ISBN_FIELD     = 3; 37        private static final int PRICE_FIELD    = 4; 38        private static final int QOH_FIELD      = 5; 39 40        private static final int VALID   = 0; 41        private static final int DELETED = 1; 42 43        /** 44        * Field offsets used with String.substring() method 45        * Note: These are not used in the final program! 46        */ 47        private static final int DELETED_START_OFFSET  =   0; 48        private static final int DELETED_STOP_OFFSET   =   1; 49        private static final int TITLE_START_OFFSET    =   1; 50        private static final int TITLE_STOP_OFFSET     =  51; 51        private static final int AUTHOR_START_OFFSET   =  51; 52        private static final int AUTHOR_STOP_OFFSET    = 101; 53        private static final int PUB_CODE_START_OFFSET = 101; 54        private static final int PUB_CODE_STOP_OFFEST  = 105; 55        private static final int ISBN_START_OFFSET     = 105; 56        private static final int ISBN_STOP_OFFSET      = 118; 57        private static final int PRICE_START_OFFSET    = 118; 58        private static final int PRICE_STOP_OFFSET     = 126; 59        private static final int QOH_START_OFFSET      = 126; 60        private static final int QOH_STOP_OFFSET       = 130; 61 62        /** 63        * Instance Fields 64        */ 65 66        private File             _filename               = null; 67        private RandomAccessFile _raf                    = null; 68        private long        _record_count            = 0; 69        private HashMap        _locked_records_map       = null; 70        private Random         _token_maker              = null; 71        private long            _current_record_number   = 0; 72 73 74        /** 75        * Constructor 76        * 77        */ 78         public DataFileAdapter(String filename) throws InvalidDataFileException { 79            try{ 80            _filename = new File(filename); 81            _raf = new RandomAccessFile(_filename, "rw"); 82            _raf.seek(0); 83            if((_raf.length() >= HEADER_LENGTH) && 84                              (_raf.readShort() == FILE_IDENTIFIER)){ // it's a valid data file 85                System.out.println("Setting up data file for I/O operations"); 86                _record_count = ((_raf.length() - HEADER_LENGTH) / RECORD_LENGTH); 87                _current_record_number = 0; 88                _locked_records_map = new HashMap(); 89                _token_maker = new Random(); 90              }else if(_raf.length() == 0) { // it's an empty file - make it a data file 91                      _raf.seek(0); 92                      _raf.writeShort(FILE_IDENTIFIER); 93                      _raf.writeBytes(DELETED_STRING); 94                      _raf.writeShort(DELETED_FIELD_LENGTH); 95                      _raf.writeBytes(TITLE_STRING); 96                      _raf.writeShort(TITLE_FIELD_LENGTH); 97                      _raf.writeBytes(AUTHOR_STRING); 98                      _raf.writeShort(AUTHOR_FIELD_LENGTH); 99                      _raf.writeBytes(PUB_CODE_STRING); 100                     _raf.writeShort(PUB_CODE_FIELD_LENGTH); 101                     _raf.writeBytes(ISBN_STRING); 102                     _raf.writeShort(ISBN_FIELD_LENGTH); 103                     _raf.writeBytes(PRICE_STRING); 104                     _raf.writeShort(PRICE_FIELD_LENGTH); 105                     _raf.writeBytes(QOH_STRING); 106                     _raf.writeShort(QOH_FIELD_LENGTH); 107 108                     _record_count = 0; 109                     _locked_records_map = new HashMap(); 110                     _token_maker = new Random(); 111 112                   } else { 113                          _raf.seek(0); 114                          if(_raf.readShort() != FILE_IDENTIFIER){ 115                             _raf.close(); 116                             System.out.println("Invalid data file. Closing file."); 117                             throw new InvalidDataFileException(null); 118                           } 119                         } 120 121           }catch(IOException e){ 122              System.out.println("Problem opening or creating data file."); 123              System.out.println(e.toString()); 124             } 125 126        } // end constructor 127 128 129        /** 130        * Default constructor 131        * 132        */ 133        public DataFileAdapter() throws InvalidDataFileException { 134           this("books.dat"); 135        } 136 137 138 139         /** 140         * CreateNewDataFile 141         * 142         */ 143         public void createNewDataFile(String filename) throws NewDataFileException{ 144           try{ 145               if(_raf != null){ 146                 _raf.close(); 147               } 148               _raf = new RandomAccessFile(filename, "rw"); 149 150               _raf.seek(0); 151               _raf.writeShort(FILE_IDENTIFIER); 152               _raf.writeBytes(DELETED_STRING); 153               _raf.writeShort(DELETED_FIELD_LENGTH); 154               _raf.writeBytes(TITLE_STRING); 155               _raf.writeShort(TITLE_FIELD_LENGTH); 156               _raf.writeBytes(AUTHOR_STRING); 157               _raf.writeShort(AUTHOR_FIELD_LENGTH); 158               _raf.writeBytes(PUB_CODE_STRING); 159               _raf.writeShort(PUB_CODE_FIELD_LENGTH); 160               _raf.writeBytes(ISBN_STRING); 161               _raf.writeShort(ISBN_FIELD_LENGTH); 162               _raf.writeBytes(PRICE_STRING); 163               _raf.writeShort(PRICE_FIELD_LENGTH); 164               _raf.writeBytes(QOH_STRING); 165               _raf.writeShort(QOH_FIELD_LENGTH); 166 167               _record_count = 0; 168               _locked_records_map = new HashMap(); 169               _token_maker = new Random(); 170 171              }catch(IOException e){ 172                   System.out.println(e.toString()); 173                   throw new NewDataFileException(e); 174                   } 175 176         } // end createNewDataFile method 177 178 179 180 181         /** 182         *  OpenDataFile 183         * 184         */ 185         public void openDataFile(String filename) throws InvalidDataFileException { 186            try{ 187               if(_raf != null){ 188               _raf.close(); 189               } 190               _filename = new File(filename); 191               _raf = new RandomAccessFile(_filename, "rw"); 192               _raf.seek(0); 193               if((_raf.length() >= HEADER_LENGTH) && 194                               (_raf.readShort() == FILE_IDENTIFIER)){ // it's a valid data file 195                   System.out.println("Setting up data file for I/O operations"); 196                   _record_count = ((_raf.length() - HEADER_LENGTH) / RECORD_LENGTH); 197                   _current_record_number = 0; 198                   _locked_records_map = new HashMap(); 199                   _token_maker = new Random(); 200                 }else if(_raf.length() == 0) { // it's an empty file - make it a data file 201                         _raf.seek(0); 202                         _raf.writeShort(FILE_IDENTIFIER); 203                         _raf.writeBytes(DELETED_STRING); 204                         _raf.writeShort(DELETED_FIELD_LENGTH); 205                         _raf.writeBytes(TITLE_STRING); 206                         _raf.writeShort(TITLE_FIELD_LENGTH); 207                         _raf.writeBytes(AUTHOR_STRING); 208                         _raf.writeShort(AUTHOR_FIELD_LENGTH); 209                         _raf.writeBytes(PUB_CODE_STRING); 210                         _raf.writeShort(PUB_CODE_FIELD_LENGTH); 211                         _raf.writeBytes(ISBN_STRING); 212                         _raf.writeShort(ISBN_FIELD_LENGTH); 213                         _raf.writeBytes(PRICE_STRING); 214                         _raf.writeShort(PRICE_FIELD_LENGTH); 215                         _raf.writeBytes(QOH_STRING); 216                         _raf.writeShort(QOH_FIELD_LENGTH); 217 218                         _record_count = 0; 219                         _locked_records_map = new HashMap(); 220                         _token_maker = new Random(); 221 222                     } else { 223                            _raf.seek(0); 224                            if(_raf.readShort() != FILE_IDENTIFIER){ 225                               _raf.close(); 226                               System.out.println("Invalid data file. Closing file."); 227                               throw new InvalidDataFileException(null); 228                             } 229                           } 230 231                }catch(IOException e){ 232                   System.out.println("Problem opening or creating data file."); 233                   System.out.println(e.toString()); 234                   } 235 236            } // end openDataFile method 237 238 239 240 241         /** 242         *  Read the record indicated by the rec_no and return a string array 243         *  were each element contains a field value. 244         */ 245          public String[] readRecord(long rec_no) throws RecordNotFoundException { 246              String[] temp_string = null; 247              if((rec_no < 0) || (rec_no > _record_count)){ 248                 System.out.println("Requested record out of range!"); 249                 throw new RecordNotFoundException("Requested record out of range", null); 250              }else{ 251                     try{ 252                         gotoRecordNumber(rec_no); 253                         if(_raf.readByte() == DELETED){ 254                           System.out.println("Record has been deleted!"); 255                           throw new RecordNotFoundException("Record " + rec_no + " deleted!", null); 256                         }else{ 257                              temp_string = recordBytesToStringArray(rec_no); 258                          } 259                         }catch(IOException e){ 260                            System.out.println(e.toString()); 261                            throw new RecordNotFoundException("Problem in readRecord method", e); 262                          } 263                     } // end else 264                return temp_string; 265           } // end readRecord() 266 267 268 269 270           /** 271            *  Update a record's fields. The record must be locked with the lockRecord() 272            *  method and the lock_token must be valid. The value for field n appears in 273            *  element record[n]. The call to updateRecord() MUST be preceeded by a call 274            *  to lockRecord() and followed by a call to unlockRecord() 275            */ 276           public void updateRecord(long rec_no, String[] record, long lock_token) throws 277           RecordNotFoundException, SecurityException{ 278                  if(lock_token != ((Long)_locked_records_map.get(new Long(rec_no))).longValue()){ 279                     System.out.println("Invalid                                                                      update record lock token."); 280                     throw new SecurityException("Invalid update record lock token.", null); 281                  }else{ 282                       try{ 283                  gotoRecordNumber(rec_no); //i.e., goto indicated record 284                  _raf.writeByte((byte)0); 285                  _raf.write(stringToPaddedByteField(record[TITLE_FIELD], TITLE_FIELD_LENGTH)); 286                  _raf.write(stringToPaddedByteField(record[AUTHOR_FIELD], AUTHOR_FIELD_LENGTH)); 287                  _raf.writeInt(Integer.parseInt(record[PUB_CODE_FIELD])); 288                  _raf.write(stringToPaddedByteField(record[ISBN_FIELD], ISBN_FIELD_LENGTH)); 289                  _raf.write(stringToPaddedByteField(record[PRICE_FIELD], PRICE_FIELD_LENGTH)); 290                  _raf.writeInt(Integer.parseInt(record[QOH_FIELD])); 291                  _current_record_number = rec_no; 292              }catch(IOException e){ 293                                System.out.println("updateRecord(): Problem writing record information."); 294                     System.out.println(e.toString()); 295               } 296               catch(NumberFormatException e){ 297                              System.out.println("updateRecord(): Problem converting Strings to numbers."); 298                    System.out.println(e.toString()); 299               } 300                       catch(Exception e){ 301                         System.out.println("updateRecord(): Exception occured."); 302                         System.out.println(e.toString()); 303                       } 304                    }// end else 305           }// end updateRecord() 306 307 308 309 310           /** 311            * Marks a record for deletion by setting the deleted field to 1. The lock_token 312            * must be valid otherwise a SecurityException is thrown. 313            */ 314          public void deleteRecord(long rec_no, long lock_token) throws 315          RecordNotFoundException, SecurityException { 316                if(lock_token != ((Long)_locked_records_map.get(new Long(rec_no))).longValue()){ 317                   System.out.println("Invalid delete record lock token."); 318                   throw new SecurityException("Invalid delete record lock token.", null); 319                }else{ 320                      try{ 321                          gotoRecordNumber(rec_no); // goto record indicated 322                          _raf.writeByte((byte)1); // mark for deletion 323 324                         }catch (IOException e){ 325                            System.out.println("deleteRecord(): " + e.toString()); 326                          } 327                  }// end else 328                 }// end deleteRecord() 329 330 331 332 333 334          /** 335           * Creates a new datafile record and returns the record number. 336           */ 337          public long createRecord(String[] record) throws FailedRecordCreationException { 338            try{ 339                gotoRecordNumber(_record_count); //i.e., goto end of file 340                _raf.writeByte((byte)0); 341                _raf.write(stringToPaddedByteField(record[TITLE_FIELD], TITLE_FIELD_LENGTH)); 342                _raf.write(stringToPaddedByteField(record[AUTHOR_FIELD], AUTHOR_FIELD_LENGTH)); 343                _raf.writeInt(Integer.parseInt(record[PUB_CODE_FIELD])); 344                _raf.write(stringToPaddedByteField(record[ISBN_FIELD], ISBN_FIELD_LENGTH)); 345                _raf.write(stringToPaddedByteField(record[PRICE_FIELD], PRICE_FIELD_LENGTH)); 346                _raf.writeInt(Integer.parseInt(record[QOH_FIELD])); 347                _current_record_number = ++_record_count; 348            }catch(IOException e){ 349                   System.out.println(e.toString()); 350                   throw new FailedRecordCreationException("IOException in createRecord method", e); 351              } 352              catch(NumberFormatException e){ 353                   System.out.println(e.toString()); 354                   throw new FailedRecordCreationException("NumberFormatException in createRecord", e); 355              } 356              catch(RecordNotFoundException e){ 357                   System.out.println(e.toString()); 358                   throw new FailedRecordCreationException("RecordNotFoundException in createRecord", e); 359              } 360            return _current_record_number; 361          } 362 363 364 365 366          /** 367           * Locks a record for updates and deletes and returns an integer 368           * representing a lock token. 369           */ 370          public long lockRecord(long rec_no) throws RecordNotFoundException { 371                 long lock_token = 0; 372                 if((rec_no < 0) || (rec_no > _record_count)){ 373                   System.out.println("Record cannot be locked. Not in valid range."); 374                  throw new RecordNotFoundException("Record cannot be locked. Not in valid range.", null); 375                 }else synchronized(_locked_records_map){ 376                       while(_locked_records_map.containsKey(new Long(rec_no))){ 377                         try{ 378                             _locked_records_map.wait(); 379                            }catch(InterruptedException ignored){ } 380                       } 381                        lock_token = _token_maker.nextLong(); 382                        _locked_records_map.put(new Long(rec_no), new Long(lock_token)); 383                   } 384 385               return lock_token; 386          } // end lockRecord method 387 388 389 390 391          /** 392          * Unlocks a previously locked record. The lock_token must be valid or a 393          * SecurityException is thrown. 394          */ 395          public void unlockRecord(long rec_no, long lock_token) throws SecurityException { 396            synchronized(_locked_records_map){ 397              if(_locked_records_map.containsKey(new Long(rec_no))){ 398                 if(lock_token == ((Long)_locked_records_map.get(new Long(rec_no))).longValue()){ 399                   _locked_records_map.remove(new Long(rec_no)); 400                   _locked_records_map.notifyAll(); 401                 }else{ 402                    System.out.println("Invalid lock token."); 403                    throw new SecurityException("Invalid lock token", null); 404                   } 405              }else{ 406                    System.out.println("Invalid record unlock key."); 407                    throw new SecurityException("Invalid record unlock key.", null); 408                  } 409           } 410          }// end unlockRecord method 411 412 413 414 415          /** 416           * Searches the records in the datafile for records that match the String 417           * values of search_criteria. search_criteria[n] contains the search value 418           * applied against field n. Data files can be searched for Title & Author. 419           * 420           */ 421           public long[] searchRecords(String[] search_criteria){ 422             Vector hit_vector = new Vector(); 423             for(int i=0; i<_record_count; i++){ 424               try{ 425                  if(thereIsAMatch(search_criteria, readRecord(i))){ 426                    hit_vector.addElement(new Long(i)); 427                  } 428                }catch(RecordNotFoundException ignored){ } // ignore deleted records 429             } // end for 430             long hits[] = new long[hit_vector.size()]; 431             for(int i=0; i<hits.length; i++){ 432               hits[i] = ((Long)hit_vector.elementAt(i)).longValue(); 433             } 434            return hits; 435           } 436 437 438 439 440 441           /** 442           * thereIsAMatch() is a utility method that actually performs 443           * the record search. Implements an implied OR/AND search by detecting 444           * the first character of the Title criteria element. 445           */ 446           private boolean thereIsAMatch(String[] search_criteria, String[] record){ 447           boolean match_result = false; 448             final int TITLE  = 0; 449             final int AUTHOR = 1; 450             for(int i=0; i<search_criteria.length; i++){ 451               if((search_criteria[i].length() == 0) || (record[i+1].startsWith(search_criteria[i]))){ 452                  match_result = true; 453                  break; 454               } //end if 455             } //end for 456 457             if(((search_criteria[TITLE].length() > 1) && (search_criteria[AUTHOR].length() >= 1)) && 458                                                            (search_criteria[TITLE].charAt(0) == '&')) { 459                 if(record[TITLE+1].startsWith(search_criteria[TITLE].substring(1, search_criteria[TITLE].length()).trim()) && record[AUTHOR+1].startsWith(search_criteria[AUTHOR])){ 460                   match_result = true; 461                }else { 462                    match_result = false; 463                 } 464 465             } // end if 466           return match_result; 467          } // end thereIsAMatch() 468 469 470 471 472           /** 473           * gotoRecordNumber - utility function that handles the messy 474           * details of seeking a particular record. 475           * 476           */ 477           private void gotoRecordNumber(long record_number) throws RecordNotFoundException { 478             if((record_number < 0) || (record_number > _record_count)){ 479                throw new RecordNotFoundException(null); 480             }else{ 481                  try{ 482                     _raf.seek(RECORDS_START + (record_number * RECORD_LENGTH)); 483 484                     }catch(IOException e){ 485                         System.out.println(e.toString()); 486                         throw new RecordNotFoundException(e); 487                       } 488                   }// end else 489 490            } // end gotoRecordNumber method 491 492 493 494            /** 495            * stringToPaddedByteField - pads the field to maintain fixed 496            * field length. 497            * 498            */ 499            protected byte[] stringToPaddedByteField(String s, int field_length){ 500              byte[] byte_field = new byte[field_length]; 501              if(s.length() <= field_length){ 502                for(int i = 0; i<s.length(); i++){ 503                  byte_field[i] = (byte)s.charAt(i); 504                } 505                for(int i = s.length(); i<field_length; i++){ 506                  byte_field[i] = (byte)' '; //pad the field 507                } 508               }else{ 509                    for(int i = 0; i<field_length; i++){ 510                      byte_field[i] = (byte)s.charAt(i); 511                    } 512               } 513               return byte_field; 514            } // end stringToPaddedByteField() 515 516 517         /** 518          * recordBytesToStringArray - reads an array of bytes from a data file 519          * and converts them to an array of Strings. The first element of the 520          * returned array is the record number. The length of the byte array 521          * argument is RECORD_LENGTH -1. 522          * 523          */ 524          private String[] recordBytesToStringArray(long record_number){ 525            String record_string = null; 526            String[] string_array = new String[FIELD_COUNT]; 527            byte[] title = new byte[TITLE_FIELD_LENGTH]; 528            byte[] author = new byte[AUTHOR_FIELD_LENGTH]; 529            byte[] isbn = new byte[ISBN_FIELD_LENGTH]; 530            byte[] price = new byte[PRICE_FIELD_LENGTH]; 531 532            try{ 533               string_array[0] = Long.toString(record_number); 534               _raf.read(title); 535               string_array[TITLE_FIELD + 1] = new String(title).trim(); 536               _raf.read(author); 537               string_array[AUTHOR_FIELD + 1] = new String(author).trim(); 538               string_array[PUB_CODE_FIELD + 1] = Integer.toString(_raf.readInt()); 539               _raf.read(isbn); 540               string_array[ISBN_FIELD + 1] = new String(isbn); 541               _raf.read(price); 542               string_array[PRICE_FIELD + 1] = new String(price).trim(); 543               string_array[QOH_FIELD + 1] = Integer.toString(_raf.readInt()); 544              }catch(IOException e){ 545                  System.out.println(e.toString()); 546                } 547            return string_array; 548          } // end recordBytesToStringArray() 549 550 551 552          /** 553          * readHeader - reads the header bytes and converts them to 554          * a string 555          * 556          */ 557          public String readHeader(){ 558            StringBuffer sb = new StringBuffer(); 559            byte[] deleted                                                                                   = new byte[DELETED_STRING.length()]; 560            byte[] title                                                                                                                            = new byte[TITLE_STRING.length()]; 561            byte[] author                                                                                     = new byte[AUTHOR_STRING.length()]; 562            byte[] pub_code = new byte[PUB_CODE_STRING.length()]; 563            byte[] isbn                                                                                                                              = new byte[ISBN_STRING.length()]; 564            byte[] price                                                                                                                            = new byte[PRICE_STRING.length()]; 565            byte[] qoh                                                                                                                                                                                    = new byte[QOH_STRING.length()]; 566            try{ 567               _raf.seek(0); 568               sb.append(Short.toString(_raf.readShort()) + " "); 569               _raf.read(deleted); 570               sb.append(new String(deleted) + " "); 571               sb.append(Short.toString(_raf.readShort()) + " "); 572               _raf.read(title); 573               sb.append(new String(title) + " "); 574               sb.append(Short.toString(_raf.readShort()) + " "); 575               _raf.read(author); 576               sb.append(new String(author) + " "); 577               sb.append(Short.toString(_raf.readShort()) + " "); 578               _raf.read(pub_code); 579               sb.append(new String(pub_code) + " "); 580               sb.append(Short.toString(_raf.readShort()) + " "); 581               _raf.read(isbn); 582               sb.append(new String(isbn) + " "); 583               sb.append(Short.toString(_raf.readShort()) + " "); 584               _raf.read(price); 585               sb.append(new String(price) + " "); 586               sb.append(Short.toString(_raf.readShort()) + " "); 587               _raf.read(qoh); 588               sb.append(new String(qoh) + " "); 589               sb.append(Short.toString(_raf.readShort()) + " "); 590               }catch(IOException e){ 591                   System.out.println(e.toString()); 592                } 593            return sb.toString(); 594          } // end readHeader() 595 596          /** 597           * getRecordCount() returns the number of records contained in the data file 598           * 599           */ 600           public long getRecordCount(){ return _record_count; } 601 602       } // end DataFileAdapter class definition
image from book

Example 18.24: FailedRecordCreationException.java

image from book
 1     public class FailedRecordCreationException extends Exception { 2 3       /***************************************************** 4       *  Constructor that takes a String argument 5       *****************************************************/ 6       public FailedRecordCreationException(String message, Throwable cause){ 7          super(message, cause); 8       } 9 10      /******************************************** 11      * Constructor that takes Throwable argument 12      ********************************************/ 13      public FailedRecordCreationException(Throwable cause){ 14        this("Failed Record Creation Exception", cause); 15      } 16 17    }
image from book

Example 18.25: InvalidDataFileException.java

image from book
 1     public class InvalidDataFileException extends Exception { 2 3     /************************************************************ 4     *   Constructor - Takes one String argument 5     ************************************************************/ 6       public InvalidDataFileException(String message, Throwable cause){ 7          super(message, cause); 8       } 9 10 11     /************************************************************ 12     *   Constructor that takes Throwable argument 13     ************************************************************/ 14        public InvalidDataFileException(Throwable cause){ 15          this("Invalid Data File Exception", cause); 16        } 17     }
image from book

Example 18.26: NewDataFileException.java

image from book
 1     public class NewDataFileException extends Exception { 2 3     /************************************************************ 4     *   Constructor - One String argument 5     ************************************************************/ 6       public NewDataFileException(String message, Throwable cause){ 7          super(message, cause); 8       } 9 10 11    /************************************************************ 12    *    Constructor that takes Throwable argument 13    ************************************************************/ 14       public NewDataFileException(Throwable cause){ 15         this("New Data File Exception", cause); 16       } 17     }
image from book

Example 18.27: RecordNotFoundException.java

image from book
 1     public class RecordNotFoundException extends Exception { 2 3       /************************************************************ 4      *  Constructor - Takes one String argument 5      ************************************************************/ 6        public RecordNotFoundException(String message, Throwable cause){ 7              super(message, cause); 8        } 9 10    /************************************************************ 11    *  Constructor that takes Throwable argument 12    ************************************************************/ 13      public RecordNotFoundException(Throwable cause){ 14          this("Record Not Found Exception", cause); 15      } 16    }
image from book

Example 18.28: SecurityException.java

image from book
 1     public class SecurityException extends Exception { 2 3     /************************************************************ 4     *  Constructor - Takes one String argument 5     ************************************************************/ 6        public SecurityException(String message, Throwable cause){ 7           super(message, cause); 8        } 9 10    /************************************************************ 11    *  Constructor that takes a Throwable argument 12    ************************************************************/ 13      public SecurityException(Throwable cause){ 14          this("Security Exception", cause); 15      } 16    }
image from book

Example 18.29: AdapterTesterApp.java

image from book
 1     public class AdapterTesterApp { 2        public static void main(String[] args){ 3          try{ 4          DataFileAdapter adapter = new DataFileAdapter(); 5          String[] rec_1 = {"C++ For Artists", "Rick Miller", "23", "1-932504-02-8", "$59.95", "80"}; 6          String[] rec_2 = {"Java For Artists", "Rick Miller", "23", "1-932504-04-X", "$69.95", "100"}; 7 8          String[] search_string = {"Java", " "}; 9 10         String[] temp_string = null; 11 12            //adapter.createNewDataFile("books.dat"); 13            adapter.createRecord(rec_1); 14            adapter.createRecord(rec_1); 15            adapter.createRecord(rec_1); 16            adapter.createRecord(rec_1); 17            adapter.createRecord(rec_1); 18            adapter.createRecord(rec_1); 19            adapter.createRecord(rec_1); 20            adapter.createRecord(rec_1); 21 22            long lock_token = adapter.lockRecord(2); 23 24            adapter.updateRecord(2, rec_2, lock_token); 25            adapter.unlockRecord(2, lock_token); 26 27            lock_token = adapter.lockRecord(1); 28            adapter.deleteRecord(1, lock_token); 29            adapter.unlockRecord(1, lock_token); 30 31 32 33            long[] search_hits = adapter.searchRecords(search_string); 34 35 36            System.out.println(adapter.readHeader()); 37 38            for(int i=0; i<search_hits.length; i++){ 39              try{ 40              temp_string = adapter.readRecord(search_hits[i]); 41              for(int j = 0; j<temp_string.length; j++){ 42                System.out.print(temp_string[j]+" "); 43              } 44              System.out.println(); 45               }catch(RecordNotFoundException  e){ System.out.println(e.toString()); } 46           } 47 48           }catch(Exception e){ e.printStackTrace(); } 49 50       } 51    }
image from book

Referring to example 18.29 — the AdapterTesterApp class tests the functionality of the DataFileAdapter class and its supporting classes. A DataFileAdapter object is created on line 4. On lines 5 and 6 two String arrays are created that contain the field values for records that will be inserted into the legacy data file for testing purposes. A String array named search_string is created on line 8. This array will be used to test the searchRecords() method.

The statement on line 12 is commented out, but was initially used to test the createNewDataFile() method. Lines 13 through 20 insert a series of record values using the data contained in the rec_1 array.

On line 22 the lockRecord() method is tested and the lock_token obtained is used on lines 24 and 25 to test the updateRecord() and unlockRecord() methods.

The searchRecords() method is tested on line 33 and the resulting array of longs is used in the for statement beginning on line 38 to print the record data for each record returned in the search. Figure 18-30 shows the results of running this program. It should be noted that each time the program is executed in its current version additional records with the data contained in the rec_1 array will be appended to the end of the file.

image from book
Figure 18-30: Results of Running Example 18.29.

Quick Review

The RandomAccessFile class is used to write and read bytes, characters, and primitive type values to and from any position within a file. The RandomAccessFile stands alone in that it is not related to the InputStream, OutputStream, Reader, or Writer classes.




Java For Artists(c) The Art, Philosophy, and Science of Object-Oriented Programming
Java For Artists: The Art, Philosophy, And Science Of Object-Oriented Programming
ISBN: 1932504052
EAN: 2147483647
Year: 2007
Pages: 452

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net