CSE 11 Programming assignment #4


Here's a description of the fourth programming assignment for CSE 11. In this assignment, you will learn about the inheritance and Java's error handling scheme, exceptions.

>>> Due deadline: ...

>>> Required solution files: FullPerson.java FullAccount.java ElevenDollar.java NoUserDataException.java ErrorInBalanceException.java IMAccount.java

This means that the files that contain your solution to the assignment must be turned in by the due deadline. Use the bundleP4 script to turn in your assignment.

As usual, it's a good idea to plan to do the assignment well before the deadline; terminals and tutors can get very busy at the last minute. And of course In doing this assignment, it's your responsibility to understand the course rules for integrity of scholarship.

1.Background

A big advantage of Object Oriented Programming is how it enables re-using code. Sometimes you want to take an existing class and extend it, so it does everything the old class did and more. Sometimes you will want to take an existing class and make use of some of it, but discard the rest.

This assignment explores these OOP principles with P3 as its background. We will extend Person and Account so they do everything they used to and a bit more. Then we will extend our new Account, this time picking and choosing what we want to use, and applying those to a different applcation. Along the way we will attempt to get the most "bang for the buck". That is, we'll try to re-use code, to write as little new code as possible, and yet have it still fulfill our varying needs. Each of the classes you write in this assignment should be relatively small--less than fifty lines in most cases, with a good deal of those being whitespaces or comments. Consequently, you'll be writing a number of small class definitions.

We'll also be adding some error handling the way Java wants it to be done, with exceptions. Exceptions are objects that act as packages of error information. Exception classes are also extremely short--often no more than a single constructor. These will allow us to create our own customized errors for our application, and will fix some shortcomings that P3 had in checkBalance and updateInformation.

As a side note, the base classes Person and Account are immutable here--that is, we will assume they were published a year ago(time flies in the quarter system) and have now become code that we simply cannot modify. In this respect, old code is like an old furniture layout--notoriously difficult to modify after some time has passed.

2.The Usual

You will need Person and Account class definitions in order to use these as base classes. Person and Account class files which correctly implement the P3 requirements will appear in the ~/../public/P4/directory after the P3 deadline. These files will be used by us when we test your P4 code.  Or, you can use your P3 definitions of these classes and everything will work if they are correct. In fact, the big picture will be clearer for you if you've written them and understand the source code.

3.FullAccount.java, FullPerson.java

P3's Account and Person had a very primitive information system. You had a name and a password, but no real information to protect. Here we'll be adding more fields to Person, to flesh out some more detailed information a Person can hold. Specifically, you will be adding the four following fields: Your quote and your trades are naturally public information--they provide either amusement or purpose to other people, and should be visible to all users. However, you will want some protection on your email and almost certainly on your screen name. The server will provide these features--you need only provide the interface to the user.

The FullPerson part

The first thing to do is modify our concept of a Person so it has six pieces of data instead of two. Ordinarily this would require you to open up the old Person file, remember how everything worked, and tweak things. This, as many programmers can tell you, is an unenviable task.

Instead we will create a new file,

FullPerson.java
which will extend the original class. In this manner, the old Person still exists, and any program that used the old Person will still run. But we will still make use of it, and create a new class that does all a Person does and more. You will add four instance variables in FullPerson, with their associated accessors and mutators:
//accessors
public String getQuote()
public String getTrades()
public String getEmail()
public String getScreen()

//mutators
public void setQuote(String newField)
public void setTrades(String newField)
public void setEmail(String newField)
public void setScreen(String newField)
(If this seems like a lot of code, see the footnote below.) You will add two constructors, to match the original two in Person. One will take six separate Strings, and the other will take a netString, which is a format similar to the server output.
public FullPerson(String nick, String pass, 
String quote, String trades,
String email, String screen)

public FullPerson(String netString)
You will also override the toString() and netString() operators so they will output all six fields in a human readable format and a tab-delimited format(suitable for ElevenClient, as before).
/**Prints out the data in a human readable format.*/
/**If the following code is executed...
System.out.println(new FullPerson("John", "Doe",
"To be or not to be",
"Surfboard lessons",
"john_doe@imaginary.place.net",
"sdKing15123"));
Then the following will be printed...
Name:John
Password:Doe
Quote:To be or not to be
Trades:Surfboard lessons
Email:john_doe@imaginary.place.net
Screen:sdKing15123
*/
public String toString()

/**Prints out the data in a tab-delimited format.*/
public String netString()
Note that because the original Person class's fields are private, you will need to use the original toString() and netString() inside the methods you'll be writing.

The FullAccount part

