// enumerated/VendingMachine10.java
// {Arg: VendingMachine10aInput.txt}
// TIJ4 Chapter Enumerated, Exercise 10, page 1047
// Modify class VendingMachine (only) using EnumMap so that one
// program can have multiple instances of VendingMachine.
/* My solution to one of the exercises in
* Thinking in Java 4th Edition (by Bruce Eckel).
* It compiles and runs correctly using JDK 1.6.0
* @author Greg Gordon
* @author www.greggordon.org
* April, 2009
*/
// Files required for input, in same package:
// VendingMachine10aInput.txt:
// QUARTER;QUARTER;QUARTER;CHIPS;
// DOLLAR; DOLLAR; TOOTHPASTE;
// QUARTER; DIME; SODA;
// QUARTER; DIME; NICKEL; SODA;
// ABORT_TRANSACTION;
// STOP;
// VendingMachine10bInput.txt:
// DOLLAR;CHIPS;
// QUARTER;DIME;TOOTHPASTE;
// STOP;
// VendingMachine10cInput.txt
// DOLLAR;DOLLAR;DOLLAR;CHIPS;
// QUARTER;QUATER;SODA;
// ABORT_TRANSACTION;
// STOP;
package enumerated;
import static enumerated.Input.*;
import java.util.*;
import net.mindview.util.*;
import static net.mindview.util.Print.*;
enum Category {
MONEY(NICKEL, DIME, QUARTER, DOLLAR),
ITEM_SELECTION(TOOTHPASTE, CHIPS, SODA, SOAP),
QUIT_TRANSACTION(ABORT_TRANSACTION),
SHUT_DOWN(STOP);
private Input[] values;
Category(Input... types) { values = types; }
private static EnumMap categories =
new EnumMap(Input.class);
static {
for(Category c : Category.class.getEnumConstants())
for(Input type : c.values)
categories.put(type, c);
}
public static Category categorize(Input input) {
return categories.get(input);
}
}
interface Command { // In order to use a Command Design Pattern
void next(Input input);
void next();
}
enum State {
RESTING,
ADDING_MONEY,
DISPENSING,
GIVING_CHANGE,
TERMINAL
}
class VendingMachine10 {
int id = ++count;
static int count = 0;
State state = State.RESTING;
int amount = 0; // for each transaction
int banked = 0; // retained after transactions
Input input = null;
Input selection = null;
boolean isTransient = false;
// Enums must be static, sot use classes instead:
class RestingDo implements Command {
public void next(Input in) {
isTransient = false;
input = in;
switch(Category.categorize(in)) {
case MONEY:
amount += in.amount();
state = State.ADDING_MONEY;
break;
case SHUT_DOWN:
state = State.TERMINAL;
default:
}
}
public void next() {
isTransient = false;
}
}
class AddingMoneyDo implements Command {
public void next(Input input) {
isTransient = false;
switch(Category.categorize(input)) {
case MONEY:
amount += input.amount();
break;
case ITEM_SELECTION:
selection = input;
if(amount < selection.amount()) {
print("Insufficient money for " + selection); }
else {
state = State.DISPENSING;
isTransient = true;
}
break;
case QUIT_TRANSACTION:
state = State.GIVING_CHANGE;
isTransient = true;
break;
case SHUT_DOWN:
state = State.TERMINAL;
banked = banked += amount;
default:
}
}
public void next() { isTransient = false; }
}
class DispensingDo implements Command {
public void next() {
isTransient = true;
print("Here is your " + selection);
state = State.GIVING_CHANGE;
}
public void next(Input input) {
isTransient = true;
print("Here is your " + selection);
state = State.GIVING_CHANGE;
}
}
class GivingChangeDo implements Command {
public void next(Input input) {
isTransient = true;
if(amount > selection.amount()) {
print("Your change: " + (amount - selection.amount()));
}
banked = banked += selection.amount();
amount = 0; // reset
state = State.RESTING;
}
public void next() {
isTransient = true;
if(amount < selection.amount())
print("Returning your: " + amount);
if(amount > selection.amount()) {
print("Your change: " + (amount - selection.amount()));
banked = banked += selection.amount();
}
if(amount == selection.amount())
banked = banked += selection.amount();
amount = 0;
state = State.RESTING;
}
}
class TerminalDo implements Command {
public void next(Input input) {
print("state TERMINAL");
isTransient = false;
}
public void next() {
print("state TERMINAL");
isTransient = false;
}
}
Map em =
Collections.synchronizedMap(new EnumMap(State.class));
VendingMachine10() { // Load up the EnumMap in the constructor
print("VendingMachine10()#" + id);
em.put(State.RESTING, new RestingDo());
em.put(State.ADDING_MONEY, new AddingMoneyDo());
em.put(State.DISPENSING, new DispensingDo());
em.put(State.GIVING_CHANGE, new GivingChangeDo());
em.put(State.TERMINAL, new TerminalDo());
}
void showAmount() { print("amount = " + amount); }
void showBanked() { print("banked = " + banked); }
public static void main(String[] args) {
Generator gen = new RandomInputGenerator();
if(args.length == 1)
gen = new FileInputGenerator10(args[0]);
VendingMachine10 vm10a = new VendingMachine10();
VendingMachine10 vm10b = new VendingMachine10();
VendingMachine10 vm10c = new VendingMachine10();
print();
print("Testing VendingMachine 10a:");
while(vm10a.state != State.TERMINAL) {
Input in = gen.next();
(vm10a.em.get(vm10a.state)).next(in);
while(vm10a.isTransient) {
(vm10a.em.get(vm10a.state)).next();
}
vm10a.showAmount();
}
vm10a.showBanked();
print();
print("Testing VendingMachine 10b:");
gen = new FileInputGenerator10("VendingMachine10bInput.txt");
while(vm10b.state != State.TERMINAL) {
Input in = gen.next();
(vm10b.em.get(vm10b.state)).next(in);
while(vm10b.isTransient) {
(vm10b.em.get(vm10b.state)).next();
}
vm10b.showAmount();
}
print();
print("Testing VendingMachine 10c:");
gen = new FileInputGenerator10("VendingMachine10cInput.txt");
while(vm10c.state != State.TERMINAL) {
Input in = gen.next();
(vm10c.em.get(vm10c.state)).next(in);
while(vm10c.isTransient) {
(vm10c.em.get(vm10c.state)).next();
}
vm10c.showAmount();
}
}
}
// Create inputs from a file of ';'-separated strings;
class FileInputGenerator10 implements Generator {
private Iterator input;
public FileInputGenerator10(String fileName) {
input = new TextFile(fileName, ";").iterator();
}
public Input next() {
if(input.hasNext()) {
return Enum.valueOf(Input.class, input.next().trim());
}
return null;
}
}