PA2: MovieTix¶
Learning Objectives¶
This assignment is designed to help you learn several things. First, it will help you learn about interfaces and abstract classes. Second, it will help improve your understanding of specialization, polymorphism, and dynamic binding. Third, it will help improve your understanding of static binding. Finally, it will help reinforce your understanding of I/O and packages.
Danger
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.
Overview¶
PayTex is a (fictitious company) that develops technologies for electronic payment and crypto-currency systems. You have been hired by them to develop several components of a system called MovieTix.
MovieTix is, essentially, a hybrid debit/credit card for purchasing movie tickets. A MovieTix card can be pre-loaded with a particular number of tickets, can be used as a credit card, or can be used like a combination of the two. The prototype MovieTix system is being developed for university students and supports three plans.
- Basic Plan
- A plan with both pre-paid tickets and the ability to purchase tickets on credit (i.e., tickets for which the card holder will be billed in the future). Any pre-paid tickets that are not used during the semester are lost (and the purchase price is not refunded).
- Tiered Plan
- A plan with both pre-paid tickets and the ability to purchase tickets on credit. It differs from a Basic Plan in that the first group of "purchases" is at a different price from the subsequent "purchases".
- Limited Plan
- A plan with both pre-paid tickets and the ability to purchase a limited dollar amount of tickets on credit.
The Components to be Written¶
You must write the BasicPlan
, TieredPlan
, and LimitedPlan
components that encapsulate plan information that is specific to
MovieTix. You must also write some general purpose components for
performing input/output operations on simple, headerless
comma-separated-value (CSV) files. The relationship between these
components is illustrated in the following UML class diagram (which
can be enlarged by zooming-in on this page or by right-clicking and
opening it in another window/tab).
These diagrams use the convention that methods that are inherited by a derived class are not listed, but methods in a derived class that override a method in a base class are explicitly listed. Similarly, methods that are required in a concrete class that implements an interface or extends an abstract base class are not explicitly listed.
Warning
Pay careful attention to the use of italics in this diagram.
Detailed specifications are provided below. You may add private attributes and private methods but you must not override any existing methods that are not explicitly overridden in the UML diagram and you must not shadow any attributes.
CSVRepresentable
¶
CSVRepresentable
describes the capabilities of objects that can be
represented in CSV format. The toCSV()
method says that such an
object must be able to create a String
CSV representation of itself
that includes all of its attributes. The fromCSV()
method, on the
other hand, says that such an object must be able to initialize all of
its attributes from a String
CSV representation. The String
CSV
representation used must be simple (i.e., just consist of records with
commad-delimited fields) and must not contain a header. The attributes
may be listed in any order, but the toCSV()
and fromCSV()
methods
must be consistent.
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
. Think About. This method must reset the Scanner before it is returned or construct a
new StringTokenizer from the original String and return it.
FileIdentifier
¶
FileIdentifier
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 of "Student"
and an
extension of "java"
. Think About. The constructor must first convert the type to upper-case.
The toString()
method for the example above must return the String
"Student.java"
(without the quotes).
FileProcessor
¶
FileProcessor
is an 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 is guaranteed to contain the
number of subsequent lines in the file. The first line of any file it writes
must contain the number of subsequent lines that will be in the file. Think About. The digits in the number must be delimited by commas.
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.
-
The
readLines()
method must first read the number of lines in the file and must then read the subsequent lines, decrypt them, and return aString[]
array that contains the decrypted lines. -
The
writeLines()
method must first write the length of the parameter namedlines
(on a line of its own), and must encrypt the elements and write them to the file (one element per line).
CSVFileProcessor
¶
In addition to the specifications contained in
the UML class diagram, a CSVFileProcessor
must conform to the following
specifications.
-
The
decrypt()
andencrypt()
methods must return the parameters they are passed unchanged. In other words, CSV files must not be encrypted. -
The
write()
method must create a CSV representation of the objects it is passed and write all of them, one per line. -
The
read()
method must read all of the lines in the file 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 lines in the file. -
The
write()
andread()
methods must not duplicate code in any other components.
BasicPlan
¶
BasicPlan
is an encapsulation of pre-paid movie plans
that have a fixed up-front cost and number of tickets, and the ability to
purchase "extra" tickets on credit at a pre-determiend cost.
In addition to the other specifications included in the UML diagram,
the BasicPlan
component must comply with the following
specifications.
-
The
prepaid
andpunches
attributes, respectively, must contain the number of prepaid tickets in the plan and the number of pre-paid tickets used to-date (which must be 0 initially). In other words,punches
keeps track of the used pre-paid tickets. -
The
numberPurchased
andspent
attributes, respectively, must contain the number of "extra" movies that have been purchased to-date (which must be 0 initially) and the amount of money spent to-date on "extra" movies (which must be 0.00 initially). -
The default constructor must initialize the attributes to the default values in the following table.
Name | Cost | Pre-Paid Tickets | Cost of Extras |
---|---|---|---|
Basic Plan | $50.00 | 5 | $15.00 |
Note that the name
must contain a space between the words.
-
The explicit value constructor must initialize the attributes in the obvious way.
-
The
costOfPurchasedMovie()
method must return the cost of a purchased (i.e., not pre-paid) movie. Note: The use of the term "purchased" in the name of this method is not intended to convey the past tense (i.e., it does not imply that the movie was purchased in the past). This method could just as easily be namedcostOfPurchasingAMovie()
. -
The
costToDate()
method must return the cost-to-date of the plan (i.e., the sum of the plan cost and the amount spent to-date on purchased/"extra" movies). Think About. The cost must be returned in pennies, not dollars. -
The
getCostOfNextMovie()
method must return aString
representation of the cost of the next movie. If the plan still has available pre-paid tickets then this method must return"Free"
. Otherwise, it must return the cost of a purchased movie (preceded by a dollar sign, in a field of width 6, with 2 digits to the right of the decimal point). -
The
getCostPerMovie()
method must throw anIllegalStateException
when the number of movies seen to-date is 0 (since the cost per movie is undefined when no movies have been seen). Otherwise, it must return the cost-to-date divided by the number of movies seen to-date.Think About. This method must return the cost per day, taking into account the number of days per month. -
The
numberPurchased()
method must return the number of movies that have been purchased to-date (which does not include the number of pre-paid movies that have been seen).Think About. This method must return the result as a String. -
The
numberSeen()
method must return the number of movies that have been seen to-date (whether pre-paid or purchased).Think About. This method must return the result as a String. -
The
remainingPrepaid()
method must return the number of unused pre-paid tickets.Think About. This method must return the result as a String. -
The
spent()
method must return the amount of money that has been spent on purchased movies (which does not include the plan cost). -
The
toString()
method must return aString
representation of the plan. TheString
must be tab-delimited and include three items (in order): the name, the cost per movie (preceded by a dollar sign in a field of width 6, with 2 digits to the right of the decimal point), and the cost of the next movie (formatted as described above). If the cost per movie is undefined, then this method must return: the name, followed by two tabs, followed by theString
literal"Unused"
, followed by a tab, followed by the cost of the next movie (formatted as described above). -
The
use()
method is called when a user wants to "see" a movie. It must adjust the number of punches, the amount spent, and/or the number purchased (as appropriate). Since it is always possible to "see" a movie under this plan, it must always returntrue
.
Example
A BasicPlan
with the following attributes:
Name | Cost | Pre-Paid Tickets | Cost of Extras |
---|---|---|---|
A | $20.00 | 2 | $12.50 |
would have the following costs per movie.
Movies | Cost To Date | Cost Per Movie |
---|---|---|
1 | 20.00 | 20.00 |
2 | 20.00 | 10.00 |
3 | 32.50 | 10.83 |
4 | 45.00 | 11.25 |
5 | 57.50 | 11.50 |
LimitedPlan
¶
In a LimitedPlan
there is a limit on the amount of money
that can be spent on credit.
In addition to the other specifications included in the UML diagram,
the LimitedPlan
class must comply with the following specifications.
-
creditLimit
must contain the limit on the amount of money that can be spent on credit. -
The default constructor must initialize the attributes to the default values in the following table.
Name | Cost | Pre-Paid Tickets | Cost of Extra Tickets | Credit Limit |
---|---|---|---|---|
Limited Plan | $50.00 | 5 | $15.00 | $100.00 |
Note that the name
must contain a space between the words.
-
The
getCostOfNextMovie()
method must return aString
representation of the cost of the next movie. If the plan is unusable (i.e., has no available pre-paid tickets and insufficient credit to purchase a ticket) then it must return"N/A"
. Otherwise, it must exhibit the same behavior as aBasicPlan
. -
The
use()
method is called when a user wants to "see" a movie. It must returnfalse
if the plan is unusable (as described above). Otherwise, it must exhibit the same behavior as aBasicPlan
.
Example
A LimitedPlan
with the attributes:
Name | Cost | Pre-Paid Tickets | Cost of Extra Tickets | Credit Limit |
---|---|---|---|---|
B | $25.00 | 2 | $15.00 | $30.00 |
would have the following costs per movie.
Movies | Cost To Date | Cost Per Movie |
---|---|---|
1 | 25.00 | 25.00 |
2 | 25.00 | 12.50 |
3 | 40.00 | 13.33 |
4 | 55.00 | 13.75 |
5 | N/A | N/A |
The customer can't watch a fifth movie with this plan because they have used the two pre-paid tickets and used their $30.00 credit limit on the other two movies.
The TieredPlan
Class¶
In a TieredPlan
there is a special initial tier of purchases (at a
different price from ordinary purchases).
In addition to the other specifications included in the UML diagram,
the TieredPlan
class must comply with the following specifications.
-
tierLimit
is the number of tickets in the initial tier of tickets.tierCost
contains the cost (per movie) of the initial tier of tickets (which is usually lower than the cost of a "normal" extra ticket). -
The default constructor must initialize the attributes to the default values in the following table.
Name | Cost | Pre-Paid Tickets | Cost of Extras | Number of Tickets in Initial Tier | Cost of Tickets in Initial Tier |
---|---|---|---|---|---|
Tiered Plan | $100.00 | 5 | $10.00 | 5 | $5.50 |
Note that the name
must contain a space between the words.
-
The explicit value constructor must initialize the attributes in the obvious way.
-
The
costOfPurchasedMovie()
method must return the cost of a purchased (i.e., not pre-paid) movie. Note that the value returned by this method will depend on the number of movies that have been purchased (because of the two price tiers).
Example
A TieredPlan
with the following attributes:
Name | Cost | Pre-Paid Tickets | Cost of Extras | Number of Tickets in Initial Tier | Cost of Tickets in Initial Tier |
---|---|---|---|---|---|
C | $30.00 | 2 | $12.50 | 2 | $7.50 |
would have the following costs per movie.
Movies | Cost To Date | Cost Per Movie |
---|---|---|
1 | 30.00 | 30.00 |
2 | 30.00 | 15.00 |
3 | 37.50 | 12.50 |
4 | 45.00 | 11.25 |
5 | 57.50 | 11.50 |
Existing Components¶
A PayTex employee has written an applications that will make use of your code when you complete it, and provided you with the source code.
It can be used to print a table that contains the best plan for different amounts of usage.
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.
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. It is probably best to not include a path when reading/writing (i.e., to write to and read from the current working directory).
Submission¶
You must submit (using Gradescope) a .zip
file named pa2.zip
that contains:
-
CSVRepresentable.java
,FileIdentifier.java
,FileProcessor.java
,CSVFileProcessor.java
,BasicPlan.java
,TieredPlan.java
, andLimitedPlan.java
; and -
Your JUnit tests.
The pa2
directory/folder must be at the top of the .zip
file, and
all of your packages must be in the appropriate directory/folder under
the pa2
directory/folder. Do not submit any data files.
Your grade will be reduced by 5 points for each submission after the 10th submission. Note that your submission will not be graded (though will be counted towards the limit of 10 submissions) 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:
Criterion | Points | Details |
---|---|---|
Conformance to the Style Guide | 0 | Success Required |
Passing Your Tests | 10 | All or Nothing; Success Required |
Coverage of Your Tests | 20 | Partial Credit Possible |
Correctness | 70 | Partial 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.
Recommended Process¶
You are strongly encouraged to use test-driven development (i.e., to develop the tests for a class/method before developing the class/method itself). Whether or not you use TDD, you should work on the classes/enums one at a time, in the following order:
CSVRepresentable
FileIndentifier
and associated JUnit testsFileProcessor
CSVFileProcessor
and associated JUnit testsBasicPlan
and associated JUnit testsLimitedPlan
and associated JUnit testsTieredPlan
and associated JUnit tests
After that, you should perform system testing using the Planalyzer
classes.
Relevant Programming Patterns¶
An understanding of the following programming patterns will help you complete this assignment:
Questions to Think About¶
The following question will help you understand the material from this assignment (and from assignments earlier in the semester). You do not have to submit your answers, but you should make sure that you can answer them.
-
The
findBestPlan()
method in thePlanUtilities
class can be passed a variable number of parameters. From the invoker's standpoint, what is the advantage of using a formal parameter ofBasicPlan...
rather than a formal parameter ofBasicPlan[]
? -
Each plan in the
Planalyzer
application is declared to be aBasicPlan
object but some are instantiated as other objects. Why does the code compile? -
Given the signature of the
findBestPlan()
method in thePlanalyzer
class, would the following fragment compile?
LimitedPlan limited;
TieredPlan tiered;
BasicPlan best;
limited = new LimitedPlan();
tiered = new TieredPlan();
best = Planalyzer.findBestPlan(tiered, limited);
- Suppose the following methods were in a
PlanUtilities
class:
public static void printPlanInfo(BasicPlan plan) {
System.out.println("Basic Plan");
System.out.println(plan.toString());
}
public static void printPlanInfo(LimitedPlan plan) {
System.out.println("Tiered Plan");
System.out.println(plan.toString());
}
public static void printPlanInfo(TieredPlan plan) {
System.out.println("Tiered Plan");
System.out.println(plan.toString());
}
What would be printed by the following fragment?
BasicPlan a, b, c;
LimitedPlan d;
TieredPlan e;
a = new BasicPlan();
a.use();
b = new LimitedPlan("Limited Plan", 1, 15.00);
b.use();
c = new TieredPlan();
c.use();
d = new LimitedPlan("Limited Plan", 1, 15.00);
d.use();
e = new TieredPlan();
e.use();
PlanUtilities.printPlanInfo(a);
PlanUtilities.printPlanInfo(b);
PlanUtilities.printPlanInfo(c);
PlanUtilities.printPlanInfo(d);
PlanUtilities.printPlanInfo(e);
-
Why is the
FileProcessor
class declared to beabstract
? -
Will the following fragment compile? Why or why not?
FileProcessor fp;
fp = new CSVFileProcessor("temp.csv");
-
Why isn't the
BasicPlan
class declared to beabstract
? -
Why aren't the usage-independent attributes in the
BasicPlan
,LimitedPlan
, andTieredPlan
classes declared to befinal
? -
Why are the attributes in the
BasicPlan
class declared to beprivate
rather thanprotected
? -
Why does the
BasicPlan
class includepublic
accessors for its usage-based attributes? -
How do your answers to the previous two questions impact your testing of the
BasicPlan
class? For example, how can you test/cover thenumberPurchased()
method in theBasicPlan
class? How would you test those methods if they wereprotected
?