Software Engineering I

advertisement
1.0
Introduction
In today’s global workplace, there is a demand for online communication between
individuals in order to do everything from developing large-scale projects to
reminding each other of an upcoming meeting. A chat system is a perfect way to
provide immediate text-based communication. Our team has chosen to create a
communication tool between two (2) users through a server, using graphical user
interface (GUI) techniques. The system is called Integrated Chat System (ICS).
We are using the Unified Modeling Language (UML) software process model in
the design. Throughout this document we use several UML models to describe the
system and its functionality.
1.1
Purpose of the System
The system developed for this project facilitates employees or members of a
corporation to communicate via an existing intranet. Each user creates an account
on the ICS chat server, which uniquely identifies them on the system. The ICS
chat client application allows the user to view and manage a contact list and
initiate and terminate conversations with other end users registered on the system.
The ICS system as designed is to be implemented behind a company’s existing
security measures, which allows for inter-corporation communication while
restricting access from the general public.
1.2
Design Methodology
An interactive software life cycle model known as the Unified Modeling
Language was used to develop the Integrated Chat System. By modeling our
system after the client/server architecture we are able to decompose the ICS into
several logical subsystems. The result of this is a well-defined, modularized,
design as illustrated by the package diagram found within section 2.1. The
Software Requirements Document produced in phase one of this project required
us to describe the ICS in terms of use cases. The diagram found in Appendix A
illustrates the functionality generated. By utilizing both the architecture model
and use cases we are able to easily identify the system objects and seamlessly
assemble them into the Sequence Diagrams illustrated in Appendix B. The Class
Diagrams found in section 3.1 demonstrates a loosely coupled system structure
with high coherence. It describes the interrelations between classes their
attributes, and operations.
1
1.3
Definitions, Acronyms, and Abbreviations
ICS
UML
SDK
GUI
LAN
WAN
CGI
SRD
DD
JVM
1.4
– Integrated Chat System
– Unified Modeling Language
– Software Development Kit
– Graphical User Interface
– Local Area Network
– Wide Area Network
– Common Gateway Interface
– Software Requirements Document
– Design Document
– Java Virtual Machine
Overview of the Document
The design document consists of five chapters. Following the introduction,
chapter two discusses the proposed architecture for the Integrated Chat System.
We provide an overview of the different subsystems being implemented. This
chapter also discusses the hardware and software mapping and persistent data
management. In Chapter three, we provide the object design. We describe the
subsystems being implemented with the use of a class diagram. Next, chapter
three provides sequence diagrams showing object interactions related to use cases.
The remainder of chapter three explains the purpose of each class and references
the appropriate class diagram located also in appendix B. Chapter four is a simple
glossary of terms used throughout the document. Finally, chapter five contains
the appendices. This will include the use case diagram for all use cases
(Appendix A), three use cases being implemented (Appendix B), detailed class
diagrams showing attributes and methods for each class (Appendix C), class
interfaces (code) for the subsystems being implemented (Appendix D), and a
diary of group meetings and tasks (Appendix E).
2
2.0
Proposed Software Architecture
An important step in the development of any system is to establish the
architecture to, which the system design must conform. This provides for clear
and concise criteria to guide the development of the project, eliminating all
ambiguity.
The information, in this section, is broken down as follows: A package diagram
and subsystem overview is outlined in section 2.1, which bisects the system into
the well-known client/server architecture, which communicate through the use of
a command interface. Each package is decomposed into its equivalent
subsystems in section 2.2, and a complete description is provided for each. In
section 2.3 we begin mapping the subsystems to hardware and software. And
finally, in section 2.4, we identify the facilities used to manage the systems
persistent data and security resources.
2.1
Subsystem Overview
The client subsystem houses all the applicable components needed to generate
the graphical user interfaces used to direct the end user. The client subsystem
monitors the communication link and manages the communication packets,
generated by the communication subsystem, sent between itself and the server
subsystem. And finally, the client subsystem updates and maintains all the local
data needed within the subsystem.
The server subsystem plays a major, if somewhat shrouded, role within the
overall system. First and for most, the server subsystem generates and manages
all the connections between itself and remote users. It facilitates message routing
between communicating clients. Lastly, it manages and maintains all persistent
data needed by itself and the client subsystem.
3
The communication subsystem contains the command interface used by the
client and server subsystems in order to communicate meaningful message
packets between each. This subsystem uses a command pattern structure to
facilitate its use.
When referring to the use case diagram found in Appendix A, it can be seen that
all essential functionality revolve around the end user and his/her interaction with
the client subsystem.
2.2
Subsystem Decomposition
The client subsystem is composed of the following four primary classes:
ChatClient, ChatWindow, LoginWindow and the ContactListWindow. The
ChatClient class is the focal point within the subsystem hierarchy. It spawns the
several windows used to interact with the system user. It also creates the socket
connection with the remote server. And it facilitates the communication between
the user, the various GUI’s and the remote server as well as any far side clients.
The LoginWindow is the first GUI encountered upon execution of the system. It
allows the user to enter a name and password. Upon entering this information the
user then presses a button to initiate the authentication process or the registration
process within the server. When authentication/registration is completed
successfully a ContactListWindow is spawned to allow the user to, initiate
communication with a contact or add or delete contacts. When the user decides to
initiate a chat they double click on the persons name located within the users
personal contact list. Then a new ChatWindow is opened to facilitate the
conversation. The user begins by typing the message in the message window and
pressing the “send” button when ready to transmit. A message is received by its
intended user and displayed in the receive message box located on the chat
window. To log the client out the user simply closes the ContactListWindow, this
will destroy all open windows and disconnect the socket connection to the server.
All of the following use cases, taken from Appendix A, are satisfactorily
implemented by the above classes: Login, Register Screen Name, Add
Contact, Delete Contact, Initiate Chat Window, Send Message, Receive
Message and Logout.
The server subsystem is decomposed into the following classes: ChatServer,
Handler, UserList, User. The ChatServer is the main execution class for this
subsystem. It begins by opening a socket and listening for a remote connection.
When a remote connection occurs a new thread is spawned to handle the new
client. The server then continues to listen for future connections. The Handler
class is the workhorse of the subsystem. It authenticates the remote clients,
retrieves and stores the clients contact list, and routes the messages intended for
client-to-client communication. The UserList class simply maintains a list of the
users currently logged into the system. The User class creates objects, which
represent the users logged into the system.
4
The communication subsystem implements the command pattern as an interface
for generating information packets to be sent between the either the client and
server or client to client. This subsystem is separated into the following classes:
Command, MessageCommand, CompleteLoginCommand, ContactListCommand,
LoginCommand, ErrorCommand, LoginErrorCommand, and Message. Each of
the other subsystems accesses the various command types through the command
interface. Each class is responsible for making objects of its type using the
supplied constructors. Each command class is equipped with a single method,
which is used to execute the command object supplied to it. The Message class is
the atomic object generated for the MessageCommand class.
2.3
Hardware and Software Mapping
The two subsystem nodes are mapped as follows: First, the client subsystem, as
outlined in section 2.1 of this report, is named “ClientSubsystem” and is placed on
a hardware configuration called “MyComputer”. Second, the server subsystem,
also outlined in section 2.1, is named “ServerSubsystem” and is placed on a
hardware configuration called “ManagementServer”. The virtual machine needed
for the above subsystems is any OS and hardware platform combination, which
supports the JVM. Examples are, Microsoft's Windows or Unix's X-Windows
systems on a PC platform. This is a many-to-one mapping, as there are several
client subsystems connected to a single server subsystem.
2.4
Persistent Data Management
Within the server subsystem we are saving two forms of persistent data. First, for
each user who registers with ICS, he/she creates both a user name and password
in order to login into the system. This information is retained permanently within
the server to facilitate future connections. Second, after the user supplies the
necessary authentication information an account is created to which personal
contacts can be added. This information is stored locally to the server so when a
registered user connects, her/his account information can be forwarded to the
client subsystem for display. This information is saved in the form of objects in a
flat file.
This system is implemented behind a corporation’s existing security measures; the
necessity for stringent security measures is low. Simply, the user’s desired
password will be encrypted for authentication purposes.
5
3.0
Object Design
Object design allows us to scope the underlying system into logical productions.
By decomposing the system into objects and explaining how these objects
sequentially intermingle we are better able to reveal the true nature of our system
design.
We begin in section 3.1 by developing those subsystems, which are critical to the
ICS project; a minimal class diagram is presented for illustration. Section 3.2
merges the defined subsystems with the use cases found in Appendix B and
visually illustrates them as sequential object interactions. Finally, in section 3.3
we explain the purpose of each class and supply appropriate class references.
3.1
Overview
LoginWindow
ChatClient
(from Client Subsystem)
1
1
(from Client Subsystem)
MessageCommand
1
(from Communication Subsystem)
1
1
Message
(from Communication Subsystem)
1
Runnable
(from l...
0..n
CompleteLoginCommand
(from Communication Subsystem)
ActionListener
(from event)
ChatWindow
JFrame
(from Client Subsystem)
(from swing)
Command
(from Communication
...) Subsystem)
ContactListUpdateCommand
0..n
0..n
(from Communication Subsystem)
Serializable
(from io)
1
LoginCommand
(from Communication Subsystem)
1
ContactListWindow
(from Client Subsystem)
ErrorCommand
(from Communication Subsystem)
1
LoginErrorCommand
(from Communication Subsystem)
Handler
(from Server Subsystem)
ChatServer
0..n
1
(from Server Subsystem)
1
Thread
1
UserList
(from lang)
(from Server Subsystem)
User
1 0..n
(from Server Subsystem)
Serializable
(from io)
6
3.2
Object Interaction
Log In:
: USER
: LoginWindow
: JButton
: ChatClient
: LoginCommand
: ObjectOutputStream
: ObjectInputStream
: Handler
: ChatServer
: CompleteLoginCommand
: ContactListWindow
actionPerformed(ActionEvent)
actionPerformed(ActionEvent)
setEnabled(boolean)
actionPerformed(ActionEvent)
login(String, String)
LoginCommand(String, String)
writeObject(Object)
readObject( )
execute(Object)
login(String, String)
authenticate(String, String)
CompleteLoginCommand(ArrayList)
ContactListWindow(ChatClient, ArrayList)
7
Initiate Chat Window:
: USER
: Doubleclick
mouseClicked(MouseEvent)
: ChatWindow
ChatWindow(ChatClient, String, String)
Send Message:
: USER
: ChatWindow
: ChatClient
actionPerformed(ActionEvent)
actionPerformed(ActionEvent)
sendMessage(String, String, String)
updateConversation(String, String)
8
3.3
Detail Class Design
This section explains the purpose of each class used in the development of the various
subsystems.
The following classes are located within the Client Subsystem. The class diagram for
this subsystem can be found in Appendix C page 24 and the appropriate code in
Appendix D pages 26 – 31.
Chat Client - The chat client is the focal point of the client subsystem. This class defines
the following methods: The main() method within this class is executed at start up in
order to create a socket connection to the server. After successful connection, this
method creates a new GUI LoginWindow in a separate thread of execution. And finally,
it waits in a loop and executes the commands received from the server. The login()
attempts to login the specified user with the specified password. The completeLogin()
method is used to request the server send the chat client the complete list of all his/her
buddies. The sendMessage() method is used to send a message to the specified user.
The receiveMessage() method decouples incoming message packets and sends them to
the ChatWindow message display. The userListUpdateMethod() updates the logged-on
status of a single user. And the showError() method generates a message box displaying
the type of error that has occurred.
ChatWindow - The constructor ChatWindow() generates a two window box in a
separate thread of execution. The top window holds the conversation between clients and
cannot be accessed by user. The bottom window is where the client enters his/her
information. When typing is completed the user presses the “send” button. This executes
the actionPerformed() method creates a message, which is displayed in the conversation
window using the updateConversation() method and then packaged up and sent to the
remote client. The run() method is defined here.
LoginWindow - The constructor LoginWindow() generates a GUI log in window. A
single button labeled “Log In” is created. Upon opening the user is prompted to enter
both a username and password. When this has been completed, and the button is pressed,
the actionPerformed() method is executed, which sends the username and password to
the server for authentication.
ContactListWindow - The constructor ContactListWindow() generates a GUI to manage
and display contacts. Two lists are created in the window. The top displays all the
contacts available to choose from. The bottom displays all the users buddies. By double
clicking on the name in the bottom list the user can initiate a chat with a contact. The
window also listens for messages from the server, which tells the window a remote user
has initiated a chat. The updateList() method updates the contact list window by
changing the logged on state of the specified user to the specified state. The
distributeMessage() method displays the message from the specified user into the
correct window.
The following classes are located within the Server Subsystem. The class diagram for
this subsystem can be found in Appendix C page 23 and the appropriate code in
Appendix D pages 31 – 37.
9
ChatServer – The chat server is the main class in the server subsystem. This class defines
the following methods: The main() method is used to initiate the server. It begins by
generating a socket to which a remote client can connect. When a connection is created
is it passed to a new thread handler and the server continues to listen for the next
connection. The authenticate() method authenticates a user that is attempting to log on
with a specified username and password.
Handler – The Handler class is the workhorse of the server subsystem. This class extends
Thread and it defines the following methods: The constructor Handler() receives both a
new socket and ChatServer object. The run() method readies both the input and output
streams for use. It also loops continuously, processing the input messages from this
client. The login() method attempts to log in the specified user to with the specified
password. The sendMessage() method performs the necessary message routing.
UserList – This class is dedicated to holding User objects in a treemap. It provides
several accessors and mutators to manipulate the map.
User – The constructor User() initializes the attributes of a new User object, which
include the username and password, loggedOn to indicate the users current status, an
ArrayList of buddies for the user, and an ArrayList of names the user is a buddy of.
There are several accessors and mutators defined in this class to access the above
attributes.
The following classes are located within the Communication Subsystem. The class
diagram for this subsystem can be found in Appendix C page 25 and the appropriate code
in Appendix D pages 37 – 43.
Command – This interface class defines the following abstract method: The execute()
method within this class directs the executing command object to one of the various
classes within the communication subsystem. The accepting class properly defines
execute() for the specified objects.
MessageCommand – The constructor MessageCommand() creates a new object, which
consists of a message object. This class properly executes the message commands by
using its execute() method.
Message – The constructor Message() creates a new object, which consists of a
destination, source and message. There are several methods defined which can access
each attribute.
CompleteLoginCommand – The class constructor CompleteLoginCommand() creates a
new object that holds an ArrayList of all the users that are the logged-on user's buddies.
This class properly executes CompleteLogin commands by using its execute() method.
ContactListUpdateCommand – The class constructor CompleteListUpdateCommand() creates a
new object that holds the name of the user that is a buddy of the logging-on user and the logged
on state of that user. This class properly executes CompleteListUpdate commands by using its
execute() method.
10
LoginCommand – The class constructor LoginCommand() creates a new object, which
consists of the username and password. This class properly executes LoginCommand
commands by using its execute() method.
ErrorCommand – The class constructor ErrorCommand() creates a new object, which
consists of the message to be displayed, and the title of the box. This class properly
executes ErrorCommand commands by using its execute() method.
LoginErrorCommand – The class constructor LoginErrorCommand() creates a new
object, which consists of the message to be displayed, and the title of the box. This class
is a specific type of ErrorCommand. This class properly executes LoginErrorCommand
commands by using its execute() method.
11
4.0 Glossary
Actor:
External entity that needs to exchange information with the system.
America On Line Instant Messenger (AOL AIM):
A commercially available Chat Program developed by AOL Time Warner.
Applet:
A Java program, which can be distributed as an attachment in a World-Wide Web
document and executed by a Java-enabled web browser.
Chat Program:
Any program that allows any number of logged-in users to have a typed, realtime, on-line conversation, either by all users logging into the same computer, or
more commonly nowadays, via a network.
Class Diagram:
UML notation representing the structure of the system in terms of objects, classes,
attributes, operations, and associations.
Client/server architecture:
A central server program delivers a software architecture in which user
interactions are managed by simple client programs and functionality.
Command Pattern:
A pattern encapsulating control objects such that they control objects that they can
be uniformly treated by the system.
Common Gateway Interface (CGI):
A standard for running external programs from a World-Wide Web HTTP server.
Connection Port:
A logical channel or channel endpoint in a communications system.
Design Document (DD):
A document describing the system design model.
Dynamic access control:
An access control policy that can be set at run time.
Dynamic Host Configuration Protocol (DHCP):
A protocol that provides a means to dynamically allocate IP addresses to
computers on a local area network.
Ethernet:
12
A local area network where data is broken into packets and each one is
transmitted until it arrives at the destination without colliding with any other
packet.
Final Document (FD):
A document describing the system once it has been developed.
Functional Requirement:
An area of functionality that the system must have.
Gantt Chart:
A bar chart where the horizontal axis represents time and the vertical axis the
different tasks to be done.
Graphical User Interface (GUI):
The use of pictures rather than just words to represent the input and output of a
program.
Hardware Requirement:
Description of the hardware required carrying out the development of the system.
ICS System:
The system being developed.
ICS User:
An end user of the ICS system.
Integrated Chat System (ICS):
The name of the system being developed.
Internet Protocol (IP):
The network layer for the TCP/IP protocol suite widely used on Ethernet
networks.
Intranet Connection:
A connection to any network which provides similar services within an
organization to those provided by the Internet outside it but which is not
necessarily connected to the Internet.
Java Virtual Machine:
A specification for software, which interprets Java programs that have been
compiled into byte-codes, and usually stored in a ".class" file.
JAVA 2 SDK build 1.4.0:
A Software Development Kit distributed by Sun Microsystems.
Local area network (LAN):
A data communications network, which is geographically, limited (typically to a 1
km radius) allowing easy interconnection of terminals, microprocessors and
computers within adjacent buildings.
13
Microsoft Excel:
A spreadsheet program from Microsoft, part of there Microsoft Office suite of
productivity tools for Microsoft Windows and Macintosh.
Microsoft Network Messenger (MSN Messenger):
A commercially available Chat Program developed by Microsoft.
Microsoft Project:
Microsoft Windows programs offering various project management tools.
Microsoft Word:
A popular word processor, part of the Microsoft Office suite.
Milestone:
End-point of a software process activity.
Nonfunctional Requirement:
A constraint on the system.
Object Design:
An activity during which developments define custom objects to bridge the gap
between the analysis model and the hardware/software platform. This includes
specifying object and subsystem interfaces, selecting off the-shelf-components,
reconstructing the object model to attain design goals, and optimizing the object
model for performance. Object design results in the object design model.
Package:
A UML grouping concept denoting that a set of objects or classes are related.
Packages are used in use case and class diagrams to deal with the complexity
associated large numbers of use cases or classes.
Program Threading:
Running multiple processes in a single program so that each process can be
handled concurrently.
Random Access Memory (RAM):
A computer's available memory to run an application.
Router:
A device, which forwards packets between networks.
Sequence Diagrams:
UML notation representing the behavior of the system as a series of interactions
among a group of objects. Each object is depicted as a column in the diagram.
Each interaction is depicted as an arrow between two columns.
Software Development Kit (SDK):
Software provided by a software vendor to allow their products to be used with
those of other software vendors.
14
Software Requirement:
Description of the software required carrying out the development of the system.
Software Requirements Document (SRD):
A document describing the system's purpose and functionality.
Subsystem:
In general, a smaller simpler part of a larger system; in the system design, a well
defined software component that provides a number of services to the other
subsystems.
Subsystem Decomposition:
Division of the system into subsystems. Each subsystem is described in terms of
its services during system design and its API during object design. The system
decomposition is part of the system design model.
Static Internet Protocol (Static IP):
An Internet Protocol used by a system that does not change over time.
System Design Model:
High level description of the system, including design goals, subsystem
decomposition, hardware/software platform, persistent storage strategy, global
control flow, access control policy, and handling of boundary conditions.
System’s Boundaries:
The characteristics of a system that describe what part of the system are inside
and what is outside the scope of the system.
Task:
An atomic unit of work that can be managed and that consumes resources.
Transmission Control Protocol/Internet Protocol Port (TCP/IP Port):
A Connection Port for the de facto standard Ethernet protocols.
Unified Modeling Language:
A standard language for writing software blueprints.
Unified Software Development Process:
An interactive software lifestyle model characterized by cycles of four phases,
called Inception, Elaboration, Construction, and Transition.
Use case Diagram Model:
A model in which use case diagrams are used to represent an abstraction of the
system.
Use case Diagram:
A diagram that shows a set of use cases and actors and their relationships.
Use case:
A general sequence of interactions between one or more actors and the system.
User Interface Design:
The design of GUI components used for the user to interface with the system.
Wide Area Network (WAN):
15
A network, usually constructed with serial lines, extending over distances greater
than one kilometer.
Yahoo Instant Messenger:
A commercially available Chat Program developed by Yahoo Inc.Appendix
A:
Use Case Diagram
Register Screen Name
Add Contact
(from Register Screen Name)
(from Add Contact)
Login
Delete Contact
(from Login)
(from Delete Contact)
End User
(f rom Actors)
Logout
Initiate Chat Window
(from Logout)
(from Initiate Chat Window)
Receive Message
Send Message
(from Receive Message)
(from Send Message)
Appendix B: Use Cases
Use Case ID: ICS01 – Log In
Scenario:
Actor: User
Pre-conditions:
16
1. The ICS chat server must be running and connected to its communication
port.
2. The Log In Window is active.
3. The “Log In” button is disabled.
4. Socket connection with server established.
Description:
1. Use case begins when the user enters their name and password in the
appropriate text fields.
2. The system enables the “Log In” button.
3. The user presses the “Log In” button.
4. The server authenticates the user name and password.
5. The chat server notifies the user of all its contacts are currently logged in
to the system.
6. Use case ends when the user’s Contact List Window is displayed.
Post-conditions:
1. The chat server notifies all the clients who have this user listed as a
contact that this user has logged in.
2. The number of server-side threads is increased by one.
Alternative Courses of Action:
1. In step D.4 (step 4 of the description) if the user name and password do not
match then the user will see an Error Message Window telling them to try
again.
Exceptions:
1. There is no communication connection to the server.
Related Use Cases: Register screen name
Decision Support
Frequency: Usually once a day per user.
Criticality: High. Allows the connection of the chat client to the server.
Risk: Medium. Implementing the use case employs standard network-based technology.
Modification History
Owner:
Group 4
Initiation date:
09/27/2003
Date last modified:
11/01/2003
17
Nonfunctional Requirements:
1. If an incorrect username/password combination is entered more than two times,
the system closes the application.
Use Case ID: ICS06 – Initiate Chat Window
Scenario:
Actor: User
Pre-conditions:
1. User is logged on to the system.
2. Contact List Window is activated.
3. At least one of the user’s contacts are logged on to the system.
Description:
1. Use case begins when user double clicks on a contact name in the
“Logged On” section of the Contact List Window (see Appendix D).
2. Use case ends when the Chat Window for that specific contact is
displayed on the user’s screen.
Post-conditions:
Number of threads on the client side increased by one.
Alternative Courses of Action: None.
Exceptions: None.
Related Use Cases:
1. Send Message.
2. Receive Message.
Decision Support
Frequency: Usually at least once every time a user logs in to the system.
Criticality: Medium. Allows one user to initiate a chat with another user.
Risk: Low. This use case implements standard GUI techniques.
Modification History
Owner: Group 4
Initiation date: 09/27/2003
Date last modified: 09/27/2003
18
Nonfunctional Requirements: None.
Use Case ID: ICS05 – Send Message
Scenario:
Actor: User
Pre-conditions:
1. Server is running.
2. The user and intended recipient are both logged on to the system.
3. Chat Window has been initiated and is active.
Description:
1.
Use case begins when the user types a message into the message text area
of the Chat Window corresponding to the intended recipient.
2.
The user presses the send button.
3.
The client sends the message to the server.
4.
Use case ends when the system echos the user’s message onto the
conversation text area of their Chat Window.
Post-conditions:
1. The server receives and redirects the message to the correct user.
2. The Message is displayed on the recipient’s Chat Window.
Alternative Courses of Action:
1. In step D.2 (step 2 of the Description) the user can choose to delete the message
that was typed, and therefore not send the message.
Exceptions:
1. At the time the user tries to send the message, the other user has already logged
off.
Related Use Cases:
1. Initiate Chat Window.
2. Receive Message.
Decision Support
Frequency: At least once every time the user logs on.
Criticality: High. Allows one user to communicate with another.
Risk: Medium. Implementing this use case employs standard network-based technology.
19
Modification History
Owner: Group 4
Initiation date: 09/18/2003
Date last modified: 11/07/2003
Nonfunctional Requirements:
1. Response time of the server.
2. Ease of use.
Appendix C: Class Diagrams
Server Subsystem:
20
Client Subsystem:
21
Communication Subsystem:
Appendix D: Class Interfaces
Client Subsystem:
-------------------------------------------------------------------------------/**
Purpose:
Author:
Date created:
Last modified:
Version:
Client main process that handles all the user windows and
incoming objects.
Group 4
November 8 2003
November 8 2003
1
//*/
public class ChatClient
{
//
Name of the user running the client.
private String userName;
//
The socket used to handle the connection.
22
//
//
//
//
//
//
//
private Socket sock;
The input stream of this connection.
private ObjectInput in;
The output stream of this connection.
private ObjectOutput out;
The ContactListWindow that contains all the user's buddies.
private ContactListWindow userListWin;
The name of the default host.
private static final String DEFAULT_HOST = "localhost";
Port used to connect.
private static final int DEFAULT_PORT = 3737;
@inv The port number and the host name are constants and will always be
the same. The username must be a string that is not null.
/**
Main procedure of the client.
@param args = An array of string arguments. Not used in the program.
@return None.
@exception: None.
@pre None.
@post None.
//*/
public static void main( String [] args );
/**
Attempts to login the specified user with the specified password.
@param userName = The username provided by the user.
@param password = The password provided by the user.
@return A number identifying the outcome of the execution of the method.
@exception: None.
@pre username and password are the correct values.
@post User is logged on.
//*/
public int login( String userName, String password );
/**
Attempts to send the user the complete list of all his/her buddies.
@param listUsers = The list of all the users that are the logging-on user's buddies.
@return A number identifying the outcome of the execution of the method.
@exception: None.
@pre listUsers is an ArrayList with all the users listed.
@post The user's buddies are displayed on the appropriate window.
//*/
public int completeLogin( ArrayList listUsers );
/**
Attempts to send a message to the specified user.
@param toUser = The user to which this message is to be sent.
@param fromUser = The user sending this message.
@param message = The message to be sent.
@return A number identifying the outcome of the execution of the method.
@exception: None.
@pre toUser is another user in the system and fromUser is this user.
@post The message will be sent.
//*/
public int sendMessage( String toUser, String fromUser, String message );
/**
Attempts to receive and display a message.
@param m = The Message being transmitted.
23
@return A number identifying the outcome of the execution of the method.
@exception: None.
@pre m is a valid message.
@post The message will have been displayed.
//*/
public int receiveMessage( Message m );
/**
Attempts to update the logged-on status of a single user.
@param userName = The user being updated
@param loggedOn = The user's current logged-on status.
@return A number identifying the outcome of the execution of the method.
@exception: None.
@pre None.
@post The user's buddy will be displayed on the appropriate window.
//*/
public int userListUpdate( String userName, boolean loggedOn );
/**
Attempts to display a message box displaying the type of error that has ocurred.
@param strMessage = The message of the message box that will be displayed to the user.
@param strTitle = The title of the message box that will be displayed to the user.
@return A number identifying the outcome of the execution of the method.
@exception: None.
@pre None.
@post The error message will be displayed.
//*/
public int showError( String strMessage, String strTitle );
}
-------------------------------------------------------------------------------/**
Purpose:
Author:
Date created:
Last modified:
Version:
Creates a login window for the user and handles its events.
Group 4
November 8 2003
November 8 2003
1
//*/
class LoginWindow extends JFrame implements ActionListener
{
//
The button the user will press to log into the server.
private JButton bLogin;
//
The text box the user will use to enter the user name.
private JTextField tName;
//
The text box the user will use to enter the password.
private JPasswordField tPassword;
//
A reference to the Client program which spawned the login window.
private ChatClient parent;
//
@inv parent will not be null.
/**
Creates the login window.
@param parent = A reference to the Client program which spawned the login window.
@return None.
@exception: None.
@pre parent points to the ChatClient object that innitialized the client.
@post The LoginWindow will be created.
//*/
24
public LoginWindow( ChatClient parent );
/**
Handles the user-defined events of the login window.
@param e = The ActionEvent that took place caused by the user.
@return None.
@exception: None.
@pre None.
@post The event handler for the specified ActionEvent will be executed.
//*/
public void actionPerformed( ActionEvent e );
}
-------------------------------------------------------------------------------/**
Purpose:
Author:
Date created:
Last modified:
Version:
Creates a contact list window for the user and handles its
events.
Group 4
November 8 2003
November 8 2003
1
//*/
public class ContactListWindow extends JFrame
{
//
Handles all the Threads of chat windows.
private Map chatWindows = new HashMap();
//
List of buddies that are currently logged on.
private JList onlineContacts;
//
List of buddies that are not currently logged on.
private JList offlineContacts;
//
Handler of the onlineContacts.
private DefaultListModel modelOnline = new DefaultListModel();
//
Handler of the offlineContacts.
private DefaultListModel modelOffline = new DefaultListModel();
//
Name of the user running the client.
private String userName;
//
A reference to the Client program which spawned the contact list window.
private ChatClient parent;
//
@inv userName must be a string that is not null. parent will not be null.
/**
Creates the contact list window.
@param parent = A reference to the Client program which spawned the contact list window.
@param sUsers = List of buddies of the user.
@param user = Name of the user running the client.
@return None.
@exception: None.
@pre parent points to the ChatClient object that innitialized the client.
uUsers is an ArrayList with the user's buddies. user is this user.
@post The contact list window will be created.
//*/
public ContactListWindow( ChatClient parent, ArrayList sUsers, String user );
/**
Updates the contact list window by changing the logged on state of the
specified user to the specified state.
@param name = The name of the user who needs to be updated in the contact
list.
25
@param loggedOn = The new state of that user.
@return None.
@exception: None.
@pre None.
@post The user's buddy state will be updated dependent upon loggedOn.
//*/
public void updateList( String name, boolean loggedOn );
/**
Displays the message from the specified user into the correct window.
@param fromUser = The user that sent the message.
@param message = The message sent.
@return None.
@exception: None.
@pre fromUser must be an user in the server.
@post The message from that user is displayed.
//*/
public void distributeMessage( String fromUser, String message );
/**
Purpose:
Author:
Date created:
Last modified:
Version:
Handles the user-defined events of the contact list window.
Group 4
November 8 2003
November 8 2003
1
//*/
class doubleClick extends MouseAdapter
{
/**
Handles the user-defined events of the contact list window.
@param e = The ActionEvent that took place caused by the user.
@return None.
@exception: None.
@pre None.
@post The event handler for the specified MouseEvent will be executed.
//*/
public void mouseClicked( MouseEvent e );
}
}
-------------------------------------------------------------------------------/**
Purpose:
Author:
Date created:
Last modified:
Version:
Handles the conversation between the client and another user.
Group 4
November 8 2003
November 8 2003
1
//*/
class ChatWindow extends JFrame implements Runnable, ActionListener
{
//
Name of the user running the client.
private String userName;
//
Name of the user to whom this chat window's conversation corresponds to.
private String toUser;
//
Button to send the message currently typed.
private JButton bSend;
//
Text area where the user will type the messages to be sent.
26
//
//
//
//
private JTextPane tpMessage;
Text area where the currently sent and received messages are displayed.
private JTextArea taConversation;
A reference to the Client program which spawned the chat window.
private ChatClient parent;
@inv userName and toUser must be strings that are not null. parent will not
be null.
/**
Creates the chat window.
@param parent = A reference to the Client program which spawned the chat
window.
@param user = Name of the user running the client.
@param tu = Name of the user to whom this chat window's conversation
corresponds to.
@return None.
@exception: None.
@pre parent points to the ChatClient object that innitialized the client. tu
is an user that is a buddy of this user.
@post The chat window will be created.
//*/
public ChatWindow( ChatClient parent, String user, String tu );
/**
Displays the currently sent or received message in the appropriate window.
@param user = The user who sent the current message to be displayed.
@param message = The message sent or received.
@return None.
@exception: None.
@pre user is either this user or the user that this window corresponds to.
@post user's message will be displayed on the window.
//*/
public void updateConversation( String user, String message );
/**
Handles the user-defined events of the chat window.
@param e = The ActionEvent that took place caused by the user.
@return None.
@exception: None.
@pre None.
@post The event handler for the specified ActionEvent will be executed.
//*/
public void actionPerformed( ActionEvent e );
/**
Run method of this Thread.
@return None.
@exception: None.
@pre None.
@post None.
//*/
public void run();
}
--------------------------------------------------------------------------------
Server Subsystem:
--------------------------------------------------------------------------------
27
/**
Purpose:
Author:
Date created:
Last modified:
Version:
Server main process that handles incoming connections.
Group 4
November 8 2003
November 8 2003
1
//*/
public class ChatServer
{
//
Port used for incoming connections.
private static final int DEFAULT_PORT = 3737;
//
Data structure of all users registered on the system.
public static UserList users;
//
Name and path of file where the database of registered users will be stored.
private static String datafile = "datafile.dat";
//
@inv The port number is a constant and will always be the same.
//
The datafile name will always be the same.
/**
Main procedure of the server.
@param args = An array of string arguments. Not used in the program.
@return None.
@exception: None.
@pre None.
@post None.
//*/
public static void main( String [] args );
/**
Authenticates an user that is attempting to log on with a specified
username and password.
@param username = The username provided by the user.
@param password = The password provided by the user.
@return The User object if the login was successful, false otherwise.
@exception:
None.
@pre username and password are the correct values for the specified user.
@post The number of logged on users increases by 1.
//*/
public User authenticate( String username, String password );
}
-------------------------------------------------------------------------------/**
Purpose:
Author:
Date created:
Last modified:
Version:
Handles an incoming connection from an user.
Group 4
November 8 2003
November 8 2003
1
//*/
public class Handler extends Thread
{
//
The socket used to handle this connection.
private Socket sock;
//
The User object that corresponds to this connection handler.
private User user = null;
//
The data structure of all the users' output streams.
28
//
//
//
//
//
//
public static Map outstreams = new HashMap();
A reference to the Server program which spawned the connection handler.
private ChatServer parent;
The input stream of this connection.
ObjectInput in;
The output stream of this connection.
ObjectOutput out;
@inv sock must always be an open connection for the duration of this Thread.
user must not be null for the duration of this Thread. parent will
not be null.
/**
Creates the handler.
@param incoming = The socket used to handle this connection.
@param parent = A reference to the Server program which spawned the
connection handler.
@return None.
@exception: None.
@pre parent points to the ChatServer object that innitialized the Server.
incoming is a valid Socket. parent
@post The Handler will be created.
//*/
public Handler( Socket incoming, ChatServer parent );
/**
Run method of this Thread.
@return None.
@exception: None.
@pre None.
@post None.
//*/
public void run();
/**
Attempts to log in the specified user to with the specified password.
@param username = The username provided by the user.
@param password = The password provided by the user.
@return A number identifying the outcome of the execution of the method.
@exception: None.
@pre username and password are the correct values for the specified user.
@post The user will be logged on.
//*/
public int login( String username, String password );
/**
Forwards a message to its destination.
@param m = The message to be sent.
@return A number identifying the outcome of the execution of the method.
@exception: None.
@pre m is a valid message.
@post The message will be sent.
//*/
public int sendMessage( Message m );
}
-------------------------------------------------------------------------------/**
Purpose:
Stores and handles all the User objects in a data structure
with limited access to the Users.
29
Author:
Date created:
Last modified:
Version:
Group 4
November 8 2003
November 8 2003
1
//*/
public class UserList implements Serializable
{
//
Stores all the user objects in a data structure with access time of
//
O (log N). Maps User names to User objects.
private TreeMap users = new TreeMap();
//
@inv None.
/**
Returns the User object identified by the name.
@param name = The name of the User object requested.
@return The User object identified by the name.
@exception: None.
@pre None.
@post None.
//*/
public User getUser( String name );
/**
Creates and adds an User to the UserList data structure.
@param name = the name of the User object to be created.
@param password = The password selected by the user for his/her account.
@return true if the user was created, false otherwise.
@exception: None.
@pre username and password are the correct values for the specified user.
@post The user will be added to the server database.
//*/
public boolean addUser( String name, String password );
/**
Determines if the specified user is logged on.
@param name = The name of the user being examined.
@return true if the specified user is currently logged on, false otherwise.
@exception: None.
@pre None.
@post None.
//*/
public boolean isLoggedOn( String name );
/**
Returns the ArrayList of user names that the specified user has as
his/her buddies.
@param name = The name of the user whose list of buddies is being requested.
@return The ArrayList of names that the user has as his/her buddies.
@exception: None.
@pre None.
@post None.
//*/
public ArrayList getMyBuddies( String name );
/**
Returns the ArrayList of user names that have the specified user as
a buddy.
@param name = The name of the user, whose list of users that have him as his
buddy, is being requested.
30
@return The ArrayList of names that have the user as their buddy.
@exception: None.
@pre None.
@post None.
//*/
public ArrayList getImBuddy( String name );
}
-------------------------------------------------------------------------------/**
Purpose:
Author:
Date created:
Last modified:
Version:
Stores information of a single User object.
Group 4
November 8 2003
November 8 2003
1
//*/
class User implements Serializable
{
//
The user's handle to identify him/her on the system.
private String username;
//
The user's password used to log on to the system.
private String password;
//
Whether or not the user is currently logged on.
private boolean loggedOn;
//
The ArrayList of names that this user has as a buddy.
private ArrayList mybuddies;
//
The ArrayList of names of users that have this user as a buddy.
private ArrayList Imbuddy;
//
@inv The username and password must be strings that are not null.
/**
Creates a new User object.
@param name = The name of the user being created.
@param pswd = he password selected by the user for his/her account.
@return None.
@exception: None.
@pre None.
@post The User will be created.
//*/
public User( String name, String pswd );
/**
Returns the user's password.
@return The user's password.
@exception: None.
@pre None.
@post None.
//*/
public String getPassword( );
/**
Returns the user's name.
@return The user's name.
@exception: None.
@pre None.
@post None.
//*/
public String getUserName( );
31
/**
Sets the user's logged on state.
@param connected = Whether or not the user is to be currently connected.
@return None.
@exception: None.
@pre None.
@post The user will be logged on or off, dependent upon connected.
//*/
public void setLoggedOn( boolean connected );
/**
Returns the user's current logged on state.
@return The user's logged on state.
@exception: None.
@pre None.
@post None.
//*/
public boolean isLoggedOn();
/**
Adds the name of an user that have this user as their buddy to
the appropriate list.
@param buddy = The name of the user that is adding this user to their
buddy list.
@return true if the name was added, false otherwise.
@exception: None.
@pre buddy is an user in the server's database.
@post The buddy will be added.
//*/
public boolean addImBuddy( String buddy );
/**
Returns the ArrayList of user names that have the specified user as
a buddy.
@return The ArrayList of names that have the user as their buddy.
@exception: None.
@pre None.
@post None.
//*/
public ArrayList getImBuddy( );
/**
Adds a buddy to this user's buddy list.
@param buddy = The name of the user to be added as a buddy.
@return true if the buddy was added, false otherwise.
@exception: None.
@pre buddy is an user in the server's database.
@post The buddy will be added.
//*/
public boolean addMyBuddy( String buddy );
/**
Returns the ArrayList of user names that the specified user has as
his/her buddies.
@return The ArrayList of names that the user has as his/her buddies.
@exception: None.
@pre None.
@post None.
//*/
32
public ArrayList getMyBuddies( );
}
--------------------------------------------------------------------------------
Communication Subsystem:
-------------------------------------------------------------------------------/**
Purpose:
Author:
Date created:
Last modified:
Version:
Handles commands.
Group 4
November 8 2003
November 8 2003
1
//*/
interface Command
{
//
Identifies a successful login.
public static final int LOGGED_IN = 2;
//
Identifies a successful command execution.
public static final int SUCCESS = 1;
//
Identifies a failed command execution.
public static final int FAILURE = 0;
//
@inv LOGGED_IN, SUCCESS, and FAILURE are constants and will always take
//
the same values.
/**
Executes the command.
@param o = The object in which the command is being executed; client or
server.
@return A number identifying the outcome of the execution of the method.
@exception: None.
@pre o is either the server or the client object.
@post The command will be executed.
//*/
public int execute( Object o );
}
-------------------------------------------------------------------------------/**
Purpose:
Author:
Date created:
Last modified:
Version:
Handles message objects.
Group 4
November 8 2003
November 8 2003
1
//*/
class MessageCommand implements Command, Serializable
{
//
The message being handled.
private Message m;
//
@inv The Message.destination and Message.source must be user names that
//
exist in the UserList data structure.
//
The Message.message must be a string that is not null.
33
/**
Creates a new MessageCommand object.
@param m = The Message being transmitted.
@return None.
@exception: None.
@pre m is a valid message.
@post The MessageCommand will be created.
//*/
public MessageCommand( Message m );
/**
Executes the Message command.
@param o = The object in which the command is being executed; client or
server.
@return A number identifying the outcome of the execution of the method.
@exception: None.
@pre o is either the server or the client object.
@post The command will be executed.
//*/
public int execute( Object o );
}
-------------------------------------------------------------------------------/**
Purpose:
Author:
Date created:
Last modified:
Version:
Handles Login commands.
Group 4
November 8 2003
November 8 2003
1
//*/
class LoginCommand implements Command, Serializable
{
//
The name of the user attempting to log on.
private String username;
//
The password the user provided when attempting to log on.
private String password;
//
@inv The username and password must be strings that are not null.
/**
Creates a new LoginCommand object.
@param username = The name of the user attempting to log on.
@param password = The password the user provided when attempting to log on.
@return None.
@exception: None.
@pre username and password are the correct values for the specified user.
@post The LoginCommand will be created.
//*/
34
public LoginCommand( String username, String password );
/**
Executes the Login command.
@param o = The object in which the command is being executed; client or
server.
@return A number identifying the outcome of the execution of the method.
@exception: None.
@pre o is either the server or the client object.
@post The command will be executed.
//*/
public int execute( Object o );
}
-------------------------------------------------------------------------------/**
Purpose:
Author:
Date created:
Last modified:
Version:
Handles CompleteLogin commands.
Group 4
November 8 2003
November 8 2003
1
//*/
class CompleteLoginCommand implements Command, Serializable
{
//
The list of all the users that are the logging-on user's buddies.
private ArrayList userList;
//
@inv None.
/**
Creates a new CompleteLoginCommand object.
@param userList = The list of all the users that are the logging-on user's
buddies.
@return None.
@exception: None.
@pre listUsers is an ArrayList with all the users listed.
@post The CompleteLoginCommand will be created.
//*/
public CompleteLoginCommand( ArrayList userList );
/**
Executes the CompleteLogin command.
@param o = The object in which the command is being executed; client or
server.
@return A number identifying the outcome of the execution of the method.
@exception: None.
@pre o is either the server or the client object.
@post The command will be executed.
//*/
public int execute( Object o );
}
-------------------------------------------------------------------------------/**
Purpose:
Author:
Date created:
Last modified:
Handles ContactListUpdate commands
Group 4
November 8 2003
November 8 2003
35
Version:
1
//*/
public class ContactListUpdateCommand implements Command, Serializable
{
//
The name of the user that is a buddy of the logging-on user.
private String userName;
//
The logged on state of that user.
private boolean loggedOn;
//
@inv userName must be a string that is not null.
/**
Creates a new ContactListUpdateCommand object.
@param userName = The name of the user that is a buddy of the logging-on
user.
@param loggedOn = The logged on state of that user.
@return None.
@exception: None.
@pre None.
@post The ContactListUpdateCommand will be created.
//*/
public ContactListUpdateCommand( String userName, boolean loggedOn );
/**
Returns the name of the buddy.
@return The name of the buddy.
@exception: None.
@pre None.
@post None.
//*/
public String getUserName();
/**
Returns the logged on state of the buddy.
@return The logged on state of the user.
@exception: None.
@pre None.
@post None.
//*/
public boolean isLoggedOn();
/**
Executes the ContactListUpdate command.
@param o = The object in which the command is being executed; client or server.
@return A number identifying the outcome of the execution of the method.
@exception: None.
@pre o is either the server or the client object.
@post The command will be executed.
//*/
public int execute( Object o );
}
-------------------------------------------------------------------------------/**
Purpose:
Author:
Date created:
Last modified:
Version:
Handles error commands.
Group 4
November 8 2003
November 8 2003
1
36
//*/
class ErrorCommand implements Command, Serializable
{
//
The message of the message box that will be displayed to the user.
String strMessage
//
The title of the message box that will be displayed to the user.
String strTitle;
//
@inv The strMessage and strTitle must be strings that are not null.
/**
Creates a new error command.
@param strMessage = The message of the message box that will be displayed to the user.
@param strTitle = The title of the message box that will be displayed to the user.
@return None.
@exception: None.
@pre None.
@post The ErrorCommand will be created.
//*/
public ErrorCommand( String strMessage, String strTitle );
/**
Executes the Error command.
@param o = The object in which the command is being executed; client or
server.
@ return A number identifying the outcome of the execution of the method.
@exception: None.
@pre o is either the server or the client object.
@post The command will be executed.
//*/
public int execute( Object o );
}
-------------------------------------------------------------------------------/**
Purpose:
Author:
Date created:
Last modified:
Version:
Handles login error commands.
Group 4
November 8 2003
November 8 2003
1
//*/
class LoginErrorCommand extends ErrorCommand
{
/**
Creates a new LoginErrorCommand object.
@param strMessage = The message of the message box that will be displayed
to the user.
@param strTitle = The title of the message box that will be displayed to
the user.
@return None.
@exception: None.
@pre None.
37
@post The LoginErrorCommand will be created.
//*/
public LoginErrorCommand( String strMessage, String strTitle );
/**
Executes the LoginError command.
@param o = The object in which the command is being executed; client or
server.
@return A number identifying the outcome of the execution of the method.
@exception: None.
@pre o is either the server or the client object.
@post The command will be executed.
//*/
public int execute( Object o );
}
-------------------------------------------------------------------------------/**
Purpose:
Author:
Date created:
Last modified:
Version:
Stores information about a Message.
Group 4
November 8 2003
November 8 2003
1
//*/
class Message implements Serializable
{
//
The user to which this message is to be sent.
private String destination;
//
The user sending this message.
private String source;
//
The message to be sent.
private String message;
//
@inv The destination and source must be user names that
//
exist in the UserList data structure.
//
The message must be a string that is not null.
/**
Creates a new Message object.
@param destination = The user to which this message is to be sent.
@param source = The user sending this message.
@param message = The message to be sent.
@return None.
@exception: None.
@pre None.
@post The Message will be created.
//*/
public Message( String destination, String source, String message );
/**
Returns the name of the destination of this message.
@return The name of the destination of this message.
@exception: None.
@pre None.
@post None.
//*/
public String getDestination();
/**
38
Returns the name of the sender of this message.
@return The name of the sender of this message.
@exception: None.
@pre None.
@post None.
//*/
public String getSource();
/**
Returns the message.
@return The message.
@exception: None.
@pre None.
@post None.
//*/
public String getMessage();
/**
Returns information about the message being sent.
@return The message information.
@exception: None.
@pre None.
@post None.
//*/
public String toString();
}
--------------------------------------------------------------------------------
Appendix E: Diary of Meetings and Tasks
Meeting (1) – 9/5/03@5:30 PM – All group members present
Topic:
Discussed the System Design and number of subsystems:
Server, Client, Storage subsystems. The server can be the storage subsystem.
Classes and Interfaces: We can inherit error messages for the client.
Server:
Manages threads (hosts connections)
Manages information flow (directs messages)
Stores user information
Client:
Spawns windows
Creates information packages
The client has aspects of a user interface subsystem.
The subsystems will be divided into networking, client, and storage.
Create a generic message class, and inherit the different types of messages sent to
the server.
39
Classes: Server thread, client thread, user.
Meeting (2) – 10/30/03@7:20 PM (in-class meeting) – All group members present
Topic:
Discussed the different tasks needed to complete and the division of labor.
We decided on all the tasks that need to be completed and how we were going to
split up the work (partially).
We decided to meet on Saturday to further analyze this and begin work on the
different individual tasks.
Meeting (3) – 11/01/03@3:00 PM – All group members present
Topic:
Began working on the different sections of the SDD document.
Melissa and Ovide began working on sections 3 and 4. Specifically, the Sequence
Diagrams and Class Diagram.
Joshua and Eric were working on sections 1 and 2.
Towards the end of the meeting, Ovide decided that most of sections 1 and 2
could not be completed at the time, so Eric began working on the program’s
command pattern.
Joshua was excused from the meeting at around 6 and about 15 minutes later Eric
was excused from the meeting as well. Ovide and Melissa continued working on
the diagrams past 6.
Meeting (4) – 11/04/03@4:30 PM – Joshua, Melissa and Ovide present.
Topic:
The group got together to discuss the work remaining and what each member
would do. Not much actual work was done, but additional tasks were allocated.
Meeting (5) – 11/08/03@11:00 AM – All group members present
Topic:
In this meeting we all got together to work on the project. Melissa was working
on the program itself, fixing bugs and finalizing it. Ovide was working on the
class diagrams. Josh was working on the glossary, and the remaining of chapter 2
material that still needed to be completed. Eric was documenting the entire
program in JavaDoc style.
As Melissa was fixing the program and Eric was documenting it, changes were
made to it that would reflect the class diagrams. Ovide was notified of these
changes.
40
Download