Written by Laura Menke and
Wendy Miller
Last modified on August 4, 1997 by Wendy Miller
Following is a brief explanation of the user interface's contents.
To CHOOSE A DICTIONARY upon starting the program:
To CHOOSE A NEW DICTIONARY while making modifications:
To ADD a word to the dictionary:
To DELETE a word in the dictionary:
To LOOKUP a word in the dictionary:
Listbox operations are available when the user performs a look up of the word or tries to perform the add or delete operations on a word and tense which already appear in the dictionary.
Delete
Cancel Click on cancel button. Program returns you to the form.
Update Click on update button. Program responds confirming that the word has been updated.
Return to User Documentation Contents.It is the user's responsibility to periodically save the dictionary. Any changes made will not be saved permanently until the user clicks done.
To SAVE the dictionary click on the done button. A message confirming that the dictionary was saved will appear.
Return to User Documentation Contents
The Dictionary Software was created by Laura Menke and Wendy Miller
during the summer of 1997 at the University of Wisconsin-Milwaukee.
For any questions or comments please email: menke_ll@cslab.uwlax.edu
or wmiller@cornell-iowa.edu.
To Install the Dictionary Software:
1. Place in any directory.
(NOTE: If you want the dictionary software to be reached from a
web site, create a directory for the Dictionary software in you
public HTML directory.)
2. You must change the path name in the three getDirectory() methods
in the file Server.java. The methods are located at the bottom of
the Server, Connection, and StreamListenerLisp classes. The path
names must be the full path to where the files for the Dictionary
software are located.
The getDirectory() method looks like this:
private String getDirectory(String file){
StringBuffer filepath =
new StringBuffer("E:\\Users\\laura\\dictionary\\");
filepath.append(file);
return filepath.toString();
}
Just replace the path name in the quotes.
(NOTE: It is important to use the double backslashes
when installing on a Windows\DOS enviroment.)
YOU MUST RECOMPILE THE Server.java FILE!!!
3. You must also change a path name in the file lispprog.lisp.
This path name will be the same path name you entered in the
Server.java file.
Below is the line of code in lispprog.lisp you will need to
change. It appear in the at the beginning of the file.
(defvar *direct* "E:\\Users\\laura\\dictionary\\")
To Run the Dictionary Software:
1. You must start the java Server.
A.) From a MS-dos window, you must be in the Dictionary
Software directory.
Then at the promt type:> java Server
B.) From a Unix or Lynix, you must be in the Dictionary
Software directory.
Then at the promt type:> java Server
2. You must use a lisp interperter to load the lispprog.lisp
file which will then start the lisp program.
3. Now you can go to the browser to test the Dictionary.
NOTE: Because java and LISP are communicating through
files, some tmp file will not be deleted if the
program stalls. You can delete a the following
files in the Dictionary Software Directory when
the server is not running!!
A. all .tmp files
B. all .lisp files
C. all files the look like Host server address
with a port appended to it.
For Example:
tigger.cs.uwm.edu9189 or localHost3874
4. For more information, read the DictDocs.html file
(from a browser) that is in the Dictionary Software
Directory.
To Modify the Dictionary HTML documents:
You will want to make three changes at the top of the
dictionary.html file.
1. There is a "last modified" line where you will want to
replace the appropriate E-mail address after "mailto:"
and also type new text for the link.
2. The next line is a link back to the main document. For
example, if you include a link to the dictionary from your
homepage then you should also include a link on the dictionary
back to your page. You will want to modify the link address and
link text.
3. The last line is another "mailto:" link. It is important that you
include the E-mail address of the person who will be able to start
up the server and lisp programs. Guests to the page will then know
who to contact in order to play with the program.
If, for some reason, you change the name of the "dictionary.html"
document then you should also one line in DictDocs.html. There is
a link at the beginning of the document to take guests back to the
page containing the interface.
To Add or Delete Dictionaries:
Adding a dictionary requires you to modify the "listOfDict.txt" file.
Just add the dictionary's name to the list.
To delete a dictionary, there are two changes that need to be made. You
need to remove the dictionary's name from the list in the "listOfDict.txt"
file. It is also necessary to delete the "****.table" file from the
directory which contains the Dictionary program. If there is no
"****.table" file, then the dictionary had not been saved to a file.
Return to SetUp Contents1. Introduction
This
documentation describes the implementation of the client side of the dictionary
software. This software was the first attempt for us at programming in
Java. We broke the clients side up into many classes. There
are many things I would change about the design of this code. For
example I would not have sparated the interface into as many of classes
as I did and I would have handled the events a little differently also.
The classes and a brief description of these classes are listed below.
The rest of the paper will take a deeper look into each of the classes.
Also there is a section on how to update the files for dynamic choice boxes
that are used in Dict_mainInputPanel.class.
The Dictionary Client Software communicates with a server side java
program which communicates with a lisp program that manipulates
the dictionary. It sends the following types of messages to the server;
C1.* to build the operation choice component, C2.* to build partOfSpeech
choice component, and dict.* to build the dictionary choice component.
Also various lisp expression message are passed to the server which just
pass them on to the lisp program.
1. Dictionary.class:
init() is also
responsible for creating instances of Dict_titlePanel,
Dict_mainInputPanel, Dict_featureValue,
Dict_Submit, Dict_donePanel,
and Dict_loadDict. These classes contain
the components that make up the Dictionary interface. It arranges these
instance on the applet using GridBagLayout.
Finally, the init() method creates an instance
of StreamListener to
listen for messages from the Server and then process that information.
The event-handling
code in the Dictionary software is a single long switch statement within
the handleEvent()
method. The handeEvent() method is invoked by the system when an
event is generated. For example it can be anything from a keyPress to ACTION_EVENT.
You can find a list of the different types of events in the book Java
in a Nutshell, ORielly & Associates, Inc..
This
is the area of the code that needs to be changed in the event of switching
to Java 1.1.
The
only events that are handled by the Dictionary code are ACTION_EVENTs.
All others are handled by the operating system. ACTION_EVENTs are generated
by the buttons; sumit, reset, done, and new dictionary. If
the event is handled by one of the four buttons cases below, the function
will return true. Else the function will return false and then the
operating system will take over and try to handle the event. Below
are the events that we defined to do something.
Submit button from class Dict_Submit
If the button clicked on
was the submit button from the Dict_Submit class, we check to to see if
the selected item of the Choice component from the Dict_mainInputPanel
was Add, Delete, or Lookup. If the selected item was Add, call the
checkingAdd() method.
Else, if selected item was Delete, call the checkingDelete()
method. Else, if selected item was Lookup, call the checkingLookUp()
method. checkingAdd(), checkingDelete(), and checkingLookUp() are
all methods that check to make sure the right information is there that
is needed by the dictionary before it is even sent to the server.
These methods will be explained in depth below.
checkingDelete() method:
This method makes sure that all of the fields that need to have something
in them do. If they are missing any part a frame object is created,
which will be titled Error and have a message explaining the error, and
nothing will be sent to the server.
For a Delete to occur there must be:
checkingLookUp() method:
This method makes sure that all of the fields that need to have something
in them do. If they are missing any part a frame
object is created, which will be titled Error and have a message explaining
the error, and nothing will be sent to the server.
For a LookUp to occur there must be:
send_to_Server() method:
This method grabs all the information from the interface and sends
it to the server in the form of a Lisp expression. An example of
the Lisp expression is shown below. This method also disables the
submit button, the done button, and the dictionaryButton. It does
this so that the user will not try to send anything else to the server
while information is already being processed. The buttons will be re-enabled
once the client receives input back from the server.
Example:(Where "health" is the name of a dictionary)
("Add" ("health"("saw"(1(root
"saw") (POF "Noun")(example "I used my new saw.")(Feature "tool"
Value "sharp")(Last modified by "menke")))))
*note: There are actually four feature value pairs that are sent to the
server blank or not.
special_send_to_Server()
method:
This method is the same as send_to_Server()
except it sends sendAllsense as the operation instead of Add, Delete,
or LookUp and it doesn't send any feature value pairs.
Done button from class Dict_donePanel
If
the button clicked on was the done button from the Dict_donePanel class,
send a message to the server to save the dictionary. The message sent to
the server is in the form of a Lisp expression. An example of what Lisp
will receive is shown below, where "health" is the name of the dictionary.
Example : ("Save" ("health"))
dictionaryButton from class
Dict_donePanel ACTION_EVENT
If the button clicked on was the dictionaryButton from the Dict_donePanel
class, send a message to the server to save the current dictionary and
then create an instance of Dict_loadDict which is a frame that asks for
the name of a new dictionary.
There are two more methods in the class Dictionary. setDictionary()
and getDictionary. setDictionary() is called in the Dict_loadDict class,
it sets the private variable dictionary to the name to the dictionary
the user wants to use. getDictionary() returns a string which
is the name of the dictionary that is currently being used.
2. StreamListener.class
This
class extends Thread. It's
job is to loop forever waiting for inputs from the server. Once it
has received input from the server, it looks to see if the input was of
type message. If the input was of type message, this class then creates
a frame object with the title of type message and then reads from
input one more time to get the message part of the frame. It then re-enables
the submit, done, and dictionaryButton. Once it has re-enabled the
buttons, it resumes waiting for the next input. Below is the code
for the message input.
4. Dict_mainInputPanel.class
This class extends Panel and has a GridLayout. It contains the
main GUI component used for input in the Dictionary Software. Dict_mainInputPanel
creates instances of TextFields, Labels, and Choice components.
Dict_mainInputPanel adds items to the Choice components dynamically by
reading from a file that is on the server. For this reason Dict_mainInputPanel
must have access to a socket's InputStream and OutputStream, which
is passed to Dict_mainInputPanel in it's constructor method. Below
is the code for the operation Choice component with the description about
the code embedded. It is the exact same for the partOfSpeech Choice component.
//Send a message "C1.*" to the server which tells
the server to open Operations.txt file //and send the choice items
back as strings.
out.println("C1.*");
//listen to the server for the start flag "!!".
while(notfound){
lines = in.readLine();
if(lines != null){
//If lines equals the start flag !! Begin reading
in and adding items to the Choice component.
if (lines.startsWith("!!"))
{
lines = in.readLine();
//Add the new items to the choice until the string
is equal to the end flag "null".
while(!(lines.equals("null"))){
choice.addItem(lines);
lines = in.readLine();
}
//When all items have been found return false
to stop the loop.
notfound = false;
}
}
}
6. Dict_Submit.class
This class extends
Panel and has a GridBagLayout. It creates Labels and Buttons for Submit
and Reset and places them. The events that Submit and Reset give off should
have been handled here instead of in the Dictionary. Nothing is wrong with
handling the events in the Dictionary class, but on larger project it might
be slower. In fact if it we were to do this whole dictionary Software
interface over I probably would not have separated the classes in this
way. I probably would have combined Dict_titlePanel.class, Dict_mainInputPanel.class,
Dict_featureValue.class, Dict_donePanel.class and Dict_Submit.class into
one class. I still would have kept them all sparated into their own Panels
though.
7. Dict_donePanel.class
This class extends Panel and has a FlowLayout.
It creates Labels and Buttons for Done and New Dictionary and places them.
The events that Done and New Dictionary button give off should have been
handled here instead of in the Dictionary class. Again Nothing is wrong
with handling the events here, but on larger projects it might be
slower.
8. Dict_loadDict.class
This class extends frame
and has a BorderLayout. This class creates a Label, a Panel to hold a Button,
and a Choice object whose items are added dynamically by reading them from
a file.. This class is also passed a connection to the Dictionary
object. The events of the Enter Button are handled in this class.
I handled the events by using the action() method instead of the handleEvent()
method to avoid needing a switch statement. When the user clicks
on Enter, a message is sent to the Server to tell what dictionary the user
would like to use. Below is an example of the message:
Example: ("loadDict" ("Meg"))
After it has sent the message to the Server, it also enables the Submit Button from the Dict_Submit class. Finally it hides and disposes of itself. Else, if the user did not enter something in the TextField, this class creates a Frame object with an error message. Below is the code for handling the event.
//Send
message to the server that a Dictionary has been chosen.
dictionary.out.println("("
+ "\"" + "loadDict" + "\"" + "(" +
"\"" + DictChoices.getSelectedItem() + "\"" + "))");
//Enable
the Submit button in the dictionary class
dictionary.submit.submit.enable();
//Hide
this frame for later use.
this.hide();
return true;
}
else return false;
}
10. frame.class
This is a generic message
frame class which extends frame and can be used in any java program.
When you create an instance of this class you pass it a string which will
be the title and then you pass it a string that will be the the message.
This frame will automatically pack itself to fit the size of the message.
Below is part of the constructor which shows the way you should pass the
title and the message
Example:
public frame(String title, String message){
When the users clicks on the Okay button. The frame hides and
then disposes of it's self.
Return to Client Documentation Introduction
Return to Documentation Contents
private String getDirectory(String
file){
//The area in the red (or in bold)
is the path name that must be changed.
StringBuffer filepath = new StringBuffer("E:\\laura\\dictionary\\");
filepath.append(file);
return filepath.toString();
}
The main body of the server thread is the run() method. It loops forever, listening for and accepting connections from clients. For each connection, the server creates a Connection object to handle communication through the ne Socket. That connection is then added to the Vector of connections. The server then outputs the names of all of the connections to a file. We use synchronize() to lock the Vector of connections from the Vulture when a new connection is created. Below is the body of the run() method.
Another method that is part of the Server class is printNames(). This method outputs the names of all the clients to the clients file so that it can be used by a lisp process which is continuously polling the clients file for input. Because we are communicating to lisp through files we must use tmp files to warn other processes when another is writing to that file. So the first thing that printNames() does is to create a clients.tmp file to warn lisp not to try to read from the clients file. Once the clients.tmp file is created, the server can then print out all the names of the clients connected to the file in the form of a lisp expression. After the server is done writing to the clients file it then closes the file and deletes the clients.tmp file signaling to lisp that it is safe to read the clients file. This method could probably be removed if Lisp and Java talked directly.
The file that Lisp writes to and Java listens
to is named by the hostname + the clients port + ".lisp". An
example is shown below.
Example : tigger.cs.uwm.edu4532.lisp
Naming the files in this way helps to keep track
of what information goes where. The constructor is also responsible
for creating the StreamListenerLisp thread that checks to see if a .lisp.tmp
file is present. Finally the constructor calls the start() method
to start the thread.
The body of the Connection class is the run() method. The run()
method provides the service to the client. Like the Server class's
run() method, the Connection class's run method continuously loops listening
to a given port waiting for something from the client. Below is the loop
of the run() method.
for(;;) {
//
read in a line
line = in.readLine();
if (line == null) break;
else if (line.equals("dict.*"))
openChoiceFile("javaTables.txt");//loads the dictionary
choices in Dict_loadDict class
else if (line.equals("C1.*"))
openChoiceFile("Operations.txt");//loads the operation
choices in the Dict_mainInput class
else if (line.equals("C2.*"))
openChoiceFile("partOfSpeech.txt");//loads the part
of Speech choices in the Dict_mainInput class.
else {
try {
File file = new File(getDirectory(client.getInetAddress().getHostName()
+ ":" + client.getPort()));
FileOutputStream fos = new FileOutputStream(file);
DataOutputStream outfile = new DataOutputStream(fos);
outfile.writeBytes(line + "\n");
fos.close();
outfile.close();
File tmp = new File(getDirectory(client.getInetAddress().getHostName()
+ ":" + client.getPort() + ".tmp"));
FileOutputStream fos2 = new FileOutputStream(tmp);
fos2.close();
} catch
(IOException ioe){
System.out.println("Exception occurred while opening file for output.");
}
System.out.println(line);
}
}
Once the run() method receives a message from the client in the form of a String, it then processes that information. There are three possible messages that can be given to the Connection; C1.*, C2.*, and any string. When connection receives the message it first checks to see if it's a C1.*, this message tells the connection to invoke the openChoiceFile method and send the file Operations.txt along as a parameter. This message along with C2.* allows us to dynamically create the items for the choice components on the client side. If the message is not C1.* or C2.* then it is a message that must be sent to a Lisp process through a file. Once the connection object has written the message to the file it creates a .tmp file to signal to Lisp that it can now read the file.
Below is the finally block
of a try/catch/finally construct. The finally block is optional, and appears
after the try block and after zero or more catch blocks. The
code in a finally block is executed once, regardless of how the code in
the try block executes. In normal execution, control
reaches the end of the try block and then proceeds to the finally block,
which generally performs any necessary cleanup. Below is the finally
block in our run() method.
finally {
try {client.close();
streamlistenerlisp.stop();
File file = new File(getDirectory(client.getInetAddress().getHostName()
+ client.getPort()));
File file2 = new File(getDirectory(client.getInetAddress().getHostName()
+ client.getPort() + ".lisp"));
file2.delete();
file.delete();
synchronized (vulture){vulture.notify();}
In this finally block, it closes the socket between
the client and the server, stops the streamlistenerlisp from listening
to the clients port, deletes the files used by the server to communicate
with the Lisp process, and finally it calls the vulture to do it's cleanup
routine.
The toString() method of the Connection thread returns the host name and the clients port which is used in the list of file names for the Lisp process to check..
The final method that needs
to be discussed in the Connection class it the openChoiceFile().
This method is pretty straight forward. It opens a file and sends the information
that is in the file to the client one line at a time. When the method
is done reading the file it send the flag e!! to the client.
5. Vulture.class
The
Vulture class is a thread that waits to be notified that a thread is dying
(exiting) and then cleans up the list of threads. The Vulture constructor
sets the threadgroup to which the vulture will belong, sets an attachment
to the server, and then starts the thread by calling this.start().
The main
body of the Vulture is the run() method. This is the method that
waits for the notification of exiting threads and cleans up the lists.
It is a synchronized method, is it acquires a lock on the clients.txt file
before running. Even if the Connection object never calls notify(),
this method wakes up every five seconds and checks all the connection,
just in case. It also prevents the Server class from adding a new
connection while we're removing old ones. After the vulture is done removing
old connections it then calls the server class's printNames to print the
updated list to a file for the Lisp process to see.
6. StreamListenerLisp.class
The StreamListenerLisp class is a thread that checks to see if a
.Lisp.tmp file exists. If the .Lisp.tmp file exists the StreamListenerLisp
will then open and read the contents of the .Lisp file and send that information
to the Clients.
The Lisp component of the Dictionary program can be divided into four main sections. The first section includes debugging tools and the creation of the pathname and hash table constants. The next section contains functions which handle the file communication with Java. The third section includes the functions needed to maintain the dictionaries. The last section contains three functions that print information to a file in a format that the Java client is programmed to understand.
There are a few things to remember about this code. The first is that Java is waiting for a reply for nearly every submission of data. So Lisp always completes its process of a submission by printing a response to its output file. This is why many functions make a call to simpleoutmess, simpleoutdict, or sendList. Another suggestion is to read the section called "Possible Improvements" before looking at the code in too much detail. It will help you see what some of the problems are so that you may have those in mind while looking at the code.
Debugging and Declarations
*test* and printMess
The function printMess and the constant *test* were used to
debug the files being used to communicate with Java. If *test* is
sent to "t", when printMess is called after a file is accessed, the
path and file will be printed on the screen.
*direct*
The constant *direct* is used to set the pathname of the directory
where the files are located.
*tables*
The hash table *tables* is used to keep track of the multiple
dictionaries. The key is the script name of the hash table, the value is
the hash table.
run()
This is the function which calls the program. It loops and, unless the
"clients.tmp" file has been created by Java, calls the readClient.
readClient()
This function reads the clients file and sets it to the constant
clientlist. Unless the list of clients is empty, the function
calls scanfiles to process data for each client.
scanfiles (dalist)
Scanfiles is a recursive function which, for each client listed,
creates a constant called *infile* and then calls check.
*infile* is the name of the file that Java client has printed
information to for Lisp to process.
check ()
Check's purpose is solely to check to see if Java has created
the client's temp file, "clientsAddress.tmp", signaling that Lisp should
read the file "clientsAddress" for information to process. If the file
has been created, then inout is called.
inout ()
This function reads in the information from *infile*. Unless there
was an error and data is nil, then the function does the following:
sets *outfile* to "clientsAddress.lisp", clears *infile*,
deletes *tempfile*, and calls determinop. After
determinop has finished, inout calls the createtmp
function to create the "clientsAddress.lisp.tmp" to signal to Java that it
needs to read "clientsAddress.lisp".
createtmp (file)
Createtmp creates a blank file.
clearfile (filename)
Clearfile clears the specified file.
Operations
determinop (data)
This function sets *command* to action the client wants lisp to perform.
It sents *data to the rest of the information. If the command is either
to load or save a dictionary, then it calls the appropriate function. Any
other command results in a call to the function operation.
operation ()
This function performs a number of "setf"s and calls the next function based
on the value of *command*. *dict* is set to the name of the
dictionary, *words* is set to the dictionary's hash table,
*wordinfo* is all the information about the word, and *word
is the word. The function calls the add, del, LookUp,
Update, and sendAllsense functions.
loadDict ()
This function sets *dict* to the name of the dictionary. It creates
a hash table for the dictionary and sets the table as a value in the hash
table, with the key being the script name of the dictionary. The function
checks to see if there is a file of the same name as the dictionary with a
".table" extension. This file is where the dictionary entries are saved.
If the file exists, then loadDict2 is called. Otherwise, the function
sends a message saying that the dictionary loaded has no entries.
loadDict2 ()
loadDict2 loads the file where a dictionary is saved
(dictionaryName.table) into a hash table and sends a message to the client
confirming that the dictionary was loaded.
add (d)
This function checks to see if the word or sense of the word exists. If the
word or sense has not yet been added to the dictionary, then it calls the
appropriate function. If the word and tense have already been entered then
a message is returned to the client.
simpleAdd (d)
simpleAdd adds a new word to the dictionary and sends a message to the
client.
tenseAdd (d)
tenseAdd adds a new sense of a word to the dictionary and sends a
message to the client.
LookUp (d)
This function checks to see if the word and sense submitted are in the
dictionary. If not, then a message is returned to the client which says
that either the word or the sense is not in the dictionary. Else, the
function calls sendList to output to the client the entire entry
for the word.
del (d)
del is similar to LookUp in that it first checks to see if
the word and sense submitted are in the dictionary. If the word and
sense has been entered then the finishDelete is called.
finishDelete (d)
Function deletes the sense from the hash table. If no more senses remain,
it removes the entire word from the dictionary and sends a message to the
client.
update (d)
This function substitutes the new information entered in the interface for an
entry which already exists. A message is returned confirming the update.
sendAllsense (d)
sendAllsense is called when the user leaves the number for sense
empty on the interface. The function checks to see if there is an entry
for the word. If an entry exists then the entry is sent to the client using
the sendList function, else a message is sent to inform the client
that the word is not in the dictionary.
exitRoutine ()
This function sets *words* to the name of the dictionary. It calls
savewords to save the hash table to a file and sends a message to the
client.
savewords (z)
savewords prints the dictionary's hash table to a file. The file
is named using the name of the dictionary and the extension ".table".
Printing Messages to Files
NOTE: It is important to include a command to remove newline
characters when printing to the file because Java will have difficulties
reading the file. An example of this from the simpleoutdict
function is
(remove #\newline (format str outmess *dict*))
simpleoutdict (outmess) and simpleoutmess (outmess)
These functions are designed for sending messages to the client. It prints
a token "message" on the first line of the *outfile* and the message
on the next line. If you include "~A" in the string sent to the function,
simpleoutdict will print the name of the dictionary in your message
and simpleoutmess will print the word. An example of this, with dog
being the word, is
(simpleoutmess "The word ~A is not in the dictionary.")
which will print to *outfile*
message
The word dog is not in the dictionary.
sendList (mess)
This function is designed for the purpose of sending the Java client a list
of entries for a word. It prints a token "list" and the entries for the
word to *outfile*.