PA2: Big Box Bargains

Programming Assignment 2

Big Box Bargains

Learning Objectives

This programming assignment is designed to help you learn several things. First, it will help you learn about interfaces and abstract classes, and improve your understanding of specialization. Second it will help you learn about polymorphism and dynamic binding. Third, it will help you learn about static binding. Finally, it will help reinforce your understanding of I/O, packages, and unit testing.

Overview

Big Box Bargains (BBB), a new “big box” store that hopes to compete with stores like Costco and Sam’s Club, has contracted with you to write some of the software they need for their checkout system.

The Components to be Written

You must write the Account and VIPAccount classes that encapsulate membership information that is specific to BBB, as well as some general purpose components for performing input/output operations on comma-separated-value (CSV) files. The relationship between these components is illustrated in the following UML class diagram.

Class Diagram

Note that methods in a class that are “concrete” implementations of abstract methods (that are listed either in an interface or an abstract class) are not explicitly listed in the class (e.g., the toCSV() method in the Account class). Method in a derived class that override a method in a base class are explicitly listed in the derived class (e.g., the toCSV() method in the VIPAccount class).

The CSVRepresentable Interface

The CSVRepresentable interface describes the capabilities of objects that can be represented in simplified CSV format (i.e., all of the fields are delimited by commas, the fields are assumed to not contain commas, and there are no column headers). The toCSV() method says that such an object must be able to create a String representation of its attributes in simplified CSV format. The fromCSV() method, on the other hand, says that such an object must be able to initialize its attributes from a String representation in simplified CSV format.

Obviously, the fromCSV() method must tokenize the String that it is passed (that has a comma as the delimiter between tokens). In principle, this can be done in many ways, but you must choose between two. Specifically, you must use either a Scanner or a StringTokenizer (both of which are in the java.util package) for this purpose. The fromCSV() method returns the object used for tokenizing the String so that it may be used to tokenize any remaining tokens in the String. In other words, the String representation may contain more tokens than the fromCSV() method uses, so it must return the object used for tokenzing in case these tokens need to be used elsewhere.

The FileIdentifier Class

The FileIdentifier class encapsulates the “classic” two-part approach to identifying files. In this “classic” approach, a file identifier consists of a name (the portion to the left of the dot) and an extension or type (the portion to the right of the dot). So, for example, one might identify the file named Student.java using a FileIdentifier that has a name attribute of "Student" and an extension attribute of "java".

This class implements/realizes the CSVRepresentable interface because it is common to include FileIdentifier objects in CSV files.

The toString() method must return the name and extension formatted using the format String "%s.%s".

The FileProcessor Class

The FileProcessor class is an abstract encapsulation of an object that can read from and write to the file system in a line-oriented way. The first line of any file it reads or writes must contain the number of lines in the file.

This class supports encryption and decryption, but those capabilities must be provided by derived classes. In addition to the specifications contained in the UML class diagram, this class must conform to the following specifications.

  1. The readLines() method must first read the number of lines in the file. It must then read the subsequent lines, decrypt them, and return a String[] array that contains the decrypted lines.

  2. The writeLines() method must first write the length of the parameter named lines (on a line of its own). It must then encrypt the elements and write them to the file (one element per line).

The CSVFileProcessor Class

The CSVFileProcessor class is a concrete specialization of the FileProcessor class. In addition to the specifications contained in the UML class diagram, this class must conform to the following specifications.

  1. The decrypt() and encrypt() methods must return the parameters they are passed unchanged. In other words, CSV files must not be encrypted.

  2. The write() method must create a CSV representation of the objects it is passed and write all of them to the appropriate file (that is passed to the constructor), one per line.

  3. The read() method must read all of the records from the appropriate file (that is passed to the constructor) and initialize the attributes of the objects that it is passed. It must assume that it is passed an array of objects that has the same number of elements as there are records (one per line) in the file. (Note: This method does not return the elements it reads. Instead, it sets the attributes of each CSVRepresentable object it is passed, using that CSVReprentable object’s fromCSV() method.)

  4. The write() and read() methods must not duplicate any code in the FileProcessor class or in the class of the CSVRepresentable objects that it is passed.

The Account Class

