/**************************************************************************** ** COPYRIGHT (C): 1997 Cay S. Horstmann. All Rights Reserved. ** PROJECT: Practical OO Development with C++ and Java ** FILE: mail.cpp ** PURPOSE: voice mail simulation ** VERSION 1.0 ** PROGRAMMERS: Cay Horstmann (CSH) ** RELEASE DATE: 3-15-97 (CSH) ** UPDATE HISTORY: ****************************************************************************/ /* Project: Console Application Add files mail.cpp Additional include directory: \PracticalOOBook\cpplib */ /* NOTES ******************************************************************** 1. Reaching an extension At the outset, the mail message system awaits the input of a four-digit extension number. Some numbers belong to active extensions, others do not. We will see below how active extensions are created. If an inactive extension has been dialed, an error message is generated, and the system reverts to its initial state. If an active extension has been reached, the mailbox greeting is played. Unless changed by the owner, the greeting is You have reached extension xxxx. Please leave a message now. At this point, the caller can type in a message, by entering the message text on the keyboard. At the end of the message, an `H' should be entered on a single line to denote hanging up the telephone. Only nonempty messages should be stored. Alternatively, callers can enter the `#' key to access their own mailboxes. 2. Accessing a mailbox To restrict access to the owner of the mailbox, the system prompts for a passcode. After the mailbox owner has entered the correct passcode, it is possible retrieve messages from the mailbox, or to change mailbox settings. the user options menu is displayed: You have n new messages and s saved messages. Press 1 to retrieve your messages. Press 2 to change your greeting. Press 3 to change your passcode. (The first command prompt is only shown when messages are pending.) When `1' is pressed, the system enters the message retrieval loop. If the caller selects to change the greeting, the system prompts to record a new greeting. If the caller selects to enter a new passcode, the system prompts to enter a new passcode. Passcodes must be four digits long. Invalid passcodes cause an error message. Upon completion of greeting or passcode change, the main menu is displayed again. If the user hangs up instead of entering a greeting or passcode, no change is recorded. 3. Retrieving messages The first message is displayed. Then the message options menu appears: Press 1 to delete the current message. Press 2 to save the current message. After the selection is processed, the next message is played. This repeats until all messages are played. Then user options menu appears again. At any time, the caller may hang up by entering `H'. New messages are played in the order in which they were received. After all new messages are played, the saved messages are played in the order in which they were saved. 4. Adding new mailboxes When first started, the mail system has one special mailbox, with extension 9999 and passcode 1728, belonging to the administrator and no further active extensions. The administrator mailbox works the same way as all other mailboxes, but has an additional option in the main menu: Press 4 to add a new extension. When `4' is pressed, the system prompts to enter the new four digit extension number and then prompts for a four digit passcode. The extension is activated and the main menu is played again. 5. Simulation of voice data and telephone equipment In our program, we need to simulate the three distinct input events that occur in a real telephone system: speaking, pushing a button on the telephone pad, and picking up and hanging up the telephone. We use the following convention for input: An `H' on a line by itself denotes hanging up the telephone. A sequence of keys `1' ... `9' on a line with no further characters denotes a dialed number. A `#' or `*' on a line by itself denotes pushing one of the command keys on the pad. Any other text denotes voice input. Entering mailbox 0000 terminates the program. 6. System limits We need to set some limits on system resources. The following limits are admittedly unrealistic, but they enable us to concentrate on the object- oriented features rather than memory management during the programming phase. We set a limit on 10 active mail boxes in addition to the administrator mailbox, up to 20 new and 10 saved messages per mailbox Attempts to generate more active accounts are rejected. If a mailbox with 20 new messages is called, the message You have reached extension xxxx. The mailbox is currently full. is displayed instead of the greeting, and no new message can be stored. If the saved message area is full, the mailbox owner can only discard new messages. ****************************************************************************/ /* IMPORT ******************************************************************/ #include "setup.h" #include #include #include /* CONSTANTS ***************************************************************/ const int NMAILBOX = 10; // maximum number of mail boxes const int NNEWMSG = 20; // maximum number of new messages const int NKEPTMSG = 10; // maximum number of kept messages /* TYPES *******************************************************************/ class InputReader /* PURPOSE: read input and separate voice messages, numbers, hangup STATES: normal | end of file */ { public: InputReader(); enum Type { NONE, ENDFILE, NUMBER, COMMAND, HANGUP, MESSAGE }; string get_input(); Type input_type() const; private: string _current; string _next; Type _type; }; /*-------------------------------------------------------------------------*/ class Message /* PURPOSE: a message left by a caller */ { public: Message(); Message(string); void play() const; #ifdef PRACTICALOO_COMPILER_UNREASONABLY_INSISTS_ON_COMPARISON_OPERATORS_FOR_VECTORS bool operator<(const Message& b) const { return _text < b._text; } bool operator==(const Message& b) const { return _text == b._text; } #endif private: string _text; }; /*-------------------------------------------------------------------------*/ class MessageQueue /* PURPOSE: a first-in, first-out bounded collection of messages */ { public: MessageQueue(int size); int length() const; // the number of messages in the queue Message head() const; // get message at head Message remove(); // remove message at head bool append(Message m); // append message at tail private: vector _q; int _head; int _tail; int _length; }; /*-------------------------------------------------------------------------*/ class Mailbox /*˙PURPOSE: A mailbox contains messages that can be listed, kept or discarded STATES: empty | full | neither inactive | active */ { public: Mailbox(); // makes inactive mailbox void receive_message(); void login(); // process passcode virtual void prompt_command(); // prompt for a command virtual void do_command(int); // execute a command Message remove_current(); void retrieve_messages(); void change_greeting(); void change_passcode(); void activate(int extension, int passcode); Message get_current() const; int extension() const; #ifdef PRACTICALOO_COMPILER_UNREASONABLY_INSISTS_ON_COMPARISON_OPERATORS_FOR_VECTORS bool operator<(const Mailbox& b) const { return _extension < b._extension; } bool operator==(const Mailbox& b) const { return _extension == b._extension; } #endif private: MessageQueue _new_messages; MessageQueue _kept_messages; string _greeting; int _extension; int _passcode; }; /*-------------------------------------------------------------------------*/ class AdminMailbox : public Mailbox /* PURPOSE: A mailbox that can add users to the mail system */ { public: virtual void prompt_command(); // prompt for a command virtual void do_command(int); // execute a command void create_mailbox(); }; /*-------------------------------------------------------------------------*/ class MailSystem /* PURPOSE: A system of voice mail boxes */ { public: MailSystem(); void process_dialing(); int locate_mailbox(int) const; bool add_mailbox(int extension, int passcode); private: vector _mailboxes; int _nused; AdminMailbox _admin; }; /* GLOBALS *****************************************************************/ InputReader input_reader; // the reader for all user input MailSystem mail_system; // the voice mail system /* FUNCTIONS ***************************************************************/ int main() { mail_system.process_dialing(); return 0; } /*-------------------------------------------------------------------------*/ InputReader::InputReader() : _type(NONE) { } /*.........................................................................*/ InputReader::Type InputReader::input_type() const /* RETURNS: the type of the last read input REMARKS: First call (and hold onto the return value of) get_input and then call input_type. */ { return _type; } /*.........................................................................*/ string InputReader::get_input() /* PURPOSE: get the next input from the keyboard RETURNS: the string containing the read message or command REMARKS: call input_type after get_input to determine how to interpret the returned string */ { _current = ""; if (_type == ENDFILE) return _current; Type t = MESSAGE; // type of next line while (t == MESSAGE) { if (_next == "") { getline(cin, _next); if (cin.fail()) { _type = ENDFILE; return _current; } } if (_next == "H" || _next == "h") t = HANGUP; else if (_next == "*" || _next == "#") t = COMMAND; else { int len = _next.length(); if (len > 0) t = NUMBER; for (int i = 0; t == NUMBER && i < len; i++) if (!isdigit(_next[i])) t = MESSAGE; } // now t is the type of the item in _next if (_current == "") { _current = _next; _type = t; _next = ""; } else if (t == MESSAGE) { _current += '\n' + _next; _next = ""; } } return _current; } /*-------------------------------------------------------------------------*/ Message::Message() {} /*.........................................................................*/ Message::Message(string t) /* RECEIVES: t - the message text */ : _text(t) {} /*.........................................................................*/ void Message::play() const /* PURPOSE: play the message text */ { cout << _text << endl; } /*-------------------------------------------------------------------------*/ MessageQueue::MessageQueue(int size) /* RECEIVES: size - the maximum queue size */ : _q(size), _head(0), _tail(0), _length(0) {} /*.........................................................................*/ int MessageQueue::length() const /* PURPOSE: return the number of messages in the queue */ { return _length; } /*.........................................................................*/ Message MessageQueue::head() const /* PURPOSE: get message at head */ { assert(length() > 0); return _q[_head]; } /*.........................................................................*/ Message MessageQueue::remove() /* PURPOSE: remove message at head */ { assert(length() > 0); Message r = _q[_head]; _head++; if (_head >= _q.size()) _head = 0; _length--; return r; } /*.........................................................................*/ bool MessageQueue::append(Message m) /* PURPOSE: append message at tail RECEIVES: m - message to append RETURNS: true if success, false if queue full */ { if (_length == _q.size()) return false; // queue full _q[_tail] = m; _tail++; if (_tail >= _q.size()) _tail = 0; _length++; return true; } /*-------------------------------------------------------------------------*/ Mailbox::Mailbox() /* PURPOSE: makes inactive mailbox */ : _extension(0), _passcode(0), _new_messages(NNEWMSG), _kept_messages(NKEPTMSG) { } /*.........................................................................*/ void Mailbox::receive_message() /* PURPOSE: get a message and add it to the mailbox, or log in */ { cout << _greeting << endl; /* now we must read the input because we don't know whether a # or voice will be entered. Unfortunately that means a user will have to make a message even if the mailbox is full. */ while (true) { string s = input_reader.get_input(); InputReader::Type type = input_reader.input_type(); if (type == InputReader::MESSAGE) { if (!_new_messages.append(Message(s))) cout << "You have reached extension " << _extension << ". The mailbox is currently full." << endl; } else if (type == InputReader::COMMAND && s == "#") login(); type = input_reader.input_type(); if (type == InputReader::HANGUP || type == InputReader::ENDFILE) return; } } /*.........................................................................*/ void Mailbox::login() /* PURPOSE: process passcode and commands */ { cout << "Enter passcode:" << endl; string p = input_reader.get_input(); InputReader::Type type = input_reader.input_type(); if (type != InputReader::NUMBER || p.length() != 4 || atoi(p.c_str()) != _passcode) { cout << "Password error!" << endl; return; } cout << "You have " << _new_messages.length() << " new messages and " << _kept_messages.length() << " kept messages." << endl; while (true) { prompt_command(); p = input_reader.get_input(); type = input_reader.input_type(); if (type == InputReader::HANGUP || type == InputReader::ENDFILE) return; do_command(atoi(p.c_str())); type = input_reader.input_type(); if (type == InputReader::HANGUP || type == InputReader::ENDFILE) return; // hung up in one of the subsidiary commands }; } /*.........................................................................*/ void Mailbox::prompt_command() /* PURPOSE: prompts for a command */ { if (_new_messages.length() + _kept_messages.length() > 0) cout << "Press 1 to retrieve your messages." << endl; cout << "Press 2 to change your greeting." << endl << "Press 3 to change your passcode." << endl; } /*.........................................................................*/ void Mailbox::do_command(int s) /* PURPOSE: executes one command RETURNS: true if ok, false on eof or hangup */ { if (s == 1) retrieve_messages(); else if (s == 2) change_greeting(); else if (s == 3) change_passcode(); } /*.........................................................................*/ void Mailbox::retrieve_messages() /* PURPOSE: command loop to retrieve mailbox messages */ { MessageQueue msgs_to_keep(NKEPTMSG); while (_new_messages.length() + _kept_messages.length() > 0) { Message current = get_current(); current.play(); bool done = false; while (!done) { cout << "Press 1 to delete the current message." << endl << "Press 2 to save the current message." << endl; string s = input_reader.get_input(); InputReader::Type type = input_reader.input_type(); if (type == InputReader::HANGUP || type == InputReader::ENDFILE) done = true; else if (s == "1" || s == "2") { remove_current(); if (s == "2") msgs_to_keep.append(current); done = true; } } } while (msgs_to_keep.length() > 0) _kept_messages.append(msgs_to_keep.remove()); } /*.........................................................................*/ void Mailbox::change_greeting() /* PURPOSE: command to change mailbox greeting */ { cout << "Please enter your new greeting." << endl; string s = input_reader.get_input(); InputReader::Type type = input_reader.input_type(); if (type == InputReader::MESSAGE) _greeting = s; else cout << "Greeting not changed." << endl; } /*.........................................................................*/ void Mailbox::change_passcode() /* PURPOSE: command to change mailbox passcode */ { cout << "Please enter your new passcode." << endl; string s = input_reader.get_input(); InputReader::Type type = input_reader.input_type(); if (type == InputReader::NUMBER && s.length() == 4) _passcode = atoi(s.c_str()); else cout << "Passcode not changed." << endl; } /*.........................................................................*/ Message Mailbox::remove_current() /* PURPOSE: remove the current message from the mailbox RETURNS: the removed message */ { if (_new_messages.length() > 0) return _new_messages.remove(); else return _kept_messages.remove(); } /*.........................................................................*/ Message Mailbox::get_current() const /* RETURNS: the current message */ { if (_new_messages.length() > 0) return _new_messages.head(); else return _kept_messages.head(); } /*.........................................................................*/ int Mailbox::extension() const /* RETURNS: the extension number */ { return _extension; } /*.........................................................................*/ void Mailbox::activate(int extension, int passcode) /* PURPOSE: activate a mailbox RECEIVES: extension - the extension number for the mailbox passcode - the initial passcode */ { _extension = extension; _passcode = passcode; char buffer[10]; itoa(extension, buffer, 10); _greeting = "You have reached mailbox " + string(buffer) + ". Please leave a message now."; } /*-------------------------------------------------------------------------*/ void AdminMailbox::prompt_command() /* PURPOSE: prompts for and executes one command RETURNS: true if command entered, false on eof or hangup */ { Mailbox::prompt_command(); cout << "Press 4 to add a new extension." << endl; } /*.........................................................................*/ void AdminMailbox::do_command(int s) /* PURPOSE: prompts for and executes one command RETURNS: true if command entered, false on eof or hangup */ { if (s == 4) create_mailbox(); else Mailbox::do_command(s); } /*.........................................................................*/ void AdminMailbox::create_mailbox() /* PURPOSE: prompts user for information to create a new mailbox */ { cout << "Please enter the extension number." << endl; string s = input_reader.get_input(); InputReader::Type type = input_reader.input_type(); if (type == InputReader::NUMBER && s.length() == 4) { int e = atoi(s.c_str()); cout << "Please enter the passcode." << endl; s = input_reader.get_input(); type = input_reader.input_type(); if (type == InputReader::NUMBER && s.length() == 4) { int p = atoi(s.c_str()); if (mail_system.add_mailbox(e, p)) return; } } cout << "Mailbox not created." << endl; } /*-------------------------------------------------------------------------*/ MailSystem::MailSystem() : _nused(0), _mailboxes(NMAILBOX) { _admin.activate(9999, 1728); } /*.........................................................................*/ int MailSystem::locate_mailbox(int e) const /* PURPOSE: locate a mailbox RECEIVES: e - the extension number RETURNS: the position of the mailbox in the _mailbox array, -1 if not found */ { for (int i = 0; i < _mailboxes.size(); i++) if (_mailboxes[i].extension() == e) return i; return -1; } /*.........................................................................*/ bool MailSystem::add_mailbox(int extension, int passcode) /*˙PURPOSE: Add a new mailbox to the system RECEIVES: extension - the extension number of the mailbox RETURNS: true iff the operation succeeded REMARKS: The operation may fail if the extension number is already in use, or if there is no space to add another mailbox */ { if (_nused >= NMAILBOX) return false; int i = locate_mailbox(extension); if( i > 0 ) return false; // duplicate _mailboxes[_nused].activate(extension, passcode); _nused++; return true; } /*.........................................................................*/ void MailSystem::process_dialing() /* PURPOSE: command loop for dialing an extension number REMARKS: terminates at end of file, or when extension 0000 is entered. */ { while (true) { cout << "Enter extension." << endl; string s = input_reader.get_input(); InputReader::Type type = input_reader.input_type(); if (type == InputReader::ENDFILE) return; if (type == InputReader::NUMBER && s.length() == 4) { int n = atoi(s.c_str()); if (n == 0) return; else if (n == 9999) _admin.receive_message(); else { int i = locate_mailbox(n); if (i >= 0) _mailboxes[i].receive_message(); else cout << "Not an active extension." << endl; } } else cout << "Input error." << endl; } } /***************************************************************************/