Now that we have a FullPerson with more fields, we'll want Account to take advantage of the improvements this allows. The server has a few new messages for this portion:
Command Format Description Server Reply
GETINFO\t[name]\t[password] Get another user's info, and uses their password. Only returns the user and password, so its use is limited. USERINFO\t[nickname]\t[password]
GETDETAILS Get your detailed information(quote, trades, email and screen) from the server. USERINFO\t[quote]\t[trades]\t[email]\t[screen]
GETDETAILS\t[name] Get another user's detailed info(quote, trades, email and screen) without using their password. Without the password, the server will hide the email and screen on response. USERINFO\t[quote]\t[trades]\t*PRIVATE*\t*PRIVATE*
GETDETAILS\t[name]\t[password] Get another user's detailed info(quote, trades, email and screen), this time using their password. All fields will be returned. USERINFO\t[quote]\t[trades]\t[email]\t[screen]
UPDATEINFO\t[name]\t[password] \t[quote]\t[trades]\t[email]\t[screen] Update your stored information. UPDATE CONFIRMED

FullAccount will extend Account and override the original getInformation methods in Account so they return a FullPerson. Note that by overriding the original methods the return type is unchanged.

This may seem a bit worrisome--how do you return a FullPerson if you're say you're going to return a Person? The answer is that Java allows this because FullPerson extends Person. A FullPerson "is a" Person. Anything you could ask of a Person, you can ask of a FullPerson. So the calling code won't break if you return a subclass of what it expects.

In addition you will add another method that overloads the original getInformation, so that it can take a user and a password. This one will be called to retrieve all information about another user, even the fields that are password-protected by the server.

public Person getInformation()
Sends a GETINFO request and a GETDETAILS request to the server. Parses the replies and creates a new FullPerson object with all six of its fields initialized. Returns this new FullPerson object.

public Person getInformation(String username)
Sends a GETINFO request and a GETDETAILS request to the server, each with one argument. Parses the replies and returns a FullPerson object. Note that the password replied by the server will be "*SECRET*". The email and screen replied by the server will both be "*PRIVATE*"

public Person getInformation(String username, String password)
Sends a GETINFO request and a GETDETAILS request to the server, each with two arguments. Parses the replies and returns a FullPerson object. All fields will be returned by the server in this case.

The remaining methods will need no changes, provided they were written properly in Account. Even updateInformation, which required a change in a server message, should function identically, as the new netString() method in FullPerson should handle the increase in fields. Test these methods with a short main before tackling ElevenDollar. That is, add a main method to FullAccount, and call each of the methods with fixed arguments. Below is an example.
public static void main(String [] args) {
System.out.println(getInformation());
System.out.println(getInformation("cs11w2"));
System.out.println(checkBalance()));
/*
.
. Continue to test until you're satisfied that your FullAccount and
. FullPerson work.
.
*/
}

4.ElevenDollar

The ElevenDollar class will need some modifications to make use of the new FullAccount. If you followed the template in P3, almost all of those changes should be limited to the two static methods outside of main:handleGetInformation and handleUpdateInformation.

Start by modifying the line in main that creates the Account. It should read something like this:

Account myAccount = new Account();
Modify it so it now reads:
Account myAccount = new FullAccount();

Now we have just two methods to work on.

handleGetInformation

Modify handleGetInformation so it maintains the original signature, but makes use of the new getInformation method in Account. If the user asks for a different account, ask the user for an optional password.

Most of this will be the same--Something important to note is the use of dynamic binding. That is, the original getInformation() and getInformation(name) will provide a different output, even though the call is exactly the same. This is because Java will decide on which method to call based on which object it's being called on. It doesn't matter that the original variable is of type Account, it matters that at runtime it's actually a FullAccount.

When actually making the call to the getInformation(name, password) method, however, you'll need to use a typecast. This is because although you as the programmer know that you have a FullAccount inside handleGetInformation, the compiler doesn't. It sees the type as Account, and can't find any two-argument getInformation there.

Often times you'll need to tell the compiler you know that what you have is a certain kind of class, or that in this case a Person is actually a FullPerson. The typecast forces it to assume you're right--consequently, if you're wrong and you actually had a Person, and you tried to call a method FullPersons know about and Persons don't, then you'll receive a ClassCastException as it runs.

Be sure to parenthesize appropriately when you typecast, as follows:

((FullAccount) myAccount).getInformation(arg1, arg2);

handleUpdateInformation

handleUpdateInformation can be boiled down to "more of the same". Whereas before you had two fields, now you have six, so execute some judicious copy/paste and search-and-replace. See the second footnote about general practice in these cases.

