undelivered=getUndelivered(packageList);
- if (undelivered.size() == 0){
- System.out.println("No undelivered packages to show");
- return;
- }
- this.list(undelivered);
- Scanner scan = new Scanner(System.in);
- int n;
- do {
- System.out.println("enter item number you want to mark delivered (0 to cancel):");
- n = scan.nextInt();
- }
- while (n < 0 || n > undelivered.size());
- if (n > 0){
- PackageInfo p = undelivered.get(n-1);
- packageList.get(packageList.indexOf(p)).setDelivered(true);
- System.out.println(p.getName() + " has been delivered.");
- }
- }
-
-
-}
diff --git a/src/cmpt213/assignment2/packagedeliveriestracker/gson/extras/RuntimeTypeAdapterFactory.java b/src/cmpt213/assignment2/packagedeliveriestracker/gson/extras/RuntimeTypeAdapterFactory.java
new file mode 100644
index 0000000..8a4807b
--- /dev/null
+++ b/src/cmpt213/assignment2/packagedeliveriestracker/gson/extras/RuntimeTypeAdapterFactory.java
@@ -0,0 +1,256 @@
+package cmpt213.assignment2.packagedeliveriestracker.gson.extras;
+
+import com.google.gson.*;
+import com.google.gson.reflect.TypeToken;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonWriter;
+
+import java.io.IOException;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * Adapts values whose runtime type may differ from their declaration type. This
+ * is necessary when a field's type is not the same type that GSON should create
+ * when deserializing that field. For example, consider these types:
+ * {@code
+ * abstract class Shape {
+ * int x;
+ * int y;
+ * }
+ * class Circle extends Shape {
+ * int radius;
+ * }
+ * class Rectangle extends Shape {
+ * int width;
+ * int height;
+ * }
+ * class Diamond extends Shape {
+ * int width;
+ * int height;
+ * }
+ * class Drawing {
+ * Shape bottomShape;
+ * Shape topShape;
+ * }
+ * }
+ * Without additional type information, the serialized JSON is ambiguous. Is
+ * the bottom shape in this drawing a rectangle or a diamond?
{@code
+ * {
+ * "bottomShape": {
+ * "width": 10,
+ * "height": 5,
+ * "x": 0,
+ * "y": 0
+ * },
+ * "topShape": {
+ * "radius": 2,
+ * "x": 4,
+ * "y": 1
+ * }
+ * }}
+ * This class addresses this problem by adding type information to the
+ * serialized JSON and honoring that type information when the JSON is
+ * deserialized: {@code
+ * {
+ * "bottomShape": {
+ * "type": "Diamond",
+ * "width": 10,
+ * "height": 5,
+ * "x": 0,
+ * "y": 0
+ * },
+ * "topShape": {
+ * "type": "Circle",
+ * "radius": 2,
+ * "x": 4,
+ * "y": 1
+ * }
+ * }}
+ * Both the type field name ({@code "type"}) and the type labels ({@code
+ * "Rectangle"}) are configurable.
+ *
+ * Registering Types
+ * Create a {@code cmpt213.assignment2.packagedeliveriestracker.gson.extras.RuntimeTypeAdapterFactory} by passing the base type and type field
+ * name to the {@link #of} factory method. If you don't supply an explicit type
+ * field name, {@code "type"} will be used. {@code
+ * cmpt213.assignment2.packagedeliveriestracker.gson.extras.RuntimeTypeAdapterFactory shapeAdapterFactory
+ * = cmpt213.assignment2.packagedeliveriestracker.gson.extras.RuntimeTypeAdapterFactory.of(Shape.class, "type");
+ * }
+ * Next register all of your subtypes. Every subtype must be explicitly
+ * registered. This protects your application from injection attacks. If you
+ * don't supply an explicit type label, the type's simple name will be used.
+ * {@code
+ * shapeAdapterFactory.registerSubtype(Rectangle.class, "Rectangle");
+ * shapeAdapterFactory.registerSubtype(Circle.class, "Circle");
+ * shapeAdapterFactory.registerSubtype(Diamond.class, "Diamond");
+ * }
+ * Finally, register the type adapter factory in your application's GSON builder:
+ * {@code
+ * Gson gson = new GsonBuilder()
+ * .registerTypeAdapterFactory(shapeAdapterFactory)
+ * .create();
+ * }
+ * Like {@code GsonBuilder}, this API supports chaining: {@code
+ * cmpt213.assignment2.packagedeliveriestracker.gson.extras.RuntimeTypeAdapterFactory shapeAdapterFactory = cmpt213.assignment2.packagedeliveriestracker.gson.extras.RuntimeTypeAdapterFactory.of(Shape.class)
+ * .registerSubtype(Rectangle.class)
+ * .registerSubtype(Circle.class)
+ * .registerSubtype(Diamond.class);
+ * }
+ *
+ * Serialization and deserialization
+ * In order to serialize and deserialize a polymorphic object,
+ * you must specify the base type explicitly.
+ * {@code
+ * Diamond diamond = new Diamond();
+ * String json = gson.toJson(diamond, Shape.class);
+ * }
+ * And then:
+ * {@code
+ * Shape shape = gson.fromJson(json, Shape.class);
+ * }
+ */
+public final class RuntimeTypeAdapterFactory implements TypeAdapterFactory {
+ private final Class> baseType;
+ private final String typeFieldName;
+ private final Map> labelToSubtype = new LinkedHashMap<>();
+ private final Map, String> subtypeToLabel = new LinkedHashMap<>();
+ private final boolean maintainType;
+
+ private RuntimeTypeAdapterFactory(Class> baseType, String typeFieldName, boolean maintainType) {
+ if (typeFieldName == null || baseType == null) {
+ throw new NullPointerException();
+ }
+ this.baseType = baseType;
+ this.typeFieldName = typeFieldName;
+ this.maintainType = maintainType;
+ }
+
+ /**
+ * Creates a new runtime type adapter using for {@code baseType} using {@code
+ * typeFieldName} as the type field name. Type field names are case sensitive.
+ * {@code maintainType} flag decide if the type will be stored in pojo or not.
+ */
+ public static RuntimeTypeAdapterFactory of(Class baseType, String typeFieldName, boolean maintainType) {
+ return new RuntimeTypeAdapterFactory<>(baseType, typeFieldName, maintainType);
+ }
+
+ /**
+ * Creates a new runtime type adapter using for {@code baseType} using {@code
+ * typeFieldName} as the type field name. Type field names are case sensitive.
+ */
+ public static RuntimeTypeAdapterFactory of(Class baseType, String typeFieldName) {
+ return new RuntimeTypeAdapterFactory<>(baseType, typeFieldName, false);
+ }
+
+ /**
+ * Creates a new runtime type adapter for {@code baseType} using {@code "type"} as
+ * the type field name.
+ */
+ public static RuntimeTypeAdapterFactory of(Class baseType) {
+ return new RuntimeTypeAdapterFactory<>(baseType, "type", false);
+ }
+
+ /**
+ * Registers {@code type} identified by {@code label}. Labels are case
+ * sensitive.
+ *
+ * @throws IllegalArgumentException if either {@code type} or {@code label}
+ * have already been registered on this type adapter.
+ */
+ public RuntimeTypeAdapterFactory registerSubtype(Class extends T> type, String label) {
+ if (type == null || label == null) {
+ throw new NullPointerException();
+ }
+ if (subtypeToLabel.containsKey(type) || labelToSubtype.containsKey(label)) {
+ throw new IllegalArgumentException("types and labels must be unique");
+ }
+ labelToSubtype.put(label, type);
+ subtypeToLabel.put(type, label);
+ return this;
+ }
+
+ /**
+ * Registers {@code type} identified by its {@link Class#getSimpleName simple
+ * name}. Labels are case sensitive.
+ *
+ * @throws IllegalArgumentException if either {@code type} or its simple name
+ * have already been registered on this type adapter.
+ */
+ public RuntimeTypeAdapterFactory registerSubtype(Class extends T> type) {
+ return registerSubtype(type, type.getSimpleName());
+ }
+
+ @Override
+ public TypeAdapter create(Gson gson, TypeToken type) {
+ if (type.getRawType() != baseType) {
+ return null;
+ }
+
+ final TypeAdapter jsonElementAdapter = gson.getAdapter(JsonElement.class);
+ final Map> labelToDelegate = new LinkedHashMap<>();
+ final Map, TypeAdapter>> subtypeToDelegate = new LinkedHashMap<>();
+ for (Map.Entry> entry : labelToSubtype.entrySet()) {
+ TypeAdapter> delegate = gson.getDelegateAdapter(this, TypeToken.get(entry.getValue()));
+ labelToDelegate.put(entry.getKey(), delegate);
+ subtypeToDelegate.put(entry.getValue(), delegate);
+ }
+
+ return new TypeAdapter() {
+ @Override
+ public R read(JsonReader in) throws IOException {
+ JsonElement jsonElement = jsonElementAdapter.read(in);
+ JsonElement labelJsonElement;
+ if (maintainType) {
+ labelJsonElement = jsonElement.getAsJsonObject().get(typeFieldName);
+ } else {
+ labelJsonElement = jsonElement.getAsJsonObject().remove(typeFieldName);
+ }
+
+ if (labelJsonElement == null) {
+ throw new JsonParseException("cannot deserialize " + baseType
+ + " because it does not define a field named " + typeFieldName);
+ }
+ String label = labelJsonElement.getAsString();
+ @SuppressWarnings("unchecked") // registration requires that subtype extends T
+ TypeAdapter delegate = (TypeAdapter) labelToDelegate.get(label);
+ if (delegate == null) {
+ throw new JsonParseException("cannot deserialize " + baseType + " subtype named "
+ + label + "; did you forget to register a subtype?");
+ }
+ return delegate.fromJsonTree(jsonElement);
+ }
+
+ @Override
+ public void write(JsonWriter out, R value) throws IOException {
+ Class> srcType = value.getClass();
+ String label = subtypeToLabel.get(srcType);
+ @SuppressWarnings("unchecked") // registration requires that subtype extends T
+ TypeAdapter delegate = (TypeAdapter) subtypeToDelegate.get(srcType);
+ if (delegate == null) {
+ throw new JsonParseException("cannot serialize " + srcType.getName()
+ + "; did you forget to register a subtype?");
+ }
+ JsonObject jsonObject = delegate.toJsonTree(value).getAsJsonObject();
+
+ if (maintainType) {
+ jsonElementAdapter.write(out, jsonObject);
+ return;
+ }
+
+ JsonObject clone = new JsonObject();
+
+ if (jsonObject.has(typeFieldName)) {
+ throw new JsonParseException("cannot serialize " + srcType.getName()
+ + " because it already defines a field named " + typeFieldName);
+ }
+ clone.add(typeFieldName, new JsonPrimitive(label));
+
+ for (Map.Entry e : jsonObject.entrySet()) {
+ clone.add(e.getKey(), e.getValue());
+ }
+ jsonElementAdapter.write(out, clone);
+ }
+ }.nullSafe();
+ }
+}
\ No newline at end of file
diff --git a/src/BookPackage.java b/src/cmpt213/assignment2/packagedeliveriestracker/model/BookPackage.java
similarity index 57%
rename from src/BookPackage.java
rename to src/cmpt213/assignment2/packagedeliveriestracker/model/BookPackage.java
index 16abec8..9d1447a 100644
--- a/src/BookPackage.java
+++ b/src/cmpt213/assignment2/packagedeliveriestracker/model/BookPackage.java
@@ -1,17 +1,23 @@
+package cmpt213.assignment2.packagedeliveriestracker.model;
+
import java.time.LocalDateTime;
-public class BookPackage extends PackageInfo{
+/**
+ * It's a subclass of PackageInfo that adds an author field
+ */
+public class BookPackage extends PackageInfo {
String author;
public BookPackage(String name, String note, double price, double weight, boolean delivered, LocalDateTime expectedDate, String author) {
super(name, note, price, weight, delivered, expectedDate);
this.author = author;
+ this.setType("book");
}
@Override
public String toString() {
- return super.toString()+"author='" + author + '\'';
+ return super.toString() + "\nAuthor: " + author;
}
}
diff --git a/src/ElectronicPackage.java b/src/cmpt213/assignment2/packagedeliveriestracker/model/ElectronicPackage.java
similarity index 54%
rename from src/ElectronicPackage.java
rename to src/cmpt213/assignment2/packagedeliveriestracker/model/ElectronicPackage.java
index 13847b2..715e209 100644
--- a/src/ElectronicPackage.java
+++ b/src/cmpt213/assignment2/packagedeliveriestracker/model/ElectronicPackage.java
@@ -1,15 +1,21 @@
+package cmpt213.assignment2.packagedeliveriestracker.model;
+
import java.time.LocalDateTime;
-public class ElectronicPackage extends PackageInfo{
+/**
+ * ElectronicPackage is a subclass of PackageInfo that adds a handlingFee attribute
+ */
+public class ElectronicPackage extends PackageInfo {
double handlingFee;
public ElectronicPackage(String name, String note, double price, double weight, boolean delivered, LocalDateTime expectedDate, double handlingFee) {
super(name, note, price, weight, delivered, expectedDate);
this.handlingFee = handlingFee;
+ this.setType("electronic");
}
@Override
public String toString() {
- return super.toString()+"handlingFee=" + handlingFee;
+ return super.toString() + "\nHandling fee: " + handlingFee;
}
}
diff --git a/src/cmpt213/assignment2/packagedeliveriestracker/model/PackageFactory.java b/src/cmpt213/assignment2/packagedeliveriestracker/model/PackageFactory.java
new file mode 100644
index 0000000..4ffe028
--- /dev/null
+++ b/src/cmpt213/assignment2/packagedeliveriestracker/model/PackageFactory.java
@@ -0,0 +1,68 @@
+package cmpt213.assignment2.packagedeliveriestracker.model;
+
+import java.time.LocalDateTime;
+
+/**
+ * The PackageFactory class is a factory class that creates a package object based on the type of
+ * package that is passed in
+ */
+public class PackageFactory {
+ /**
+ * > This function creates a new package object based on the package type
+ *
+ * @param type The type of package.
+ * @param name The name of the package.
+ * @param notes A string of notes about the package.
+ * @param price The price of the package
+ * @param weight The weight of the package in kilograms
+ * @param delivered Whether or not the package has been delivered
+ * @param expectDate The date the package is expected to be delivered.
+ * @param author The author of the book.
+ * @param expiryDate The date the package expires.
+ * @param handlingFee The handling fee for the package.
+ * @return A new instance of the package type.
+ */
+
+ public static PackageInfo create(PackageType type, String name, String notes, double price, double weight, boolean delivered, LocalDateTime expectDate, String author, LocalDateTime expiryDate, double handlingFee) {
+ return type.getInstance(name, notes, price, weight, delivered, expectDate, author, expiryDate, handlingFee);
+ }
+
+ // Creating an enum called PackageType.
+ public enum PackageType {
+ Book {
+ public PackageInfo getInstance(String name, String notes, double price, double weight, boolean delivered, LocalDateTime expectedDate, String author, LocalDateTime expiryDate, double handlingFee) {
+ return new BookPackage(name, notes, price, weight, delivered, expectedDate, author);
+ }
+ },
+
+ Perishable {
+ public PackageInfo getInstance(String name, String notes, double price, double weight, boolean delivered, LocalDateTime expectedDate, String author, LocalDateTime expiryDate, double handlingFee) {
+ return new PerishablePackage(name, notes, price, weight, delivered, expectedDate, expiryDate);
+ }
+ },
+
+ Electronic {
+ public PackageInfo getInstance(String name, String notes, double price, double weight, boolean delivered, LocalDateTime expectedDate, String author, LocalDateTime expiryDate, double handlingFee) {
+ return new ElectronicPackage(name, notes, price, weight, delivered, expectedDate, handlingFee);
+ }
+ };
+
+ /**
+ * It returns a PackageInfo object.
+ *
+ * @param name The name of the package.
+ * @param notes a string that describes the package
+ * @param price The price of the package
+ * @param weight the weight of the package in kg
+ * @param delivered true if the package has been delivered, false otherwise
+ * @param expectedDate The date the package is expected to be delivered.
+ * @param author The name of the author of the book.
+ * @param expiryDate The date when the package expires.
+ * @param handlingFee The handling fee for the package.
+ * @return A package object
+ */
+
+ public abstract PackageInfo getInstance(String name, String notes, double price, double weight, boolean delivered, LocalDateTime expectedDate, String author, LocalDateTime expiryDate, double handlingFee);
+ }
+
+}
\ No newline at end of file
diff --git a/src/cmpt213/assignment2/packagedeliveriestracker/model/PackageInfo.java b/src/cmpt213/assignment2/packagedeliveriestracker/model/PackageInfo.java
new file mode 100644
index 0000000..e4ef20b
--- /dev/null
+++ b/src/cmpt213/assignment2/packagedeliveriestracker/model/PackageInfo.java
@@ -0,0 +1,85 @@
+package cmpt213.assignment2.packagedeliveriestracker.model;
+
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.temporal.ChronoUnit;
+
+import static java.lang.Math.abs;
+
+/**
+ * PackageInfo is a class that contains information about a package
+ */
+
+public class PackageInfo implements Comparable {
+ private final String name;
+ private final String note;
+ private final double price;
+ private final double weight;
+ private final LocalDateTime expectedDate;
+ private boolean delivered;
+ private String type;
+
+ public PackageInfo(String name, String note, double price, double weight, boolean delivered, LocalDateTime expectedDate) {
+ this.name = name;
+ this.note = note;
+ this.price = price;
+ this.weight = weight;
+ this.delivered = delivered;
+ this.expectedDate = expectedDate;
+ }
+
+ /**
+ * This function returns the name of the person.
+ *
+ * @return The name of the person.
+ */
+ public String getName() {
+ return name;
+ }
+
+
+ public boolean getDelivered() {
+ return delivered;
+ }
+
+ /**
+ * This function sets the value of the delivered variable to the value of the delivered parameter.
+ *
+ * @param delivered This is a boolean value that indicates whether the message has been delivered
+ * to the recipient.
+ */
+ public void setDelivered(boolean delivered) {
+ this.delivered = delivered;
+ }
+
+ public LocalDateTime getExpectedDate() {
+ return expectedDate;
+ }
+
+ /**
+ * This function sets the type of the object to the type passed in as a parameter
+ *
+ * @param type The type of the event.
+ */
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ // Comparing the expected date of the package to the expected date of the package passed in as a
+ // parameter.
+ @Override
+ public int compareTo(PackageInfo p) {
+ return this.expectedDate.compareTo(p.getExpectedDate());
+ }
+
+ // Overriding the toString method.
+ @Override
+ public String toString() {
+ DateTimeFormatter format = DateTimeFormatter.ofPattern("yyy-MM-dd HH:mm");
+ LocalDateTime today = LocalDateTime.now();
+ today.format(format);
+ long diff = ChronoUnit.DAYS.between(today, expectedDate);
+ String isDelivered = delivered ? "yes" : "no";
+ return "Name: " + name + "\n" + "Notes: " + note + "\n" + "Price: " + price + "\n" + "Weight: " + weight + "\n" + "Expected Delivery Date: " + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm").format(expectedDate) + "\n" + "Delivered? " + isDelivered + "\n" + ((diff > 0 && !delivered) ? diff + " days remaining" : abs(diff) + " days overdue");
+ }
+}
diff --git a/src/cmpt213/assignment2/packagedeliveriestracker/model/PerishablePackage.java b/src/cmpt213/assignment2/packagedeliveriestracker/model/PerishablePackage.java
new file mode 100644
index 0000000..522ec5e
--- /dev/null
+++ b/src/cmpt213/assignment2/packagedeliveriestracker/model/PerishablePackage.java
@@ -0,0 +1,24 @@
+package cmpt213.assignment2.packagedeliveriestracker.model;
+
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+
+/**
+ * It's a subclass of PackageInfo that adds an expiry date
+ */
+public class PerishablePackage extends PackageInfo {
+ LocalDateTime expiryDate;
+
+ // It's a constructor that initializes the object with the given parameters.
+ public PerishablePackage(String name, String note, double price, double weight, boolean delivered, LocalDateTime expectedDate, LocalDateTime expiryDate) {
+ super(name, note, price, weight, delivered, expectedDate);
+ this.expiryDate = expiryDate;
+ this.setType("perishable");
+ }
+
+ // It's a method that returns a string representation of the object.
+ @Override
+ public String toString() {
+ return super.toString() + "\nExpiry date: " + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm").format(expiryDate);
+ }
+}
diff --git a/src/cmpt213/assignment2/packagedeliveriestracker/textui/PackageDeliveriesTracker.java b/src/cmpt213/assignment2/packagedeliveriestracker/textui/PackageDeliveriesTracker.java
new file mode 100644
index 0000000..405470c
--- /dev/null
+++ b/src/cmpt213/assignment2/packagedeliveriestracker/textui/PackageDeliveriesTracker.java
@@ -0,0 +1,137 @@
+package cmpt213.assignment2.packagedeliveriestracker.textui;
+
+import cmpt213.assignment2.packagedeliveriestracker.gson.extras.RuntimeTypeAdapterFactory;
+import cmpt213.assignment2.packagedeliveriestracker.model.BookPackage;
+import cmpt213.assignment2.packagedeliveriestracker.model.ElectronicPackage;
+import cmpt213.assignment2.packagedeliveriestracker.model.PackageInfo;
+import cmpt213.assignment2.packagedeliveriestracker.model.PerishablePackage;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.TypeAdapter;
+import com.google.gson.reflect.TypeToken;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonWriter;
+
+import java.io.*;
+import java.lang.reflect.Type;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.Scanner;
+
+/**
+ * It's a class that contains a main method that creates a TextMenu object, loads a list of packages
+ * from a file, and then loops through a menu of options until the user chooses to quit
+ */
+public class PackageDeliveriesTracker {
+ private static final String fileName = "list.json";
+ private static final RuntimeTypeAdapterFactory r = RuntimeTypeAdapterFactory.of(PackageInfo.class, "type")
+ .registerSubtype(BookPackage.class, "book")
+ .registerSubtype(PerishablePackage.class, "perishable")
+ .registerSubtype(ElectronicPackage.class, "electronic");
+ private static final Gson gson = new GsonBuilder().registerTypeAdapter(LocalDateTime.class,
+ new TypeAdapter() {
+ @Override
+ public void write(JsonWriter jsonWriter,
+ LocalDateTime localDateTime) throws IOException {
+ jsonWriter.value(localDateTime.toString());
+ }
+
+ @Override
+ public LocalDateTime read(JsonReader jsonReader) throws IOException {
+ return LocalDateTime.parse(jsonReader.nextString());
+ }
+ }).registerTypeAdapterFactory(r).create();
+ private static ArrayList packageList = new ArrayList<>();
+
+ /**
+ * It saves the packageList to a file.
+ */
+ private static void save() {
+ try {
+ Writer w = new FileWriter(fileName);
+ gson.toJson(packageList, w);
+ w.flush();
+ w.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * It reads the json file, converts it to a list of PackageInfo objects, and then sets the type of
+ * each object to the appropriate type
+ */
+ private static void load() {
+ File file = new File(fileName);
+ try {
+ String json = Files.readString(Paths.get(fileName));
+ Type lType = new TypeToken>() {
+ }.getType();
+ packageList = gson.fromJson(json, lType);
+ for (PackageInfo p : packageList) {
+ if (p instanceof BookPackage) {
+ p.setType("book");
+ } else if (p instanceof PerishablePackage) {
+ p.setType("perishable");
+ } else if (p instanceof ElectronicPackage) {
+ p.setType("electronic");
+ }
+ }
+ System.out.println("packages loaded");
+ } catch (FileNotFoundException e) {
+ System.out.println("no packages to load");
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * This function is the main function of the program. It creates a TextMenu object, loads the
+ * packages from the file, displays the menu, and then waits for the user to input a number between
+ * 1 and 7. If the user inputs a number outside of that range, the program will display an error
+ * message. If the user inputs 7, the program will save the packages to the file and exit. If the
+ * user inputs a number between 1 and 6, the program will call the corresponding function in the
+ * TextMenu class
+ */
+ public static void main(String[] args) {
+ TextMenu menu = new TextMenu();
+ load();
+ do {
+ menu.display();
+ System.out.println("choose an option between 1 and 7:");
+ Scanner scan = new Scanner(System.in);
+ int option = Integer.parseInt(scan.nextLine());
+ if (option > 7 || option < 1) {
+ System.out.println("invalid input");
+ } else if (option == 7) {
+ save();
+ System.out.print("packages saved");
+ break;
+ } else {
+ switch (option) {
+ case 1:
+ menu.list(packageList);
+ break;
+ case 2:
+ menu.add(packageList);
+ break;
+ case 3:
+ menu.remove(packageList);
+ break;
+ case 4:
+ menu.overDueList(packageList);
+ break;
+ case 5:
+ menu.upcomingList(packageList);
+ break;
+ case 6:
+ menu.markDelivered(packageList);
+ }
+ }
+ }
+ while (true);
+ }
+}
+
diff --git a/src/cmpt213/assignment2/packagedeliveriestracker/textui/TextMenu.java b/src/cmpt213/assignment2/packagedeliveriestracker/textui/TextMenu.java
new file mode 100644
index 0000000..7613412
--- /dev/null
+++ b/src/cmpt213/assignment2/packagedeliveriestracker/textui/TextMenu.java
@@ -0,0 +1,280 @@
+package cmpt213.assignment2.packagedeliveriestracker.textui;
+
+import cmpt213.assignment2.packagedeliveriestracker.model.PackageFactory;
+import cmpt213.assignment2.packagedeliveriestracker.model.PackageInfo;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+import java.util.*;
+
+/**
+ * It's a class that displays a menu and allows the user to interact with the program
+ */
+public class TextMenu {
+ private final String title = "Package Tracker";
+ private final String[] options = new String[]{
+ "List of all packages",
+ "Add a package",
+ "remove a package",
+ "list overdue packages",
+ "List upcoming packages",
+ "mark package as delivered",
+ "exit"};
+
+ /**
+ * This function displays the title of the menu, the current date, and the options available to the
+ * user
+ */
+ public void display() {
+ int tag = title.length();
+ for (int i = 0; i <= tag + 3; i++) {
+ System.out.print("#");
+ }
+ System.out.println("\n# " + title + " #");
+ for (int i = 0; i <= tag + 3; i++) {
+ System.out.print("#");
+ }
+ DateFormat today = new SimpleDateFormat("yyy-MM-dd");
+ Calendar cal = Calendar.getInstance();
+ System.out.println("\nToday is: " + today.format(cal.getTime()));
+ for (int i = 0; i < options.length; i++) {
+ System.out.println((i + 1) + ": " + options[i]);
+ }
+ }
+
+ /**
+ * This function takes an ArrayList of PackageInfo objects and prints them out in reverse order
+ *
+ * @param packageList The list of packages to be displayed
+ */
+ public void list(ArrayList packageList) {
+ if (packageList.size() == 0) {
+ System.out.println("No packages to show");
+ } else {
+ Collections.sort(packageList);
+ for (int i = 0; i < packageList.size(); i++) {
+ System.out.println("Package #" + (i + 1));
+ System.out.println(packageList.get(i) + "\n");
+ }
+ }
+ }
+
+ /**
+ * This function allows the user to add a package to the list of packages
+ *
+ * @param packageList the list of packages
+ */
+ public void add(ArrayList packageList) {
+ Scanner scan = new Scanner(System.in);
+ int itemType;
+ do {
+ System.out.println("please select (1) book, (2) perishable, or (3) electronic package");
+ itemType = Integer.parseInt(scan.nextLine());
+ }
+ while (itemType < 1 || itemType > 3);
+ PackageFactory.PackageType pType = PackageFactory.PackageType.Book;
+ String author = "";
+ LocalDateTime expiryDate = LocalDateTime.now();
+ double handlingFee = 0.0;
+ String pName;
+ do {
+ System.out.println("enter a valid package name:");
+ pName = scan.nextLine();
+ }
+ while (pName.length() == 0);
+ System.out.println("notes:");
+ String pNotes = scan.nextLine();
+ boolean checkDate = false;
+ DateTimeFormatter format = DateTimeFormatter.ofPattern("yyy-MM-dd HH:mm");
+ LocalDateTime pDate = LocalDateTime.now();
+ while (checkDate == false) {
+ try {
+ System.out.println("enter date as yyyy-mm-dd hh:mm:");
+ pDate = LocalDateTime.parse(scan.nextLine(), format);
+ checkDate = true;
+ } catch (DateTimeParseException e) {
+ System.out.println("invalid date format");
+ }
+ }
+ double pPrice;
+ do {
+ System.out.println("enter price:");
+ pPrice = Double.parseDouble(scan.nextLine());
+ }
+ while (pPrice < 0);
+ double pWeight;
+ do {
+ System.out.println("enter weight:");
+ pWeight = Double.parseDouble(scan.nextLine());
+ }
+ while (pWeight <= 0);
+ switch (itemType) {
+ case 1:
+ do {
+ System.out.println("enter author");
+ author = scan.nextLine();
+ }
+ while (author.length() == 0);
+ break;
+ case 2:
+ boolean checkExpDate = false;
+ while (checkExpDate == false) {
+ try {
+ System.out.println("enter expiry date as yyyy-mm-dd hh:mm");
+ expiryDate = LocalDateTime.parse(scan.nextLine(), format);
+ checkExpDate = true;
+ } catch (DateTimeParseException e) {
+ System.out.println("invalid date format");
+ }
+ }
+ pType = PackageFactory.PackageType.Perishable;
+ break;
+ case 3:
+ do {
+ System.out.println("enter handling fee");
+ handlingFee = Double.parseDouble(scan.nextLine());
+ }
+ while (handlingFee <= 0);
+ pType = PackageFactory.PackageType.Electronic;
+ break;
+ }
+ PackageInfo p = PackageFactory.create(pType, pName, pNotes, pPrice, pWeight, false, pDate, author, expiryDate, handlingFee);
+ packageList.add(p);
+ System.out.println(pName + " has been added to the list!");
+ }
+
+ /**
+ * This function takes in an ArrayList of PackageInfo objects and prints out the list of packages
+ * in the ArrayList. The user is then prompted to enter the number of the package they want to
+ * remove. If the user enters a number that is not in the list, the user is prompted to enter a
+ * number again. If the user enters 0, the function returns without removing anything. If the user
+ * enters a number that is in the list, the package is removed from the list and the user is
+ * notified
+ *
+ * @param packageList the list of packages to be displayed
+ */
+ public void remove(ArrayList packageList) {
+ this.list(packageList);
+ if (packageList.size() == 0) {
+ return;
+ }
+ Scanner scan = new Scanner(System.in);
+ int n;
+ do {
+ System.out.println("enter item number you want to remove (0 to cancel):");
+ n = scan.nextInt();
+ }
+ while (n < 0 || n > packageList.size());
+ if (n > 0) {
+ System.out.println(packageList.get(n - 1).getName() + " has been removed from the list.");
+ packageList.remove(n - 1);
+ }
+ }
+
+ /**
+ * This function takes in a list of packages, a boolean for whether or not the user wants to see
+ * packages that are due, and a boolean for whether or not the user wants to see all packages. It
+ * then returns a list of packages that are sorted by their expected delivery date
+ *
+ * @param pList the list of packages to be sorted
+ * @param due true if you want to sort the list by due date, false if you want to sort the list by
+ * expected date
+ * @return An ArrayList of PackageInfo objects.
+ */
+ //due=true returns overdue packages, else upcoming packages
+ public ArrayList sortList(ArrayList pList, boolean due) {
+ ArrayList sortedList = new ArrayList<>();
+ LocalDateTime today = LocalDateTime.now();
+ DateTimeFormatter format = DateTimeFormatter.ofPattern("yyy-MM-dd HH:mm");
+ today.format(format);
+ for (int i = 0; i < pList.size(); i++) {
+ PackageInfo p = pList.get(i);
+ if (!p.getDelivered()) {
+ if (due && today.isAfter(p.getExpectedDate())) {
+ sortedList.add(p);
+ } else if (!due && today.isBefore(p.getExpectedDate())) {
+ sortedList.add(p);
+ }
+ }
+ }
+ Collections.sort(sortedList);
+ return sortedList;
+ }
+
+ /**
+ * This function takes in a list of packages and returns a list of packages that are overdue
+ *
+ * @param packageList the list of packages to be sorted
+ */
+ public void overDueList(ArrayList packageList) {
+ ArrayList overdue = sortList(packageList, true);
+ if (overdue.size() == 0) {
+ System.out.println("no overdue packages to show");
+ return;
+ }
+ this.list(overdue);
+ }
+
+ /**
+ * This function takes in a list of packages and prints out the packages that are upcoming
+ *
+ * @param packageList the list of packages to be sorted
+ */
+ public void upcomingList(ArrayList packageList) {
+ ArrayList upcoming = sortList(packageList, false);
+ if (upcoming.size() == 0) {
+ System.out.println("no upcoming packages to show");
+ return;
+ }
+ this.list(upcoming);
+ }
+
+ /**
+ * This function takes a list of packages and returns a list of packages that have not been
+ * delivered
+ *
+ * @param packageList The list of packages to be filtered
+ * @return An ArrayList of PackageInfo objects that have not been delivered.
+ */
+ public ArrayList getUndelivered(List packageList) {
+ ArrayList undelivered = new ArrayList<>();
+ for (int i = 0; i < packageList.size(); i++) {
+ if (packageList.get(i).getDelivered() == false) {
+ undelivered.add(packageList.get(i));
+ }
+ }
+ return undelivered;
+ }
+
+ /**
+ * This function takes a list of packages and prints out the undelivered packages, then asks the
+ * user to select a package to mark as delivered
+ *
+ * @param packageList the list of packages to be marked as delivered
+ */
+ public void markDelivered(ArrayList packageList) {
+ ArrayList undelivered = getUndelivered(packageList);
+ if (undelivered.size() == 0) {
+ System.out.println("No undelivered packages to show");
+ return;
+ }
+ this.list(undelivered);
+ Scanner scan = new Scanner(System.in);
+ int n;
+ do {
+ System.out.println("enter item number you want to mark delivered (0 to cancel):");
+ n = scan.nextInt();
+ }
+ while (n < 0 || n > undelivered.size());
+ if (n > 0) {
+ PackageInfo p = undelivered.get(n - 1);
+ packageList.get(packageList.indexOf(p)).setDelivered(true);
+ System.out.println(p.getName() + " has been delivered.");
+ }
+ }
+
+
+}