School Locker Database - a Mini-project with RandomAccessFile

Investigation

Our school has lockers - one for each student in grades 6-12. Each homeroom teacher keeps track of the locker assignments by writing them on a sheet of paper.

Before starting to write the program, you should do a more extensive investigation. You should try to answer most of the following questions:

  -  How many lockers are there?
  -  How are they numbered?
  -  Do any lockers have 2 students assigned?
  -  Do any students have 2 lockers?
  -  Are the combinations written down?
  -  Who is "in charge" of the lockers?
  -  Are there problems with the current system that can be handled better using a computer program?
  -  It's good to collect some sample documents  - e.g. a homeroom teacher's paper list

People's Tasks

What tasks must the people (e.g. teachers, administrators) perform?  For example:

  -  Assign a set of lockers to a homeroom teacher
  -  Assign a locker to a student
  -  Assign a locker to a new student, by first finding an empty locker
  -  Unassign a locker when a student leaves the school.
  -  Open a locker in an emergency
  -   ...  more tasks ... especially any new tasks to be performed differently in the computerized version

Algorithms

What algorithms should the program be able to perform?  These must adequately support the tasks listed above.

  -  Input and record a name for a locker
  -  Erase the name from an assigned locker (unassign)
  -  Find the number of an empty locker
  -  Search for a particular name and print the corresponding locker number
  -  Print out all the assigned locker numbers with names
  -  Sort the list of assigned lockers alphabetically
  -  Save all the names and other data in a file on the disk drive
  -  ... more algorithms ...

Data Representations and Data-Structures

The investigation turned up that there are two sets of lockers - one set for the middle school (on the first floor) and another set for the high school (on the second floor).  Both sets start counting at #1.  So the representation will need to make a distinction.  They could be called 1001, 1002, ... for the middle school and then 2001, 2002, ... for the high school.  This allows all lockers to be saved in a single list with no confusion.

The list can be stored in a 2-D array, like this:

Row

Name

Homeroom

Combination

1001
1002
1003
...
...
2001
2002
2003
...

Luke, Lucky
Adams, Annie
Chaplin, Charlie
...
...
Baker, Bobbie
Carter, Billy
Davis, Dave
...

Mr Smith
Mr Smith
Mr Smith
...
...
Mr Smith
Mr Smith
Mr Smith
...

1-1-1
3-2-1
7-4-3
...
...
none
none
none
...

Interface

The LAST step is to consider the interface. This sits in the center of everything, connecting the user to the algorithms. The interface must contain appropriate controls for doing data input/output and to allow the user to control the program. A very, very simple interface will be used in the feasibility prototype below, supporting only a small part of the actual requirements:




Feasibility Prototype

// a simplified program to check that storing in a 2-D array will work properly //

import java.awt.*;     // contains GUI controls
import java.io.*;      // contains data-file commands - for later

public class Lockers extends EasyApp
{
   public static void main(String[] args)
   {  new Lockers();  }
   
   Button bClear = addButton("Clear All",50,50,100,50,this);
   Button bAssign = addButton("Assign",150,50,100,50,this);
   Button bShowNames = addButton("Show Names",250,50,100,50,this);
   
   String[][] list = new String[3000][3]; //
3 columns for name, HR, combination
   
   public Lockers()
   {  setSize(400,150);
      setTitle("Lockers Database");
   }      
   
   public void actions(Object source,String command)
   {  
      
if (
source == bClear)
      {  clearList();  }
      else if (source == bAssign)
      {  assignLocker();  }
      else if (source == bShowNames)
      {  showNames(); }
   }
   
   public void clearList()    // Fills array with empty Strings
   {
      for (int row = 0; row < 3000; row = row+1)
      {
         for (int col = 0; col < 3; col = col+1)
         {
            list[row][col] = "";
         }
      }
   }
   
   public void assignLocker()   // Put student name into list
   {  
      
String name = input("Type the name of the student");
      int locker = inputInt("Type the locker number:");
      list[locker][0] = name;
   }
   