The Account class is an encapsulation of a membership account at BBB. In addition to the specifications contained in the UML class diagram, this class must conform to the following specifications.

  1. The purchases attribute must be used to keep track of the total purchases made by the member (measured in dollars), and the creditsUsed attribute must be used to keep track of the total credits used by the member (measured in dollars).

  2. The default constructor must initialize both attributes to 0.00.

  3. The availableCredit() method must return the available credit, which is based on a percentage (obtained from getRewardPercentage()) of the purchases attribute minus the credits used to date. So, for example, if purchases is 2000.00, getRewardsPercentage() returns 0.01, and creditsUsed is 5.00, then this method must return 15.00. Note that the value returned must never be less than 0.00. So, for example, if purchases is 2000.00, getRewardsPercentage() returns 0.01, and creditsUsed is 25.00, then this method must return 0.00.

  4. An Account holder can use the express line when the total purchases made by the member is greater than 1000.00.

  5. The getRewardPercentage() method must return 0.01 (i.e., 1 percent) for Account holders.

  6. The increaseCreditsUsed() method must increase the creditsUsed attribute by the given amount. It must never decrease the creditsUsed attribute (i.e., it must do nothing when the parameter is negative).

  7. The increasePurchases() method must increase the purchases attribute by the given amount. It must never decrease the purchases attribute (i.e., it must do nothing when the parameter is negative).

  8. The purchase() method must process a purchase of the given amount (when it is greater than 0.00), using as many credits as are available when the applyCredits parameter is true (in which case it must invoke the increaseCreditsUsed() method, passing it the appropriate value). It must invoke the increasePurchases() method passing it the amount due (i.e., the amount of the purchase less any credits that are used), and must return the amount due. For example, if passed 100.00 and true when the Account holder has 20.00 credits available, it must pass 20.00 to increaseCreditsUsed(), pass 80.00 to increasePurchases() and must return 80.00. The amount due must never be less than 0.00, but can be 0.00 (when enough credits are available). The credits used must never be greater than the purchase.

  9. The toCSV() method must return a String representation of the purchases and credits used, formatted using a format String of "%.2f,%.2f".

  10. The fromCSV() method must set the attributes of the owning object. It may assume that the String it is passed was created by the toCSV() method and must not do any error checking.

  11. The toString() method must use return a String representation of the purchases, credits used, and available credit, formatted using a format String of "Purchases: %.2f\nCredits Used: %.2f\nCredits available: %.2f".

The VIPAccount Class

The VIPAccount class is an encapsulation of an exclusive membership account at BBB. (“VIP” is an acronym for “very important person”.) In addition to the specifications contained in the UML class diagram, this class must conform to the following specifications.

  1. The visits attribute must keep track of the number of visits to BBB in which the member made a purchase.

  2. The default constructor must initialize all attributes to 0.00.

  3. A VIPAccount holder can use the express line whenever an Account holder can. A VIPAccount holder can also use the express line when they have 10 or more visits.

  4. A VIPAccount holder has the same starting reward percentage as an Account holder, however, it can increase. Specifically, for every ten visits, a VIPAccount holder’s reward percentage increases by 0.01 up to a maximum of 0.15 (i.e., it can bever be larger than 15 percent). For example, a VIPAccount holder who has made 26 visits will have a reward percentage of 0.03 (i.e., 0.01 + 0.02) and a VIPAccount holder who has made 371 visits will have a reward percentage of 0.15.

  5. Each time the increasePurchases() method is invoked, it must increase the number of visits by 1. Of course, it must also increase the purchases attribute in the base class.

  6. The toCSV() must return a String representation of the result of invoking the toCSV() method in the base class followed by the number of visits, formatted using a format String of "%s,%d".

  7. The fromCSV() method must set all of the attributes of the owning object. It may assume that the String it is passed was created by the toCSV() method and must not do any error checking.

  8. The toString() must return a String representation of the result of invoking the toString() method in the base class followed by the number of visits, formatted using a format String of "%s\nVisits: %d".

  9. No method in the VIPAccount class may duplicate code in the Account class.

Unit Testing

