Skip to content

PA2: MovieTix

MovieTix Logo

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).

UML Class Diagram

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.

  1. The readLines() method must first read the number of lines in the file and 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), 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.

  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, one per line.

  3. 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.

  4. The write() and read() 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.

  1. The prepaid and punches 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.

  2. The numberPurchased and spent 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).

  3. 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.

  1. The explicit value constructor must initialize the attributes in the obvious way.

  2. 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 named costOfPurchasingAMovie().

  3. 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.

  4. The getCostOfNextMovie() method must return a String 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).

  5. The getCostPerMovie() method must throw an IllegalStateException 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.

  6. 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.

  7. 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.

  8. The remainingPrepaid() method must return the number of unused pre-paid tickets.Think About. This method must return the result as a String.

  9. The spent() method must return the amount of money that has been spent on purchased movies (which does not include the plan cost).

  10. The toString() method must return a String representation of the plan. The String 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 the String literal "Unused", followed by a tab, followed by the cost of the next movie (formatted as described above).

  11. 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 return true.

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.

  1. creditLimit must contain the limit on the amount of money that can be spent on credit.

  2. 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.

  1. The getCostOfNextMovie() method must return a String 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 a BasicPlan.

  2. The use() method is called when a user wants to "see" a movie. It must return false if the plan is unusable (as described above). Otherwise, it must exhibit the same behavior as a BasicPlan.

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.

  1. 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).

  2. 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.

  1. The explicit value constructor must initialize the attributes in the obvious way.

  2. 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.

Planalyzer.java

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:

  1. CSVRepresentable.java, FileIdentifier.java, FileProcessor.java, CSVFileProcessor.java, BasicPlan.java, TieredPlan.java, and LimitedPlan.java; and

  2. Your JUnit tests.

The pa2directory/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.

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:

  1. CSVRepresentable
  2. FileIndentifier and associated JUnit tests
  3. FileProcessor
  4. CSVFileProcessor and associated JUnit tests
  5. BasicPlan and associated JUnit tests
  6. LimitedPlan and associated JUnit tests
  7. TieredPlan 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.

  1. The findBestPlan() method in the PlanUtilities class can be passed a variable number of parameters. From the invoker's standpoint, what is the advantage of using a formal parameter of BasicPlan... rather than a formal parameter of BasicPlan[]?

  2. Each plan in the Planalyzer application is declared to be a BasicPlan object but some are instantiated as other objects. Why does the code compile?

  3. Given the signature of the findBestPlan() method in the Planalyzerclass, would the following fragment compile?

LimitedPlan limited;
TieredPlan  tiered;
BasicPlan   best;

limited = new LimitedPlan();
tiered  = new TieredPlan();

best = Planalyzer.findBestPlan(tiered, limited);
  1. 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);
  1. Why is the FileProcessor class declared to be abstract?

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

FileProcessor fp;
fp = new CSVFileProcessor("temp.csv");
  1. Why isn't the BasicPlan class declared to be abstract?

  2. Why aren't the usage-independent attributes in the BasicPlan, LimitedPlan, and TieredPlan classes declared to be final?

  3. Why are the attributes in the BasicPlan class declared to be private rather than protected?

  4. Why does the BasicPlan class include public accessors for its usage-based attributes?

  5. How do your answers to the previous two questions impact your testing of the BasicPlan class? For example, how can you test/cover the numberPurchased() method in the BasicPlan class? How would you test those methods if they were protected?