csc207-hw03.
Email the address of that repository to csc207-01-grader@cs.grinnell.edu.
Please use a subject of “CSC207 2019S Assignment 3 (Your Name)”.
a. Create a new Eclipse project for this assignment. You can name the project whatever you like, provided it’s not in bad taste.
b. Create a new package named csc207.*username*.layout. You will
use this package on parts A and B of the assignment.
c. Copy the various classes from the recent lab on subtype polymorphism into that package.
d. Create a new package named com.mcfarevee.groceries.
You will use this package in part C of the assignment.
e. Create a new package named com.mcfarevee.shopping.
You will use this package in part D of the assignment.
f. Create a new package named com.mcfarevee.tests.
You will use this package for your experiments and tests.
In the reading and
lab on polymorphism, you explored
a number of related classes that permitted you to build interesting
two-dimensional layouts of characters. You were able to combine
and recombine text objects because of Java’s support of polymorphism.
That is, if we treat every combination of text as an object that
implements the TextBlock interface, then we can combine these
objects arbitrarily.
The textual layout example also had a hidden agenda: To help you think about problems in a more object-oriented fashion. In particular, most beginning programmers, when asked to do some form of textual composition, focus on the methods that would compose pieces of text. In this example, we built objects that represent those composed pieces of text. The advantages of using objects included the ability to reuse a composed piece of text and a better ability to take advantage of polymorphism.
However, we left some aspects of the example unexplored in both the reading and the lab. Let’s begin by building some new composition classes.
a. Write a class, Grid, which implements TextBlock and represents
an w-by-h grid of a single character. You should provide one
constructor, Grid(int width, int height, char ch). The width()
method of the object you construct should return width. The
height() method of the object you construct should return height.
And the row(i) method should return a string of width copies
of ch for all non-negative i less than height.
For example, given the following line,
TBUtils.print(pen, new BoxedBlock(new Grid(7, 3, '*')));
the output will be
+-------+
|*******|
|*******|
|*******|
+-------+
You may choose to throw an exception in the constructor if the width or height are not sensible values. (If you don’t yet know how to throw exceptions, don’t worry about it.)
b. Write a class, TruncatedBlock, which implements
TextBlock and represents the truncation of a text block
to a desired width. As you might expect, the constructor for this
class should take a TextBlock and a width as parameters.
public class TruncatedBlock implements TextBlock {
/**
* Create a new truncated block of the specified width.
*/
public TruncatedBlock(TextBlock tb, int width) {
...
} // TruncatedBlock(TextBlock, int)
/**
* Get the ith row of the block.
*/
public String row(int i) {
...
} // row(int)
/**
* Determine how many rows are in the block.
*/
public int height() {
...
} // height()
/**
* Determine how many columns are in the block.
*/
public int width() {
...
} // width()
} // class TruncatedBlock
For example, given the following program fragment,
TextBlock block = new VComposition(new TextLine("Hello"), new TextLine("Goodbye"));
TextBlock block2 = new TruncatedBlock(block, 3);
TBUtils.print(pen, block);
TBUtils.print(pen, block2);
we see output something like the following
Hello
Goodbye
Hel
Goo
c. Write a class, CenteredBlock, which implements
TextBlock and represents the result of centering a text
block within a certain width, which is provided as a parameter to
the constructor. That width should be no less than the
width of the underlying text block, and your constructor should throw
an exception if given an inappropriate width.
Your class should not attempt to examine the contents of the block which is centered. If that text is left justified, you simply center the left-justified text within your block.
For example, given the following program fragment,
TextBlock block = new VComposition(new TextLine("Hello")), new TextLine("Goodbye"))
TextBlock block2 = new BoxedBlock(new CenteredBlock(block, 11));
TBUtils.print(pen, block2);
we see output something like the following
+-----------+
| Hello |
| Goodbye |
+-----------+
In contrast, given the following program fragment,
TextBlock top = new CenteredBlock(new TextLine("Hello"), 11);
TextBlock bottom = new CenteredBlock(new TextLine("Goodbye"), 11);
TextBlock block = new BoxedBlock(new VComposition(top,bottom));
TBUtils.print(pen, block);
we should see something like the following
+-----------+
| Hello |
| Goodbye |
+-----------+
d. Write a class, RightJustified, which implements TextBlock
and represents the result of right-justifying the underlying text
block in a block of a width specified in the constructor. You may
choose to throw an exception if the underlying block is too large.
e. Write a class, BlockPair, which implements TextBlock and
represents two copies of the same block, side by side. For example,
if the third row of block is "Hello", then the third row of
new BlockPair(block) should be "HelloHello". Similarly, if
the third row of block is "Hello " (e.g., because it’s in a
vertical composition), the third row of the corresponding BlockPair
should be "Hello Hello ".
As written, our various text blocks are immutable. What would it mean to make them mutable? Let’s consider those issues.
Add a setContents(String newContents) method to the
TextLine class. As the name suggests, this method should
set the contents of that block to the new value.
Some of the other classes may make assumptions about the immutability of text blocks. Identify where those assumptions are made and update the code so that it accommodates blocks that change.
Note that the only block that you will explicitly make mutable is
TextLine. Your job is to update everything else so that when a
TextLine changes, any block that contains that TextLine also
changes.
You need only submit one set of code for parts A and B.
Here’s a code fragment to help you think about changes.
TextLine tb1 = new TextLine("Hello");
TextLine tb2 = new TextLine("World");
TextBlock compound = new BoxedBlock(new CenteredBlock(new BoxedBlock(new CenteredBlock(new VComposition(tb1, tb2), 7)), 15));
pen.println("ORIGINAL");
TBUtils.print(pen, compound);
tb2.setContents("Someone");
pen.println("UPDATED");
TBUtils.print(pen, compound);
tb1.setContents("Nice to meet you,");
pen.println("RE-UPDATED");
TBUtils.print(pen, compound);
And here’s some corresponding output.
ORIGINAL
+---------------+
| +-------+ |
| | Hello | |
| | World | |
| +-------+ |
+---------------+
UPDATED
+---------------+
| +-------+ |
| |Hello | |
| |Someone| |
| +-------+ |
+---------------+
RE-UPDATED
*Something that indicates that there was an error, which may just be a missing line.*
For clarity, here’s the behavior we expect.
BoxedBlock should stretch
or shrink to accommodate that change.BlockPair should stretch
or shrink to accommodate that change.HComposition or a VComposition should change.CenteredBlock should not change. However, you may
have to change how you center the underlying block. If the width of
the underlying block is too large, you should either throw an exception,
truncate the lines of the underlying block, or create a string of
the correct width that indicates an error, such as "?????".RightJustified should not change. You
can use a similar approach to your CenteredBlock.TruncatedBlock should not change. Since these
blocks already truncate, they should continue to behave correctly,
even when the width of the underlying block changes.McFare-Vee corporation has decided to upgrade the software used in their grocery stores and has hired you to help implement their object model. (You may think it strange that they are hiring a relatively experienced programmer. However, the demand for programmers outstrips supply, and you are fortunate to have a general aura of competence.)
They’ve decided to start by having you model the items in the grocery store, focusing on how the consumer might think of purchasing those items. Here’s their initial assessment of how they want the data organized.
+-Item----------------+ +-BulkFood----------+ +-Weight------+
| getWeight(): Weight | | name: String | | unit: Unit |
| getPrice(): int | | unit: Unit | | amount: int |
| toString(): String | | pricePerUnit: int | +-------------+
+---------------------+ | supply: int |
^ ^ ^ ^ +-------------------+
| | | |
| | | +-----------------------------------+
| | +----------------------------------+ |
| +--------------------+ | |
| | | |
+-BulkItem------------+ +-Package--------+ | +-ManyPackages---+
| food: BulkFood | | name: String | | | type: Package |
| unit: Unit | | weight: Weight | | | count: int |
| amount: int | | price: int | | +----------------+
+---------------------+ +----------------+ |
|
+-NonFood--------+
| name: String |
| weight: Weight |
| price: int |
+----------------+
That is,
Item is an interface.BulkItem, Package, NonFood,
and ManyPackages all implement the Item interface. Hence,
each must include implementations of the getWeight(), getPrice(),
and toString() methods.BulkFood and Weight are additional
classes, used mostly to represent objects used by the other
classes.As you might guess, each of the implementations of Item differs
in its implementation of the toString, getWeight, getPrice,
and equals methods. Note that the getWeight method returns the
weight in whatever units the item uses. The getPrice method
should return the price in cents.
BulkItem
toString method should return a string that
gives the number of units, the units, and the type of bulk
food. For example, “5 pounds of bananas”.getWeight method should return the weight
(built from the unit and quantity).getPrice method should multiply the number
of units times the price per unit.equals method should only return true if the
compared object is a BulkItem object with equal
fields.Package
toString method should give the weight,
the word “package”, and the name. For example,
“5 oz. package of mac and cheeze”.getWeight and getPrice methods
should return the obvious values.equals method should only return true if the
compared object is a Package object with equal
fields.ManyPackages
toString method should give the quantity, a
times sign (x), and the description of the package. For example,
“4 x 5 oz. package of mac and cheeze”.getWeight and getPrice methods
should multiply the corresponding values in the underlying
package by the quantity.equals method should only return true if
the compared object occupies the same memory location.NonFood
toString method should give the name. For example, “can opener”.getWeight and getPrice methods should return the obvious values.equals method should only return true if the compared object is a NonFood object with equal fields.Create a new package called com.mcfarevee.groceries and create
classes for these objects in the package.
Your predecessor has implemented the Unit class to
give you a head start. You can find that class at the end of this
assignment.
Here’s a program fragment that shows some of these classes in action.
// The store has 20 pounds of bananas, priced at 50 cents per pound
BulkFood bananas = new BulkFood("bananas", Unit.POUNDS, 50, 20);
// The store has 200 grams of saffron, priced at 1000 cents per gram
BulkFood saffron = new BulkFood("saffron", Unit.GRAMS, 1000, 200);
// The customer adds three pounds of bananas to the cart
cart.addItem(new BulkItem(bananas, Unit.POUNDS, 3);
// The customer adds a bag of 1 gram of saffron to the cart
cart.addItem(new BulkItem(saffron, Unit.GRAMS, 1);
// The customer adds a can opener to the cart, priced $3.489.
cart.addItem(new NonFood("can opener", new Weight(Unit.OUNCES, 2), 349);
// The customer adds a box of oreos to the cart
cart.addItem(new Package("oreos", new Weight(Unit.OUNCES, , 12), 399);
// The customer adds five 6oz packages of macncheez to the cart, each
// priced at 77 cents.
cart.addItem(new ManyPackages(new Package("macncheez",
new Weight(Unit.OUNCES, 6), 77), 5));
Note that if we were modeling this fully, we would decrement the supply of bananas or saffron when we added each to the cart. You do not need to worry about doing so, although you should also feel free to include that action in your code.
McFare-Vee are optimistic about your ability to implement the design above
and have asked you to build a Cart class, too. They ask
that you put that class in package com.mcfarevee.shopping
and that you include the following methods.
addItem(Item). Add an item to the cart.numThings(). Get the number of things in the cart.
For counting items, you should count most items as one thing,
except for ManyPackages, for which you should use
the count. (You may want to add an accessor for that count.)numEntries(). Get the number of entries in the cart.
This is much like numThings(), except that you should
count a ManyPackages objects as one entry.printContents(PrintWriter). Print the contents of
the cart.getPrice(). Computes the total price of the order,
in cents.getWeight(). Since there are multiple kinds of weights
in use at McFare-Vee, they note that this method can return an array
of weights, but that you should combine similar weights. (E.g., you
should combine all of the weights in pounds together, but you should
not convert from ounces, grams, or kilograms to pounds.) You may
assume that we have only the four basic kinds of weight.remove(String name). Removes all of the products whose
name exactly matches name.merge(). Finds identical items and merges them into
a single item. For example, if you have two Package
items with the same name, weight, and price, you should combine
them into a single ManyPackages object. If you have a
ManyPackages object and a Package object of the same kind,
you should combine them.
Continuing, if you have two ManyPackages objects that contain
the same kind of package, you should combine them into a single
ManyPackages object. Similarly, if you have two BulkItem
objects with the same food and units, you should combine them
into one BulkItem.
This method does not need to do anything with NonFood objects.
You may use whatever data structure you consider most appropriate
to store the data in the cart - an array, a linked list you create,
a Vector, an ArrayList, a LinkedList,
or whatever else you decide is appropriate. Note that your choice of
data structure will have a large effect on the ease of implementing the
various methods, so choose thoughtfully.
Unit Class/**
* A Unit of measurement. The primary units available at
* Unit.GRAM, Unit.KILOGRAM, Unit.OUNCE, and Unit.POUND.
*/
public class Unit {
// +--------+------------------------------------------------------
// | Fields |
// +--------+
/**
* The name of the unit.
*/
String name;
/**
* The abbreviation of the unit.
*/
String abbrev;
/**
* The plural name of the unit.
*/
String plural;
// +--------------+------------------------------------------------
// | Constructors |
// +--------------+
/**
* Create a new unit with a given name.
*/
private Unit(String name, String abbrev, String plural) {
this.name = name;
this.abbrev = abbrev;
this.plural = plural;
} // Unit(String, String, String)
// +-----------+---------------------------------------------------
// | Accessors |
// +-----------+
/**
* Get the unit name.
*/
public String toString() {
return this.name;
} // toString()
/**
* Get the abbreviation of the unit name.
*/
public String abbrev() {
return this.abbrev;
} // abbrev()
/**
* Get the plural of the unit name. (One does not always form a plural
* by just adding "s", so we provide this additional method.)
*/
public String plural() {
return this.plural;
} // plural
// +-----------+---------------------------------------------------
// | Constants |
// +-----------+
/**
* Standard unit: Pounds
*/
public static final Unit POUND = new Unit("pound", "lb.", "pounds");
/**
* Standard unit: Ounces
*/
public static final Unit OUNCE = new Unit("ounce", "oz.", "ounces");
/**
* Standard unit: Kilograms
*/
public static final Unit KILOGRAM = new Unit("kilogram", "kg.", "kilograms");
/**
* Standard unit: Grams
*/
public static final Unit GRAM = new Unit("gram", "gm.", "grams");
} // class Unit
Much of this assignment is based on HW6 from Samuel A. Rebelsky’s Fall 2014 section of Grinnell College’s CSC 207, available at https://www.cs.grinnell.edu/~rebelsky/Courses/CSC207/2014F/assignments/assignment.06.html. That assignment is, itself, based on two other assignments.
The text block problems are based closely on HW 3 from Samuel A. Rebelsky’s spring 2005 section of Grinnell College’s CSC 152, available at http://www.cs.grinnell.edu/~rebelsky/Courses/CS152/2005F/Homework/hw.03.html.
The grocery store problems were inspired by Henry Walker’s first supplemental problem for section 2 of CSC 207 in Fall 2014, available at http://www.cs.grinnell.edu/~walker/courses/207.fa14/suppl-prob.shtml.
Although the history of this assignment is long, the problems remain relevant. Manipulating text blocks is much like manipulating GUI elements and the use of subtype polymorphism for such objects; the grocery problems exercise skills in object design.