   public void showNames()   // Show non-blankc names and lockers
   {             
      for (int row = 0; row < 3000; row = row + 1)
      {  
         if ( !list[row][0].equals("") )
         {  System.out.println(row + "\t" + list[row][0]); }
      }
   }
}


Modular System Design

This diagram shows that a computer system is a lot more than just the program, so developers must pay attention to a lot more pieces than programmers.



Expansion

After the initial idea, and a very simple prototype program, we need to talk to the intended user.
That would be the vice principal(s).

Talking to the Intended User

A sophisticated computer user might tell you exactly what they need.  Most users aren't that logical, so you need to have an active conversation with them - asking questions, suggesting solutions, recording all the ideas ON PAPER (or in your computer if you have one along).

Most users respond well to requests for stories (scenarios).  For example:


The conversation above can be summarized in a story (scenario / use-case):

This leads to a need:

This leads to several goals:    

This leads to some thoughts about how to do this:


** Caution **  Remember, you cannot do everything.  Sometimes you need to say "no" to the user, when they ask for something that is silly, useless, or impossible, e.g.:

User : "Can you make a program control the locker's locks directly, like those plastic cards you get for hotel rooms?"

You say : "No, we can't do that.  We'd need to wire all the lockers - I don't know how to do that."

User says : "Ahhh, too bad, but that's okay."


Goals (objectives)

More questions and answers and stories will lead to a long list of goals.  The following summarizes some goals for this program (the stories are not shown here) :

Need (task)

Goals (interface)

How (algorithms/data structures)

Store data centrally

Store locker#, name,
combination

Use RandomAccessFile, one record for each locker

Assign lockers

[Add button]

Input boxes for #, name, combination

Find name & combination

Search method

Loop through all records

Find many people, for example in same family

Sorted list of all people

Load records into an array
Sort the array
Print all records in a TextArea

Find an empty locker for a new student

Show list of all empty lockers

Loop through all records
if name is empty, print locker number

Erase a name from a locker

[Delete button]

Input name
Search for name - if found, erase it

               (In a real application, this list would be a lot longer, but we are just practicing)


Prototype

A prototype is a simple, preliminary version of the program.  There are two reasons for a prototype:

  1. Interface - make a preliminary user-interface to facilitate further discussions with the user.
      
  2. Feasibility - check some technical issues to make sure the program is possible

The prototype should not attempt to implement all of the goals.  Rather, it should concentrate on the most significant user-interface and algorithm/data-structure features.  For example, the big TextArea and the RandomAccessFile are more significant than the [Delete] button.

The program above is not really enough.  It needs to be expanded.  We will expand it as follows:

This is sufficient to check that the RandomAccessFile storage will work and to show the user more or less what we are planning.


Lockers Database - RandomAccessFile Prototype

We produced a preliminary prototype for the lockers database, to show how to store the names and homeroom teachers in a 2-D array. We noticed it is pretty annoying to type in test data every time we run the program. Some students found it difficult to do the testing for this reason, and naturally did a poor job of testing as a result.

In a real application, the data needs to be stored permanently. This is called data persistence - the data continues to exist when the program shuts down. The data needs to be saved in a data-file on the hard-disk.

One way to do this is to write the entire array into a sequential text file (BufferedReader, PrintWriter). This method would be quite acceptable, except for one small danger - if the user forgets to save the data, or if the program crashes before saving, a lot of data could be lost.

A more robust solution is to use a RandomAccessFile. In a RandomAccessFile, each new piece of data can be written directly to the file when it is entered - this is called direct access. Then if the program crashes, the data is already saved. This provides high levels of persistence (data stays) and robustness (reliability).

RandomAccessFile Structure and Commands

A RandomAccessFile can be broken up into records corresponding to the rows in an array. Each record is broken up into fields, corresponding to the columns in a 2-D array. Rather than using an index value to find a row in an array, the program must seek to a position in the file in order to read or write a record.

Example Data File

