/**
* @author drakebennion
* @date February 5, 2014
*/
package homework04;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Map;
import java.util.Scanner;
import java.util.TreeMap;
/**This program contains the methods and instance variables used to keep track of a series of
* warehouses and their respective inventories in order to determine what fooditems go in which inventory,
* when these items expire, etc...
*
* This is accomplished by taking each line of a file, which is given by WarehouseTest.java, and
* reading the first word: in the specific file format we require, this tells us an action. The
* processLine method decides which "sub-method" should be called in order to complete that action.
* As these "sub-methods" do their respective jobs, we are building up a set of warehouses,
* each with their own Inventory of fooditems. This program will also be able to determine
* which fooditems appear in every warehouse inventory, which don't appear in any inventory,
* and which date was the busiest for each warehouse.
*
*/
public class WarehouseReport
{
//A calendar object to keep track of the effective date
private GregorianCalendar effective;
//And one to keep track of the first date
private GregorianCalendar begin;
//a map of UPC codes to corresponding fooditems
private Map<String, FoodItem> map;
//a map of upc codes to the name of the item
private Map<String,String> namemap;
//a map of a warehouse to its respective inventory
private Map<String, Inventory<FoodItem>> foodmap;
//a map to keep track of how many warehouses contain a certain item
private Map<FoodItem, Integer> fullstock;
private Map<FoodItem, Integer> nostock;
//map will keep track of the number of transactions for each warehouse (and be reset every day)
private Map<String, Integer> transaction;
//map will keep track of the busiest day information for each warehouse.
private Map<String, GregorianCalendar> busiest;
//keep track of the highest number of transactions for each warehouse
private Map<String, Integer> busiesttransactions;
/**
* Default constructor to set maps to be empty, and to set the date variables
* to be today (dates that represent the moment they are called.)
*/
public WarehouseReport()
{
effective=new GregorianCalendar();
begin=new GregorianCalendar();
map=new TreeMap<String,FoodItem>();
foodmap=new TreeMap<String, Inventory<FoodItem>>();
transaction=new TreeMap<String,Integer>();
busiesttransactions=new TreeMap<String,Integer>();
busiest=new TreeMap<String,GregorianCalendar>();
namemap=new TreeMap<String,String>();
fullstock=new TreeMap<FoodItem, Integer>();
nostock=new TreeMap<FoodItem, Integer>();
}
/**
* This method will read a line from a file, word by word, and decide
* what each word is telling the program to do, by delegating each action to
* a separate action method. However, some "filler words" are essentially
* ignored by the method, though there are few of these.
*
* @param line--the line of the file to be read
*/
public void processLine(String line)
{
//read through each word of the given line
Scanner read=new Scanner(line);
//while the line still has characters in it:
while(read.hasNext())
{
//read the next word of the line
String nextword=read.next();
if(nextword.equals("FoodItem"))
{
//A fooditem is being processed--call the processFood method on the line
processFood(line);
}
else if(nextword.equals("Warehouse"))
{
//A warehouse is being processed--call the processWarehouse method on the line
processWarehouse(line);
}
else if(nextword.equals("Start"))
{
//A beginning date is being processed--call the processStart method on the line
processStart(line);
}
else if(nextword.equals("Receive:"))
{
//A fooditem is being received--call the processReceive method on the line
processReceive(line);
}
//A fooditem is being requested--call the processRequest method on the line
else if(nextword.equals("Request:"))
{
processRequest(line);
}
else if(nextword.equals("Next"))
{
//The date should be incremented--call the processNext method on the line
processNext(line);
}
else if(nextword.equals("End"))
{
//The file has reached its end--after this, the program should terminate.
//Before it terminates, we need to see if the final date was the busiest
//date for any of our warehouses.
//Create a date to represent this final date
int tempday, tempyear, tempmonth;
tempday=effective.get(Calendar.DAY_OF_MONTH);
tempmonth=effective.get(Calendar.MONTH);
tempyear=effective.get(Calendar.YEAR);
GregorianCalendar temp=new GregorianCalendar(tempyear, tempmonth,tempday);
//loop through all of the warehouses
for(String s:transaction.keySet())
{
//If there were more total transactions here than any other day
//for any warehouse, then that warehouse should have its busiest
//day information updated.
if(transaction.get(s)>=(busiesttransactions.get(s)))
{
busiesttransactions.put(s,transaction.get(s));
busiest.put(s, temp);
}
}
//Now terminate
break;
}
else //otherwise, the word isn't indicating a command, and the method should go past it
continue;
}
}
/**Reads through a line that starts with the word "Food", indicating a fooditem
* is being processed.
*
* adds the upc code and the fooditem to our map<String, FoodItem>, and
* adds the upc code with the name of the fooditem to the map<String, String>.
*
* @param line--the string to be read
* @return none
*/
public void processFood(String line)
{
//variables to store a fooditems information
FoodItem food=null;
String upc="";
String name="";
int shelf=0;
//create a scanner to read through the line word by word
Scanner read=new Scanner(line);
//while there is still another word
while(read.hasNext())
{
//The word following certain "buzzwords" (in this case,
// Code, life or Name) will indicate different information
//about the fooditem, so we store these words appropriately.
String word=read.next();
if(word.equals("Code:"))
upc=read.next();
else if(word.equals("life:"))
shelf=Integer.valueOf(read.next());
else if(word.equals("Name:"))
{
while(read.hasNext())
name+=read.next()+" ";
}
else
continue;
}
//create the new food item with the given information
food=new FoodItem(name,upc,shelf);
//add the fooditem to the upc->fooditem map
map.put(upc, food);
namemap.put(upc, name);
}
/**Reads through a line to process Warehouse information.
* adds the warehouse name and its respective inventory to our
* <String, Inventory<FoodItem> map.
* Also sets the warehouse's transactions for today to 0 and sets today to be its
* busiest day (each in its respective Map)
*
* @param line the line to be read
* @return none
*/
public void processWarehouse(String line)
{
//a variable to store the warehouse name
String warehouse="";
Scanner read=new Scanner(line);
while(read.hasNext())
{
//similar process to the processFoodItem method above
String word=read.next();
if(word.equals("-"))
while(read.hasNext())
warehouse+=read.next()+" ";
}
Inventory<FoodItem> winventory=new Inventory<FoodItem>();
foodmap.put(warehouse, winventory);
transaction.put(warehouse, 0);
busiesttransactions.put(warehouse, 0);
}
/**Reads through a line to get the information about a current date.
* Sets the instance variable "effective" to the beginning date (this will change)
* and sets "beginning" to this date as well (this will remain the same)
*
* @param line
* @return none
*/
public void processStart(String line)
{
//A likewise similar process to the above two process methods
Scanner read = new Scanner(line);
String tempstring="";
while(read.hasNext())
{
if(read.next().equals("date:"))
{
//The date is read into a string
tempstring=read.next();
//The string is broken up into pieces, each piece corresponding to
//the characters between the / character
String[] holder=tempstring.split("/");
//temporary variables to hold this information
int tempmonth=0;
int tempday=0;
int tempyear=0;
//reads the first two characters as an integer
//needs to be decremented in order to be consistent with the GregorianCalendar--
//0=January, 1=February...11=December.
tempmonth=Integer.parseInt(holder[0])-1;
//stores next two characters as day integer
tempday=Integer.parseInt(holder[1]);
//stores the next 4 characters as the year integer
tempyear=Integer.parseInt(holder[2]);
//GregorianCalendar objects constructed with these numbers
begin=new GregorianCalendar(tempyear,tempmonth,tempday);
effective=new GregorianCalendar(tempyear,tempmonth,tempday);
}
}
//Set each warehouse to have its busiest day on the beginning date
for(String s:foodmap.keySet())
busiest.put(s, begin);
}
/**Reads through a line to process information about an item being received
* in an inventory. Updates the inventory of the corresponding warehouse.
* Also updates the transaction information for the respective warehouse.
*
* @param line--string to read
* @return --none
*/
public void processReceive(String line)
{
//variables to hold the receiving information
String receivingupc="";
int quantity=0;
String warehouse="";
Scanner read=new Scanner(line);
//the next word will be Receive, so ignore it
read.next();
//The next few inputs will be important, so store them
receivingupc=read.next();
quantity=Integer.valueOf(read.next());
while(read.hasNext())
warehouse+=read.next()+" ";
//retrieve the fooditem stored at the received upc code
FoodItem food=map.get(receivingupc);
//calculate the fooditem's expiration date (uses a method inside FoodItem.java
GregorianCalendar expires=food.getExpirationDate(effective);
//add the fooditem with the calculated expiration date and the given quantity
//to the proper warehouse
foodmap.get(warehouse).addItem(food,expires,quantity);
//update transaction information for this warehouse
int gettransaction=transaction.get(warehouse);
gettransaction+=quantity;
transaction.put(warehouse,gettransaction);
}
/**Reads through a line to process information about an item being Requested
* in an inventory (meaning the item is to be removed). Updates the inventory
* of the corresponding warehouse.
* Also updates the transaction information for the respective warehouse.
*
* @param line--string to read
* @return --none
*/
public void processRequest(String line)
{
//variables to store request info
String requestupc="";
int quantity=0;
String warehouse="";
Scanner read=new Scanner(line);
//store the info
read.next();
requestupc=read.next();
quantity=Integer.valueOf(read.next());
while(read.hasNext())
warehouse+=read.next()+" ";
//update transaction information
int gettransaction=transaction.get(warehouse);
gettransaction+=quantity;
transaction.put(warehouse,gettransaction);
//Create a temporary GregorianCalendar object to start at the beginning
//a new object must be created, because saying temp=begin would make
//temp and begin reference the same object--then whenever one changes, the
//other must also change...we don't want that.
int tempday, tempyear, tempmonth;
tempday=begin.get(Calendar.DAY_OF_MONTH);
tempmonth=begin.get(Calendar.MONTH);
tempyear=begin.get(Calendar.YEAR);
GregorianCalendar temp=new GregorianCalendar(tempyear, tempmonth, tempday);
//Get the requested food item
FoodItem food=map.get(requestupc);
//removing was a bit of a tricky situation, but here's what I think works:
//starting from the first date, loop through the dates until we get to the last
//possible expiration date of the food item: the items shelf life+the current date.
//While we're still working before that date, and while our quantity to be removed
//is >0, continue looping and removing, as described below:
while((temp.compareTo(food.getExpirationDate(effective))<=0)&&quantity>0)
{
//If the inventory has enough of the requested fooditem, then
//remove it, and make our removing quantity zero--then the loop
//is exited.
if((foodmap.get(warehouse)).getQuantity(food, temp)>=quantity)
{
(foodmap.get(warehouse)).removeItem(food,temp,quantity);
quantity=0;
}
//Otherwise, figure out how many we can remove, remove them, and
//change the quantity we want to remove accordingly.
else
{
int tempremovequantity=(foodmap.get(warehouse)).getQuantity(food, temp);
(foodmap.get(warehouse)).removeItem(food,temp,tempremovequantity);
quantity-=tempremovequantity;
}
//Then increment the date by one day. Then this loop will only exit once
//either 1. The request is entirely satisfied
//or 2. There are not enough fooditems currently in the inventory to
// satisfy the request. Then the request is simply dropped.
temp.add(Calendar.DAY_OF_MONTH,1);
} //hope that all makes sense!
}
/**processes the "next" command--increments the date by one and
* removes expired items from their respective inventories
* Also resets each warehouse's transaction map info, and updates
* the busiest date info for each warehouse.
*
* @param line to be read
* @return none
*/
private void processNext(String line)
{
//need to create a GregorianCalendar to represent the effective date so we
//can update the busiest day info--using effective means the date inside the
//busiest day map will change whenever effective date changes, which we
//don't want.
int tempday, tempyear, tempmonth;
tempday=effective.get(Calendar.DAY_OF_MONTH);
tempmonth=effective.get(Calendar.MONTH); //System.out.println(tempmonth);
tempyear=effective.get(Calendar.YEAR);
GregorianCalendar temp=new GregorianCalendar(tempyear, tempmonth,tempday);
//for every warehouse
for(String s:transaction.keySet())
{
//If the current days transaction were more than the currently busiest days,
//update busiest day to the current day. Also update the busiest day number of transactions.
if(transaction.get(s)>=(busiesttransactions.get(s)))
{
busiesttransactions.put(s,transaction.get(s));
busiest.put(s, temp);
}
//reset the transaction map for each warehouse
transaction.put(s, 0);
}
//increment the date
effective.add(Calendar.DAY_OF_MONTH, 1);
//remove any now-expired fooditems
for(Inventory<FoodItem> food:foodmap.values())
food.removeExpired(effective);
}
/*Prints to console what fooditems make an appearance in every warehouse.
*
*
* @param--none
* @return--none, but prints to console
*/
public void printFullStock()
{
//loop through every fooditem in our map to decide how many warehouses contain it:
for(FoodItem f:map.values())
{
//let the number of warehouses that contain that item be zero
int warehousescontaining=0;
//for each warehouse's inventory, if the fooditem is inside that inventory,
//increment the number of warehouses that do hold the item.
for(Inventory<FoodItem> food:foodmap.values())
{
if((food.getItems().contains(f)))
warehousescontaining++;
}
//This map keeps track of how many inventories contain each fooditem
fullstock.put(f,warehousescontaining);
}
//This is the number of warehouses we have in our map
int warehousenumber=foodmap.size();
//Then for every fooditem inside the fullstock map...
for(FoodItem f:fullstock.keySet())
{
//...if the number of warehouses containing the fooditem is equal to the
//number of warehouses we're keeping track of, then it must have a positive
//quantity in every warehouse. Then output it's information to the console.
if(fullstock.get(f).equals(warehousenumber))
System.out.println(f.getUpcCode()+" "+namemap.get(f.getUpcCode()));
}
}
/*Prints to console what fooditems aren't in any warehouse
*
*@param none
*@return none, prints to console
*/
public void printOutofStock()
{
//works very similarly to the Fullstock method above: instead, keep track of how many
//warehouses do not contain the food item in question.
//again, this is how many warehouses we have
int warehousenumber=foodmap.size();
//loop through every fooditem we have information for
for(FoodItem f:map.values())
{
//Assume that every warehouse contains the item
int warehousescontaining=warehousenumber;
for(Inventory<FoodItem> food:foodmap.values())
{
//If the warehouse actually doesn't contain the item,
//decrement the count of how many warehouses
//do contain it.
if(!(food.getItems().contains(f)))
warehousescontaining--;
}
//put this information in our map
nostock.put(f,warehousescontaining);
}
//for every fooditem...
for(FoodItem f:nostock.keySet())
{
//If no warehouse holds the fooditem, the fooditems map value will be zero
//then print its info to the console
if(nostock.get(f).equals(0))
System.out.println(f.getUpcCode()+" "+namemap.get(f.getUpcCode()));
}
}
/**For each warehouse we have, prints the warehouse name, the busiest day
* and the highest number of transactions that occurred for the warehouse.
*
* @param none
* @return none, prints to the console
*/
public void printBusyDays()
{
//simple enough, print out the information inside our busiest day map
for(String s:busiest.keySet())
{
int busyday=busiest.get(s).get(Calendar.DAY_OF_MONTH);
int busymonth=busiest.get(s).get(Calendar.MONTH)+1;
int busyyear=busiest.get(s).get(Calendar.YEAR);
//The month needs to be incremented to account for the GregorianCalendar month discrepancy
// System.out.println(s+((busiest.get(s)).get(Calendar.MONTH)+1)+"/"+(busiest.get(s)).get(Calendar.DAY_OF_MONTH)
// +"/"+(busiest.get(s)).get(Calendar.YEAR)+" "+busiesttransactions.get(s));
System.out.print(s+(busymonth<10? "0":"")+busymonth+"/");
System.out.print((busyday<10? "0":"")+busyday+"/");
System.out.println(busyyear+" "+busiesttransactions.get(s));
//need to print out date differently (updated 3/2/14)
/*SimpleDateFormat ft = new SimpleDateFormat ("MM-dd-yyyy");
Date t;
try {
t = ft.parse(busiest.get(s).toString());
System.out.println(t);
} catch (ParseException e) {
System.out.println("Unparseable using " + ft);
}
*/
}
}
}