School Locker Database - a Mini-project with RandomAccessFile
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
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 ...
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 |
Luke, Lucky |
Mr Smith |
1-1-1 |
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:
// 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]);
}
}
}
}
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.
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).
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:
You ask: "Do students ever forget their combination?"
User says: "Only a few kids. But they come all the time. There's one kid that is in here at least once a week. I don't look up his combination [on the sheets] any more - I've memorized it."
You ask: "How often do you need to look up a combination?"
User says: "I don't know, maybe a couple times each week."
You ask: "Would you rather do that with a computer than with paper sheets?"
User says: "Not really. But I'd rather that the kid could ask their homeroom teachers - they have a copy of the paper. But if I'm not in the office and the kid can't find the teacher - will the kids be able to access this database?"
You say: "Well, then they could see everyone's combination. I guess we could give them passwords."
User says: "Isn't that a lot of trouble?"
You say: "How about the secretaries? We could give them access."
User says: "Yeah, that's a good idea. They're always at their desks, at least one of them anyway."
The conversation above can be summarized in a story (scenario / use-case):
A student has forgotten their combination. They go to the VP to ask him to look it up, but he isn't available. So they look for their HR teacher, but she isn't available either. So they are stuck and cannot get books out of their locker. They should be able to ask the secretary.
This leads to a need:
Student can retrieve lost combination by talking to the VP, HR teacher or secretary
This leads to several goals:
(a) Combinations are stored in the data file.
(b) The program and the data file(s) are available to several people - VP, HR teacher, secretary
(c) Combinations are only available to the "trusted" people in (b) - not directly to students
This leads to some thoughts about how to do this:
(a) Data file contains: locker number , name , combination
(b) Data file(s) and program are stored on a central server
(c) Files and program have protected access via log-in passwords
** 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."
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, |
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 |
Find an empty locker for a new student |
Show list of all empty lockers |
Loop through all records |
Erase a name from a locker |
[Delete button] |
Input name |
(In a real application, this list would be a lot longer, but we are just practicing)
A prototype is a simple, preliminary version of the program. There are two reasons for a prototype:
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.
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).
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.
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` |
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 |
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.
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.
|
To Do:
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 "".
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) .
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.
Change showNames so that whenever the "teacher" parameter equals * , the method prints all the student names regardless of the homeroom teacher in their record.
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.
Make the method respond sensibly when firstLetter contains *
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.
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