You must write JUnit tests for all of your classes. Your JUnit test suite must cover all statements and all branches (as measured by EclEmma) in all of the classes you write. Your tests must be in a package named testing and each test class must include the word “Test” in its name.

As in the past, your unit tests of the input/output methods must make use of round-trip testing. So, you must create a JUnit test that first executes the appropriate write method and then executes the appropriate read method. Note that, to ensure that the write happens before the read, the two must be in the same JUnit test. You can (and should) have multiple tests of this kind, but each must be independent of the others.

Submission

You must submit (using Gradescope) a .zip file named pa2.zip that contains:

  1. CSVRepresentable.java, FileIdentifier.java , FileProcessor.java, CSVFileProcessor.java, Account.java, and VIPAccount.java; and

  2. Your JUnit tests

packaged appropriately. The io, membership, and testing directories/folders must be at the top of the .zip file. (See the previous programming assignment if you have forgotten how to do this.) Do not submit any .bbb files.

Your grade will be reduced by 5 points for each submission after the 10th submission. So, you should try to ensure that your code is correct before you submit it the first time. In other words, you should not use Gradescope to check your style, to test your code, or to ensure that your tests cover your code - you should do all of that on your computer before you make any submissions to Gradescope.

Note that your submission will not be graded if it does not comply with the specifications. So, if you do not complete a class your submission should include a stubbed-out version of it. This will allow you to potentially get credit for the methods/classes that you do complete.

Grading

Your code will first be graded by Gradescope and then by the Professor. The grade you receive from Gradescope is the maximum grade that you can receive on the assignment

Gradescope Grading

Your code must compile (in Gradescope, this will be indicated in the section on “Does your code compile?”) and all class names and method signatures must comply with the specifications (in Gradescope, this will be indicated in the section on “Do your class names, method signatures, etc. comply with the specifications?”) for you to receive any points on this assignment. Gradescope will then grade your submission as follows:

CriterionPointsDetails
Conformance to the Style Guide0Success Required
Passing Your Tests10All or Nothing; Success Required
Coverage of Your Tests20Partial Credit Possible
Correctness70Partial Credit Possible

As discussed above, your grade will be reduced by 5 points for each submission after the 10th submission. Gradescope will provide you with hints, but may not completely identify the defects in your submission.

Manual Grading

After the due date, the Professor may manually review your code. At this time, points may be deducted for code that does not conform to the specifications, inelegant code, duplicate code, inappropriate variable names, bad comments, etc.

At this point in the semester, you should be able to create a good process and use it.

Questions to Think About

You don’t have to submit your answers to these questions, but you should try to answer them because they will help you determine whether or not you understand some of the important concepts covered in this assignment. Note that you do not need the source code for the CashRegister class to answer them.

  1. Suppose you have a CashRegister class in which a canUseExpressLine() message is sent to an Object named member, which is declared to be an Account but can actually be either an Account or a VIPAccount. Both classes have such a method. What code is invoked and why?

  2. Suppose you have a CashRegister class in which the availableCredit() message is sent to an Object named member, which is declared to be an Account but can actually be either an Account or a VIPAccount. The only availableCredit() method is in Account.java so, obviously, that code will be invoked. However, the getRewardPercentage() method is then invoked, and this method is implemented in both classes. What code will be invoked and why?

  3. Suppose you have a CashRegister class in which the purchase() message is sent to an Object named member, which is declared to be an Account but can actually be either an Account or a VIPAccount. The only purchase() method is in Account.java so, obviously, that code will be invoked. However, the increasePurchases() method is then invoked, and this method is implemented in both classes. What code will be invoked and why?

  4. Suppose you have a CashRegister class that has an overloaded addToLog() method, one which is passed an Account and of which is passed a VIPAccount. Suppose further that this method is passed an Object named member which is declared to be an Account but can actually be an Account or a VIPAccount, both of which implement the CSVRepresentable interface. Which overloaded version of addToLog() will be invoked and why?

  5. Why is the FileProcessor class declared to be abstract?

  6. Will the following fragment compile? Why or why not?

    FileProcessor fp;
    fp = new CSVFileProcessor("temp.csv");
    
  7. In the previous programming assignment, the io package was a subpackage of the product package. In this assignment, the io package is not inside the membership package. Why not?