Here our typecast problem will crop up many times. For every prompt you'll use an accessor, and for every user input you'll use a mutator. This, to put it mildly, is painful, and it'd be only worse if we decided we needed to make some more changes.

So instead of doing that, we will modify the method prototype so it expects a FullAccount. We can do this because we're not overriding anything--the method prototype is ours to change. Modify it so it reads as follows:

public static void handleUpdateInformation(FullAccount myAccount)

Now you only need one typecast, inside the method call.

See the sample run for how ElevenDollar should behave differently after your changes.

Two Exceptions for ElevenDollar

In previous projects, there have been times where a method could fail in a spectacular, undefined manner if something was wrong. P2's normalize method, for instance, could fail if a Rational had a 0 somewhere. In fact, it could fail at any time if someone made a Rational with a 0 in the denominator.

Likewise in P3, if an Account was created and its person never initialized, it could fail in updateInformation. And if you tried to check your balance and it somehow returned ERROR, you couldn't return ERROR in a string because it said it would return a double.

Exceptions provide a mechanism for a method to tell the caller about things that the method can't handle. The caller can handle the exception in a clean fashion, instead of dying with an unsightly stack trace.

Exceptions follow an interesting baseball-ish sort of analogy. Exceptions are "thrown" from a method that doesn't know what to do. If it was called from within a "try" block, then execution moves to the "catch" block after the try. This would take a lengthy explanation, so if you are unfamiliar with exceptions, take a look at chapter 9 of the textbook for the details.

We will be writing two exceptions in FullAccount, both of which will extend RuntimeException. This way we won't need to declare them in a  throws clause, and so we won't have to modify the method signatures. Remember that because we extended a class, to override methods we have to use the same signature. This allows us to add exception handling, and override the methods appropriately.

Now that errors are being noticed, we need to deal with them. Modify
ElevenDollar.java
so it has a try/catch clause and prints out an appropriate error message if an exception is thrown. After handling the exeception, return to normal execution and prompt the user as usual.

5.IMAccount.java

As mentioned earlier, sometimes you only want to inherit some methods of a base class. OOP purists often object to this, because it goes against the principle that everything that is-a X should have all the behaviors of an X. Nevertheless, in practice it is often convenient. It happens enough that in Java there is a specific kind of RunTimeException, UnsupportedOperationException, for the purpose of signalling that a base class's method has been prevented from executing. (You might think that nother way to do it would be to make the method private in the derived class; but Java does not let you override a public method in that way.)

The code we have now is something similar to an ATM--it has accounts, information, the ability to transfer money and remember the transaction history. However, suppose we wanted to build a messaging client with the existing software, similar to AIM or Yahoo Messenger. Note that our server has no way of asking a client to respond. Therefore, a client must always poll, i.e. ask, for messages, which is a bit different from AIM.

Part of the code is similar--we need an interface to a server, accounts with information, and the ability to get and update that information. This we will inherit.

Part of the code is different--we don't need anything relating to 11-dollars anymore, such as checking your balance, transferring money, and remembering a transaction history. These we will "hide" with the UnsupportedOperationException.

Finally we'll need to add a few methods, and create a different user interface. But at the end of it, we'll have re-used a lot of old code to apply to a completely different application.

To start with, the server will understand three final commands for this new application.

Command Format Description Server Reply
BUDDYLIST\t[name]\t[password] Adds a user to your buddylist. The password must be correct for this to be successful. BUDDYLIST UPDATED
LEAVEMESSAGE\t[user]\t[message] Leave a message for another user. The user must be on your buddylist for this to be successful. MESSAGE SENT
CHECKIMINFO See when your buddies were last connected to the server, and if there are any messages left for you. The last 100 messages are stored on the server and all of them will be retrieved in this message. Buddy activity:\n
[last logins of buddies]
Messages:\n
[messages]

Create a class named

IMAccount.java
that extends FullAccount. Add three methods to implement the previous three commands.

  /**Add a user to the owner's buddy list.*/
public String addToBuddyList(String user, String pass)

/**Send a message to another user. This user must be on the buddy list,
otherwise error is returned. The server's reply is returned.*/
public String leaveMessage(String target, String message)

/**Get info about buddies, and retrieve all messages.*/
public String checkIMInfo()

Add three methods to override the unwanted abilities of FullAccount. Each of these will simply throw UnsupportedOperationException.

public double checkBalance()
public String transferToAcct(String user, double amount, String comment)
public String getHistory()
Finally, we will need to write a new front-end for our IMClient. This is the analogue of ElevenDollar with a similar interface and format, but written for a different application. This portion of code will be given to you in a file named IMClient.class--if the rest of your code works, this should allow you to run a text-based and time-delayed version of a messenging client.