In the file shown below, each record occupies 75 bytes, and contains 3 fields: 32 bytes for the student name, 32 bytes for the homeroom teacher, and 11 bytes for the combination. The apostrophes ` represent blank spaces (so we can see them). The ## signs represent 2 bytes that Java uses to store the length of the UTF String. This is managed automatically, so the programmer needn't worry about it. Since 32 bytes are available, each String is limited to 30 characters so the ## bytes also fit in the field.

##Chaplin, Charlie``````````````##Mr Smith``````````````````````##11-12-13`
##Schwarzenegger, Governor Arnol##Mr Smith``````````````````````##01-02-04`
```````````````````````````````````````````````````````````````````````````
##John, Olivia``````````````````##Ms Patterson``````````````````##`````````

It appears that the third record is empty, indicating an unassigned locker. The combination for the 4th locker is blank - maybe the student doesn't have a lock.

The programmer must be careful that strings are small enough to fit in the available space. It appears that Schwarzenegger's name was truncated (the 'd' in "Arnold" is missing) so that it fits in the 30 character limit. If the String is not shortened before writing, it will run off the end of the field and destroy the data in the next field. This is called a buffer over-run. Many Trojan Horse attacks use buffer overruns to crash an operating system so they can take control of the computer. In a data-file, a buffer overrun won't cause a system crash, but could destroy data.

The following commands assign the 3rd locker to "Mike Earbyter". It uses readUTF to check whether the 3rd locker is actually free before writing into that record.

 try
 
{
   RandomAccessFile
data = new RandomAccessFile("lockers.dat");
 
   data.seek(2*75);                    // go to locker #2, name field
   
String name = data.readUTF();       // read the name
   
if (name.equals(""))                // check that the name is empty
   {
      data.seek(2*75);                 // go to locker #2, name field
      data.writeUTF("Earbyter, Mike"); // write the name
 
      data.seek(2*75 + 32);            // go to locker #2, teacher field
      data.writeUTF("Ms Patterson");   // write the teacher
 
      data.seek(2*75 + 64);            // go to locker #2, combination field
      data.writeUTF("77-77-77");       // write the combination
   }
   
   data.close();                       // close the file - now the data is "safe"
 
 }
 catch(IOException e)
 { output(e.toString()); }


Each field is written using a seek command followed by writeUTF. The seek command must tell the
exact position in the file by calculating bytes. Notice that the records are numbered starting with 0 (zero), not starting with 1. Also notice that file access can cause an IOException, and this must be handled by the algorithm. One interesting "feature" is that if a record number is larger than the actual file, Java will automatically make the file bigger to accommodate the command. This prevents indexOutOfBoundsExceptions like you get in an array. But a typing mistake could cause the size of the file to become very large, possibly filling up the entire hard-disk. That could be bad, so the program below checks that the locker number is smaller than 3000 before writing anything.

RandomAccessFile Code

The program below is a simple prototype for a persistent Lockers database. You should copy the code into a Java file and get it running, and check that it correctly saves the data after the program ends.


import
java.awt.*;     // contains GUI controls
import java.io.*;      // contains data-file commands – for RandomAccessFile

public class LockersDB extends EasyApp
{
   public static void main(String[] args)
   {  new LockersDB();  }
   
   Button bClear = addButton("Clear All",50,50,100,50,this);
   Button bAssign = addButton("Assign",150,50,100,50,this);
   Button bShowNames = addButton("Show Names",250,50,100,50,this);
   
   public LockersDB()
   {
      setSize(500,150);
      setTitle("Lockers Database");
   }      
   
   public void actions(Object source,String command)
   {
      if (source == bClear)
      {  clearList();  }
      else if (source == bAssign)
      {  
         String name = input("Type the name of the student");
         int locker = inputInt("Type the locker number:");
         assignLocker(locker,name);  
      }
      else if (source == bShowNames)
      {  showNames(); }
   }
   
