import java.io.*; import java.text.*; import java.util.*; /** * GasMiles v2 * A convenient utility for calculating gas mileage of cars without trip * odometers. * Copyright (c) 2007 William R. Fraser * All rights reserved. Personal, noncommercial use is permitted. * * The program uses a data file containing lines of the following format: * car name : mileage : date : gallons : dollars * (date is mm/dd/yyyy or whatever is appropriate for your Java locale, enter * dollars without any dollar sign [$], and all whitespace is optional) * * Comments begin with a hash sign [#]. A hash sign appearing mid-line will * cause the program to ignore the rest of that line. * * Every time gas is put in the car, make a line in the data file marking how * much gas was bought, for how much money, the date, and the current odometer * reading. * * If the car was filled up by that purchase, add an additional field stating, * simply, "full". * * Every time the gas tank reaches a consistent state - being full - the * program can calculate gas mileage and other statistics based on the data in * the file. * * The program reports (per each refuel period): * - number of days passed * - miles traveled * - gallons put in * - dollars spent * - miles per gallon * - dollars per mile * - dollars per gallon * - miles per day. * * Additional statistics may easily be added to the GasMiles.printData() method, * if desired. This method gets passed the days, miles, gallons, and dollars. * * @author William R. Fraser */ public class GasMiles { public static final String version = "v2 2007.07.27"; public String dataFile; public String selectedCar; private Scanner scan; private HashMap cars; /** * Parse command line arguments, instantiate, and run. * @param args */ public static void main(String args[]) { if (args.length == 0) { System.err.printf("GasMiles %s (c) 2007 William R. Fraser\n", version); System.err.println("usage: GasMiles [carName]"); System.exit(1); } String selectedCar = null; if (args.length == 2) selectedCar = args[1]; GasMiles app = new GasMiles(args[0], selectedCar); app.go(); } /** * Initialize member variables * * (if selectedCar is set to null, it means view all cars) * * @param dataFile * @param selectedCar */ public GasMiles(String dataFile, String selectedCar) { this.dataFile = dataFile; this.selectedCar = selectedCar; try { scan = new java.util.Scanner(new File(dataFile)); } catch (FileNotFoundException ex) { System.err.println("Data file not found."); System.exit(1); } cars = new HashMap(); } /** * Run the program! * */ public void go() { // parse the data file loadData(); if (cars.size() == 0) { if (selectedCar != null) { System.err.printf("No data for car \"%s\" found.\n", selectedCar); System.exit(2); } else { System.err.printf("No records in data file.\n"); System.exit(2); } } for (GasHistory hist : cars.values()) { System.out.printf("GasMiles analysis for car \"%s\":\n", hist.carName); if (hist.numPeriods() == 0) { System.out.printf("\tNo statistics to report yet.\n\n"); } for (int i = 0; i < hist.numPeriods(); i++) { float miles = hist.newMiles(i); float gallons = hist.newGallons(i); float dollars = hist.newDollars(i); Date[] dates = hist.periodDates(i); Calendar spread = Calendar.getInstance(); spread.setTimeInMillis(dates[1].getTime() - dates[0].getTime()); int days = spread.get(Calendar.DAY_OF_YEAR); System.out.printf("Period %d: %s - %s\n", i, DateFormat.getDateInstance(DateFormat.SHORT).format(dates[0]), DateFormat.getDateInstance(DateFormat.SHORT).format(dates[1])); printData(miles, gallons, dollars, days); } } for (GasHistory hist : cars.values()) { System.out.printf("Totals for car \"%s\": ", hist.carName); float miles = hist.newMiles(GasHistory.PERIOD_ALL); float gallons = hist.newGallons(GasHistory.PERIOD_ALL); float dollars = hist.newDollars(GasHistory.PERIOD_ALL); Date[] dates = hist.periodDates(GasHistory.PERIOD_ALL); Calendar spread = Calendar.getInstance(); spread.setTimeInMillis(dates[1].getTime() - dates[0].getTime()); int days = spread.get(Calendar.DAY_OF_YEAR); System.out.printf("%s - %s\n", DateFormat.getDateInstance(DateFormat.SHORT).format(dates[0]), DateFormat.getDateInstance(DateFormat.SHORT).format(dates[1])); printTotals(miles, gallons, dollars, days); } } /** * Print the various fields on screen. * * @param miles * @param gallons * @param dollars * @param days */ public void printData(float miles, float gallons, float dollars, int days) { System.out.printf("\tdays | miles | gallons | dollars | MPG | $/mile | $/gal | miles/day\n"); System.out.printf("\t%4d | %5.1f | %7.3f | $%6.2f | %6.3f | $%5.3f | $%5.3f | %6.3f\n", days, miles, gallons, dollars, miles/gallons, dollars/miles, dollars/gallons, miles/days); System.out.println(); } /** * Print the various fields on screen. (used for totals) * * @param miles * @param gallons * @param dollars * @param days */ public void printTotals(float miles, float gallons, float dollars, int days) { System.out.printf("days | miles | gallons | dollars | MPG | $/mile | $/gal | miles/day\n"); System.out.printf("%4d | %8.1f | %8.3f | $%7.2f | %6.3f | $%5.3f | $%5.3f | %6.3f\n", days, miles, gallons, dollars, miles/gallons, dollars/miles, dollars/gallons, miles/days); System.out.println(); } /** * Parse the data file into the cars variable. * */ public void loadData() { int lineNo = 0; String field = ""; // for keeping potentially bogus fields while (scan.hasNextLine()) { lineNo++; String line = scan.nextLine().trim(); // ignore anything after a '#' line = line.split("#",2)[0]; // skip empty lines (and comments) if (line.length() == 0) continue; Scanner lineScan = new Scanner(line).useDelimiter(":"); // get each field String carName = lineScan.next(); // if we're only looking for one car, skip the others if (selectedCar != null && !carName.equals(selectedCar)) continue; if (!cars.containsKey(carName)) { // if this is a car we haven't seen, make a new history for it cars.put(carName, new GasHistory(carName)); } GasHistory currentHistory = cars.get(carName); Date date; float mileage, gallons, dollars; boolean full = false; try { // catch not enough fields try { mileage = Float.parseFloat(field = lineScan.next()); } catch (NumberFormatException ex) { System.err.printf("Parse error, %s:%d - unknown data, " + "expecting a number (mileage): \"%s\"\n", dataFile, lineNo, field); System.exit(1); return; } try { date = DateFormat.getDateInstance(DateFormat.SHORT) .parse(field = lineScan.next()); } catch (ParseException ex) { System.err.printf("Parse error, %s:%d - unknown date " + "format: \"%s\"\n", dataFile, lineNo, field); System.exit(1); return; } try { gallons = Float.parseFloat(field = lineScan.next()); } catch (NumberFormatException ex) { System.err.printf("Parse error, %s:%d - unknown data, " + "expecting a number (gallons): \"%s\"\n", dataFile, lineNo, field); System.exit(1); return; } try { dollars = Float.parseFloat(field = lineScan.next()); } catch (NumberFormatException ex) { System.err.printf("Parse error, %s:%d - unknown data, " + "expecting a number (dollars): \"%s\"\n", dataFile, lineNo, field); System.exit(1); return; } if (lineScan.hasNext()) { if ((field = lineScan.next().trim()).equals("full")) { full = true; } else { System.err.printf("Parse error, %s:%d - unknown data, " + "expecting \"full\" or no field: \"%s\"\n", dataFile, lineNo, field); System.exit(1); return; } } } catch (NoSuchElementException ex) { System.err.printf("Parse error, %s:%d - not enough fields!\n", dataFile, lineNo); System.exit(1); return; } if (full) { currentHistory.pushFull(mileage, date, gallons, dollars); } else { // add the data to the current period currentHistory.pushGas(mileage, date, gallons, dollars); } } } }