6.FAQ

Do I have to give out my real info?
No. Well, you can give out your cs11w email, probably. On the other hand, there's only fifty people in the class, so it's unlikely you'll find an anonymous stalker.

Updating the information looks easy when I'm using my program, but it's a lot of repeated code. Is there a better way than copy-pasting from P3's ElevenDollar a whole lot?
Often times user input interaction is a lot of work. There are some things you could do, for instance. If you wrote a static method that took in input and returned a boolean of whether it was blank...

The server seems to be remembering more than the last 100 messages. What's going on?
This is a bug discovered after the server was released in P3. Sorry folks.

How do I remove people from my buddylist?
You can't. Again, sorry, but three methods seemed optimal for the size of IMAccount.

7.ElevenDollar Sample Run

Welcome to the 11-dollar banking system
A)Retrieve personal information
B)Update your personal information
C)Check your balance
D)Transfer money to another account
E)Check your transaction history
X)Exit
a Which account?(Leave blank to retrieve your own) cs11w Optional password? Nickname:The Professor Password:*SECRET* Quote:"Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it." Trades:Java expertise, code consulting Email:*PRIVATE* Screen:*PRIVATE* Welcome to the 11-dollar banking system A)Retrieve personal information B)Update your personal information C)Check your balance D)Transfer money to another account E)Check your transaction history X)Exit a Which account?(Leave blank to retrieve your own) cs11w Optional password? 11_4ever Nickname:The Professor Password:11_4ever Quote:"Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it." Trades:Java expertise, code consulting Email:kube@cs.ucsd.edu Screen:none Welcome to the 11-dollar banking system A)Retrieve personal information B)Update your personal information C)Check your balance D)Transfer money to another account E)Check your transaction history X)Exit a Which account?(Leave blank to retrieve your own) Nickname:SupercoolStudent Password:11isgr8 Quote:Episode 4, A New Assignment Trades:pencil drawings Email:11stud@imaginary.place.net Screen:skywalker Welcome to the 11-dollar banking system A)Retrieve personal information B)Update your personal information C)Check your balance D)Transfer money to another account E)Check your transaction history X)Exit b New Nickname?(default:SupercoolStudent) New Password?(default:11isgr8) New Quote?(default:Episode 4, A New Assignment) I like coffee, I like tea. I love the Java Jive and it loves me. New Trades?(default:pencil drawings) New Email?(default11stud@imaginary.place.net) New Screen?(default:skywalker) inkspots1940 UPDATE CONFIRMED Welcome to the 11-dollar banking system A)Retrieve personal information B)Update your personal information C)Check your balance D)Transfer money to another account E)Check your transaction history X)Exit a Nickname:SupercoolStudent Password:11isgr8 Quote:I like coffee, I like tea. I love the Java Jive and it loves me. Trades:pencil drawings Email:11stud@imaginary.place.net Screen:inkspots1940 Welcome to the 11-dollar banking system A)Retrieve personal information B)Update your personal information C)Check your balance D)Transfer money to another account E)Check your transaction history X)Exit x

Grading of assignment #4

There are 20 possible points for this assignment, broken down as follows:
a.  5  points for your FullPerson.java
b. 5 points for your FullAccount.java
c. 4 points for your ElevenDollar.java
d. 1 points for your NoUserDataException.java
e. 1 points for your ErrorInBalanceException.java
f. 4 points for your IMAccount.java
In each case, doing a part of the assignment means that your code compiles, runs correctly, satisfies the specifications, is well-written and commented.


Footnotes

1 Some of you may be thinking--what a waste! Why do we need accessors and mutators? Just make the variables public and be done with it! And that would in fact save us all this code you're about to write.

But the price of making those variables public is that we lose control of the access. At some later date we might want to prevent someone from changing a variable, or from reading it. With methods, we can override or modify them so access has some control. Without the accessors and mutators, there is no way to keep the variable safe--not without making the variable private and breaking all the code built on it.

2 Copy/paste is in general a bad practice--any time you see yourself duplicating code, there's a good chance you could've written a single method and just called it several times with different arguments. It will be easier to debug, easier to maintain, and clearer to read. This is the case in FullAccount, for instance. Several GETINFO and GETDETAILS calls can really be "factored" into one method, with the differences being passed in as arguments. Suggested helper methods like getNth and trimToken() are the same.

In this case, however, the varying components(as you will see) are method calls, which you in general cannot pass. You could pass objects and rely on dynamic method binding, but it's a bit more trouble than it's worth here.

Hence the advice to copy paste.