   public void clearList()     // Fills file with blanks
   {
      try
      {  RandomAccessFile data = new RandomAccessFile("lockers.dat","rw");
         for (int rec = 0; rec < 3000; rec = rec+1)
         {
            data.seek(rec*75);      // clear the name field
            data.writeUTF("");

            data.seek(rec*75+32);   // clear the homeroom teacher field
            data.writeUTF("");

            data.seek(rec*75+64);   // clear the combination field
            data.writeUTF("");
         }
         data.close();
      }
      catch (IOException e)
      {  System.out.println("IOException occured in clearList()");  }
   }

   
   public void assignLocker(int locker,String name)   // Put name into file
   {
      if (
locker >= 3000)
      { return; }

      try
      {  
         RandomAccessFile data = new RandomAccessFile("lockers.dat","rw");

         if (name.length() > 30)
         {  name = name.substring(0,30);  }
         
         data.seek(locker*75);
         data.writeUTF(name);
         
         data.close();
      }
      catch (IOException e)
      {  System.out.println("IOException occured in clearList()");  }  
   }
   
   public void showNames()    // Show non-blank names and lockers
   {     
      try
      {
         RandomAccessFile data = new RandomAccessFile("lockers.dat","rw");
         for (int row = 0; row < 3000; row = row + 1)
         {  
            data.seek(row*75);
            String name = data.readUTF();
            
            if ( !name.equals("") )
            {  
               System.out.println(row + "\t" + name);
            }
         }
         data.close();
      }   
      catch (IOException e)
      {  System.out.println("IOException occured in clearList()");  }  
   }
}


To Do:

  1. Change assignLocker() so it checks that the locker is empty before writing a name into the data file. It needs to use readUTF() to get the name currently in the record, and check that it equals "".

  2. Add parameters "firstLocker" and "lastLocker" to the showNames method, so it can print a smaller list of lockers, like 1001 - 1020. Change the actions method accordingly, so it calls showNames(0 , 2999) .

  3. Add another parameter "teacher" to showNames, and change the method so it only prints lockers that belong to that teacher. It should still handle the "firstLocker" and "lastLocker" parameters correctly.

  4. Change showNames so that whenever the "teacher" parameter equals * , the method prints all the student names regardless of the homeroom teacher in their record.

  5. Add a parameter called "firstLetter" to showNames, so it is possible to print only the students whose name starts with A, or any other letter.

  6. Make the method respond sensibly when firstLetter contains *

  7. Now the users will want to have some nice, simple buttons to click, to get the results they want.
    Make a button for each of the following reports. Be sure to put all input commands in the user interface (actions) -
    don't put input commands into the showNames() method.

    [All names] = prints locker number and names for all occupied lockers - don't print blank names

    [One letter] = input a single letter (e.g. M) and print all the names starting with that letter

    [One homeroom] = input a homeroom teacher's name, and print ALL the lockers (empty or occupied) for that teacher

    [Find empty] = input a firstNumber and lastNumber, and search for and display all the empty lockers between those numbers.

    [Assign to homeroom] = input firstNumber and lastNumber and the name of a homeroom teacher. Assign those lockers to that teacher by writing the teacher's name into each record.

  8. COPY (?!!?) the showNames method to make a printNames() method. This should function EXACTLY the same as showNames(), except that it sends output to a text-file and then to the printer instead of the monitor. Do this as follows:

      PrintWriter file = new PrintWriter(new FileWriter("temp.txt"));

     loop
       print into the file using file.println(...);
     
    end loop
     file.close();

     runProgram("RUNDLL32.EXE MSHTML.DLL,PrintHTML \"temp.txt\"");


    Now whenever the user clicks a report button, they should be asked whether they want to get the results on the screen or on the printer. Then the appropriate method should be executed – showNames() or printNames()


The last command is platform-specific. It only works on a Windows platform. In fact, it might only work under Windows 2000 and no other version of Windows – it has only been tested under Windows 2000. Don't forget to close the text file before printing – otherwise the file won't contain all the data, as the OS usually buffers access to text files and only finishes saving when .close() is executed. If you have trouble with this command, you may need to change some Windows settings in the File Associations, so the OS knows how to print a file automatically. You can get more information about this command at :  http://www.robvanderwoude.com/printfiles.html