Compare commits

..

16 Commits

Author SHA1 Message Date
041d85b85d hw3: unpackage things for submission 2025-03-26 21:41:40 -07:00
19141831c8 hw3: add googledoc link 2025-03-26 21:41:13 -07:00
f098922149 hw3: add javadoc everywhere 2025-03-26 21:14:46 -07:00
c10ba4b2fc hw3: PhotoAlbumView: improve performance by caching thumbnails 2025-03-25 00:47:27 -07:00
d4705dc11e hw3: PhotoAlbumModel: improve performance by returning unmodifiable list rather than copying 2025-03-25 00:47:19 -07:00
0fb5fd0654 hw3: PhotoAlbumModel: remove sortingStrategy from constructor 2025-03-25 00:32:23 -07:00
b12cf9f6d2 hw3: PhotoAlbumView: add thumbnails, date, file size to list 2025-03-25 00:31:55 -07:00
528254c9bc hw3: PhotoAlbumView: disable unusable buttons 2025-03-25 00:09:22 -07:00
63864e4e25 hw3: use iterator pattern for iterating through list 2025-03-24 23:59:34 -07:00
f8b38bae8f hw3: convert class members to final where possible, make Photo a record class 2025-03-24 23:28:39 -07:00
31302dd0d5 hw3: prototype completed 2025-03-24 23:18:10 -07:00
81abdd064d hw3: init 2025-03-24 01:38:54 -07:00
593840bd51 midterm1: practice stuff 2025-03-11 23:18:20 -07:00
a80fe72611 hw2: add JavaDoc, required runs 2025-02-24 21:16:08 -08:00
1e55218f47 hw2: functionality completed (I think) 2025-02-23 23:35:58 -08:00
ff2c9489bb WIP: hw2 2025-02-22 18:01:17 -08:00
48 changed files with 1959 additions and 0 deletions

29
hw2/.gitignore vendored Normal file
View File

@@ -0,0 +1,29 @@
### IntelliJ IDEA ###
out/
!**/src/main/**/out/
!**/src/test/**/out/
### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
### VS Code ###
.vscode/
### Mac OS ###
.DS_Store

8
hw2/.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

View File

@@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
</profile>
</component>

6
hw2/.idea/misc.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="openjdk-22" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

8
hw2/.idea/modules.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/hw2.iml" filepath="$PROJECT_DIR$/hw2.iml" />
</modules>
</component>
</project>

6
hw2/.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
</component>
</project>

6
hw2/CL34 Normal file
View File

@@ -0,0 +1,6 @@
16,hi
34,hi
64,hi
91,hi
195,hi
489,hi

2
hw2/Users Normal file
View File

@@ -0,0 +1,2 @@
hi,PUBLIC,R3JlZXRlcg==,aGVsbG8=
admin,ADMIN,YWRtaW4=,YWRtaW4=

1
hw2/googledoc.txt Normal file
View File

@@ -0,0 +1 @@
https://docs.google.com/document/d/1JvaiUiMjBGCoZ_znD07hcGHm9pu1VZYskcvsJmrsNv4/edit?usp=sharing

11
hw2/hw2.iml Normal file
View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@@ -0,0 +1,417 @@
import java.io.File;
import java.io.FileNotFoundException;
import java.io.PrintWriter;
import java.util.*;
import java.util.stream.Stream;
/**
* Main entrypoint for the reservation system.
* <p>
* The reservation system allows users to make, cancel, and view reservations.
* The system also allows administrators to view a manifest list of reservations.
* <p>
* The system loads reservations and users from files specified as command line arguments.
* If the files do not exist, the system creates them and creates an admin user with the username "admin" and password "admin".
* <p>
* The system saves reservations and users to the files when the admin exits the system.
* <p>
*/
public class ReservationSystem {
private static final Scanner stdin = new Scanner(System.in);
private static final String ADMIN_USERNAME = "admin";
private static final String ADMIN_PASSWORD = "admin";
private static Map<String, User> users = new HashMap<>();
private static ArrayList<String> seatReservations = new ArrayList<>(Stream.generate(() -> (String) null).limit(Seat.TOTAL_SEATS).toList());
public static void main(String[] args) {
if (args.length != 2) {
System.out.println("Usage: java ReservationSystem <reservationFilename> <userFilename>");
return;
}
loadFiles(args[0], args[1]);
System.out.println(users.get(ADMIN_USERNAME).toCsvString());
userSelectionMenu();
saveFiles(args[0], args[1]);
}
/**
* User selection menu: [A]dministrator, [P]ublic User
*/
private static void userSelectionMenu() {
String input = "";
while (!input.equals("A")) {
System.out.println(String.join(";\t ",
"User:\t [A]dministrator",
"[P]ublic User"
));
input = stdin.nextLine().toUpperCase();
switch (input) {
case "A" -> adminMenu(authenticateAdmin());
case "P" -> publicUserMenu(authenticatePublicUser());
default -> System.out.println("Invalid input");
}
}
}
/**
* Authenticate admin user with username and password.
*
* @return authenticated user
*/
private static User authenticateAdmin() {
System.out.print("Enter admin user id: ");
String username = stdin.nextLine();
System.out.print("Enter admin password: ");
String password = stdin.nextLine();
final User user = users.get(username);
if (user == null || !user.isAdmin() || !user.checkPassword(password)) {
System.out.println("Invalid user id or password");
return authenticateAdmin();
}
return users.get(username);
}
/**
* Authenticate or create public user with id, name, and password.
*
* @return authenticated or created user
*/
private static User authenticatePublicUser() {
System.out.println(String.join(";\t ",
"[S]ign up",
"[L]og in"
));
String input = stdin.nextLine().toUpperCase();
switch (input) {
case "S" -> {
System.out.print("Enter user id: ");
// can't have commas in a csv because I'm too lazy to do escaping
String id = stdin.nextLine().replace(",", "");
System.out.print("Enter name: ");
String name = stdin.nextLine();
System.out.print("Enter password: ");
String password = stdin.nextLine();
User user = new User(id, User.Type.PUBLIC, name, password);
users.put(id, user);
return user;
}
case "L" -> {
System.out.print("Enter user id: ");
String id = stdin.nextLine();
System.out.print("Enter user password: ");
String password = stdin.nextLine();
final User user = users.get(id);
if (user == null || user.isAdmin() || !user.checkPassword(password)) {
System.out.println("Invalid user id or password");
return authenticatePublicUser();
}
return users.get(id);
}
default -> {
System.out.println("Invalid input");
return authenticatePublicUser();
}
}
}
/**
* Admin menu: [M]anifest list, E[X]it
*
* @param user authenticated admin user
*/
private static void adminMenu(User user) {
String input = "";
while (true) {
System.out.println(String.join(";\t ",
"Show [M]anifest list",
"E[X]it"
));
input = stdin.nextLine().toUpperCase();
switch (input) {
case "M" -> showManifestList();
case "X" -> {
return;
}
default -> System.out.println("Invalid input");
}
}
}
/**
* Public user menu: [A]vailability, [R]eservation, [C]ancel Reservation, [V]iew Reservations, [D]one
*
* @param user authenticated public user
*/
private static void publicUserMenu(User user) {
String input = "";
while (true) {
System.out.println(String.join(";\t ",
"Check [A]vailability",
"Make [R]eservation",
"[C]ancel Reservation",
"[V]iew Reservations",
"[D]one"
));
input = stdin.nextLine().toUpperCase();
switch (input) {
case "A" -> checkAvailability();
case "R" -> makeReservation(user);
case "C" -> cancelReservation(user);
case "V" -> viewReservations(user);
case "D" -> {
return;
}
default -> System.out.println("Invalid input");
}
}
}
/**
* Show [M]anifest list: A manifest lists the reserved seats (only) and the corresponding passenger name.
*/
private static void showManifestList() {
System.out.println("First");
for (int seat = 0; seat < Seat.FIRST_CLASS_SEATS; seat++) {
if (seatReservations.get(seat) != null) printManifestReservation(seat);
}
System.out.println("\nEconomy Plus");
for (int seat = Seat.FIRST_CLASS_SEATS; seat < Seat.FIRST_CLASS_SEATS + Seat.ECONOMY_PLUS_SEATS; seat++) {
if (seatReservations.get(seat) != null) printManifestReservation(seat);
}
System.out.println("\nEconomy");
for (int seat = Seat.FIRST_CLASS_SEATS + Seat.ECONOMY_PLUS_SEATS; seat < Seat.TOTAL_SEATS; seat++) {
if (seatReservations.get(seat) != null) printManifestReservation(seat);
}
}
private static void printManifestReservation(Integer seat) {
User user = users.get(seatReservations.get(seat));
System.out.printf("%d%c: %s%n", Seat.getSeatRow(seat), Seat.getSeatColumn(seat), user.getName());
}
/**
* Check [A]vailability: This option shows a seat availability list which shows the available seats of each class along with the seat price.
*/
private static void checkAvailability() {
System.out.println("Seat Availability\n");
System.out.print("First (price: $1000/seat)");
for (int seat = 0; seat < Seat.FIRST_CLASS_SEATS; seat++) {
if (seat % Seat.ROW_SIZE == 0) {
System.out.println();
System.out.print(Seat.getSeatRow(seat) + ": ");
}
if (seatReservations.get(seat) == null) {
System.out.print(Seat.getSeatColumn(seat) + ", ");
}
}
System.out.print("\n\nEconomy Plus (price: $500/seat)");
for (int seat = Seat.FIRST_CLASS_SEATS; seat < Seat.FIRST_CLASS_SEATS + Seat.ECONOMY_PLUS_SEATS; seat++) {
if (seat % Seat.ROW_SIZE == 0) {
System.out.println();
System.out.print(Seat.getSeatRow(seat) + ": ");
}
if (seatReservations.get(seat) == null) {
System.out.print(Seat.getSeatColumn(seat) + ", ");
}
}
System.out.print("\n\nEconomy (price: $250/seat)");
for (int seat = Seat.FIRST_CLASS_SEATS + Seat.ECONOMY_PLUS_SEATS; seat < Seat.TOTAL_SEATS; seat++) {
if (seat % Seat.ROW_SIZE == 0) {
System.out.println();
System.out.print(Seat.getSeatRow(seat) + ": ");
}
if (seatReservations.get(seat) == null) {
System.out.print(Seat.getSeatColumn(seat) + ", ");
}
}
System.out.print("\n\n");
}
/**
* Make [R]eservation: With this option, the user can make multiple reservations but one seat at a time. The user enters the seat number consisting of a number and a letter (e.g., 1K for a first-class seat). If the seat number is not valid (specifically, not an existing seat number in the plane), the system asks for another one until the user enters a valid one. If the seat is not available, the system prompts an error and asks for another seat. If the seat is available, the system prompts the seat number, the type of service corresponding to this seat, and the price, and asks for the user's confirmation. If the user confirms the reservation, the seat will be reserved. If not, the reservation request will be void. The system asks if the user wants to make another one or not and proceeds with the user's request.
*
* @param user
*/
private static void makeReservation(User user) {
System.out.print("Enter seat number: ");
String seat = stdin.nextLine().toUpperCase();
try {
int seatNumber = Seat.getSeatNumber(seat);
if (seatReservations.get(seatNumber) != null) {
System.out.println("Seat is already reserved");
makeReservation(user);
return;
}
System.out.printf("Seat: %s\n", seat);
System.out.printf("Service: %s, $%d\n", Seat.getServiceClass(seatNumber), Seat.getSeatPrice(seatNumber));
System.out.println("Confirm reservation? [Y/N]");
String confirm = stdin.nextLine().toUpperCase();
while (!confirm.equals("Y") && !confirm.equals("N")) {
System.out.println("Invalid input");
System.out.println("Confirm reservation? [Y/N]");
confirm = stdin.nextLine().toUpperCase();
}
switch (confirm) {
case "Y" -> {
seatReservations.set(seatNumber, user.getId());
System.out.println("Reservation confirmed");
}
case "N" -> System.out.println("Reservation cancelled");
}
System.out.println("Make another reservation? [Y/N]");
String another = stdin.nextLine().toUpperCase();
while (!another.equals("Y") && !another.equals("N")) {
System.out.println("Invalid input");
System.out.println("Make another reservation? [Y/N]");
another = stdin.nextLine().toUpperCase();
}
if (another.equals("Y")) {
makeReservation(user);
}
} catch (IllegalArgumentException e) {
System.out.println("Invalid seat number");
makeReservation(user);
}
}
/**
* [C]ancel Reservation: With this option, the system first shows all the seats the user reserved. The user can cancel multiple seat reservations but one seat at a time. The user is supposed to enter a seat number from the list. If the user enters an unlisted number, the system asks for another one until the user enters one of the listed numbers.
*
* @param user
*/
private static void cancelReservation(User user) {
System.out.println("Your reservations:");
for (int seat = 0; seat < seatReservations.size(); seat++) {
if (seatReservations.get(seat) != null && seatReservations.get(seat).equals(user.getId())) {
System.out.printf("%d%c, %s, $%d\n", Seat.getSeatRow(seat), Seat.getSeatColumn(seat), Seat.getServiceClass(seat), Seat.getSeatPrice(seat));
}
}
System.out.print("Enter seat number to cancel: ");
String seat = stdin.nextLine().toUpperCase();
try {
int seatNumber = Seat.getSeatNumber(seat);
if (seatReservations.get(seatNumber) == null || !seatReservations.get(seatNumber).equals(user.getId())) {
System.out.println("Seat is not reserved by you");
cancelReservation(user);
return;
}
seatReservations.set(seatNumber, null);
System.out.println("Reservation cancelled");
System.out.println("Cancel another reservation? [Y/N]");
String another = stdin.nextLine().toUpperCase();
while (!another.equals("Y") && !another.equals("N")) {
System.out.println("Invalid input");
System.out.println("Cancel another reservation? [Y/N]");
another = stdin.nextLine().toUpperCase();
}
if (another.equals("Y")) {
cancelReservation(user);
}
} catch (IllegalArgumentException e) {
System.out.println("Invalid seat number");
cancelReservation(user);
}
}
/**
* [V]iew Reservations: It shows all reservations made by the current transaction. It doesn't show any cancelled reservation. The following format is suggested:
* <p>
* Name: Bill Smith
* Seats: 2I $1000, 17J $500, 42k $250
* Total Balance Due: $1750
*
* @param user
*/
private static void viewReservations(User user) {
System.out.printf("Name: %s\n Seats: ", user.getName());
int totalBalanceDue = 0;
for (int seat = 0; seat < seatReservations.size(); seat++) {
if (seatReservations.get(seat) != null && seatReservations.get(seat).equals(user.getId())) {
System.out.printf("%d%c $%d, ", Seat.getSeatRow(seat), Seat.getSeatColumn(seat), Seat.getSeatPrice(seat));
totalBalanceDue += Seat.getSeatPrice(seat);
}
}
System.out.printf("\nTotal Balance Due: $%d\n", totalBalanceDue);
}
/**
* Load reservations and users from files.
* If the files do not exist, create them and create an admin user with the username "admin" and password "admin".
*
* @param reservationFilename reservation file name
* @param userFilename user file name
*/
private static void loadFiles(String reservationFilename, String userFilename) {
// check if files exist
File reservationFile = new File(reservationFilename);
File userFile = new File(userFilename);
if (!reservationFile.isFile() || !userFile.isFile()) {
users.put(ADMIN_USERNAME, new User(ADMIN_USERNAME, User.Type.ADMIN, ADMIN_USERNAME, ADMIN_PASSWORD));
try {
reservationFile.createNewFile();
userFile.createNewFile();
try (PrintWriter writer = new PrintWriter(userFile)) {
users.values().forEach(user -> writer.println(user.toCsvString()));
}
System.out.println(reservationFilename + " and " + userFilename + " are now created.");
System.out.println("Admin user created with username: \"" + ADMIN_USERNAME + "\" and password: \"" + ADMIN_PASSWORD + "\"");
} catch (Exception e) {
System.out.println("Error creating files");
return;
}
return;
}
// load reservations from reservationFile
try (Scanner scanner = new Scanner(new File(reservationFilename))) {
while (scanner.hasNextLine()) {
String line = scanner.nextLine();
String[] parts = line.split(",");
int seatNumber = Integer.parseInt(parts[0]);
String passengerId = parts[1];
seatReservations.set(seatNumber, passengerId);
}
} catch (FileNotFoundException e) {
System.out.println(reservationFilename + " not found");
}
// load users from userFile
try (Scanner scanner = new Scanner(new File(userFilename))) {
while (scanner.hasNextLine()) {
String line = scanner.nextLine();
User user = User.fromCsvString(line);
users.put(user.getId(), user);
}
} catch (FileNotFoundException e) {
System.out.println(userFilename + " not found");
}
System.out.println("Existing Reservations and Users are loaded.");
}
/**
* Save reservations and users to files.
*
* @param reservationFilename reservation file name
* @param userFilename user file name
*/
private static void saveFiles(String reservationFilename, String userFilename) {
try (PrintWriter reservationWriter = new PrintWriter(reservationFilename)) {
for (int seatNumber = 0; seatNumber < seatReservations.size(); seatNumber++) {
if (seatReservations.get(seatNumber) != null) {
reservationWriter.println(Seat.toCsvString(seatNumber, seatReservations.get(seatNumber)));
}
}
} catch (FileNotFoundException e) {
System.out.println("Error saving reservations");
}
try (PrintWriter userWriter = new PrintWriter(userFilename)) {
users.values().forEach(user -> userWriter.println(user.toCsvString()));
} catch (FileNotFoundException e) {
System.out.println("Error saving users");
}
System.out.printf("Saved reservations to %s and users to %s%n", reservationFilename, userFilename);
}
}

124
hw2/src/Seat.java Normal file
View File

@@ -0,0 +1,124 @@
/**
* Seat class - static methods for seat number conversion, seat class and price calculation.
* row numbers are 1 to 50, column letters are 'A' to 'J'. This is represented by numbers 0 to 499.
* <p>
* Defines constants for total seats (500), row size (10), row count (50), first class seats (40),
* economy plus seats (110), economy seats (350), first class price ($1000), economy plus price ($500),
* economy price ($250).
*
* @author Yuri Tatishchev
* @version 0.1 2025-02-24
*/
public class Seat {
public static final int TOTAL_SEATS = 500;
public static final int ROW_SIZE = 10;
public static final int ROW_COUNT = TOTAL_SEATS / ROW_SIZE;
public static final int FIRST_CLASS_SEATS = 40;
public static final int ECONOMY_PLUS_SEATS = 110;
public static final int ECONOMY_SEATS = TOTAL_SEATS - FIRST_CLASS_SEATS - ECONOMY_PLUS_SEATS;
public static final int FIRST_CLASS_PRICE = 1000;
public static final int ECONOMY_PLUS_PRICE = 500;
public static final int ECONOMY_PRICE = 250;
public enum ServiceClass {
FIRST_CLASS,
ECONOMY_PLUS,
ECONOMY,
}
/**
* Get seat number from row and column.
*
* @param row 1 to 50 row number
* @param column 'A' to 'J' column letter
* @return seat number from 0 to 499
* @throws IllegalArgumentException if row or column is out of range
*/
public static int getSeatNumber(int row, char column) {
if (row < 1 || row > ROW_COUNT || column < 'A' || column >= 'A' + ROW_SIZE) {
throw new IllegalArgumentException("Invalid seat number");
}
return (row - 1) * 10 + (column - 'A');
}
/**
* Get seat number from seat string.
*
* @param seat seat string like "1A"
* @return seat number from 0 to 499
* @throws IllegalArgumentException if seat string is invalid
*/
public static int getSeatNumber(String seat) {
String seatNumber = seat.substring(0, seat.length() - 1);
char seatColumn = seat.charAt(seat.length() - 1);
return getSeatNumber(Integer.parseInt(seatNumber), seatColumn);
}
/**
* Get row number from seat number.
*
* @param seatNumber 0 to 499 seat number
* @return 1 to 50 row number
*/
public static int getSeatRow(int seatNumber) {
return (seatNumber / 10) + 1;
}
/**
* Get column letter from seat number.
*
* @param seatNumber 0 to 499 seat number
* @return 'A' to 'J' column letter
*/
public static char getSeatColumn(int seatNumber) {
return (char) ('A' + seatNumber % 10);
}
/**
* Get a csv formatted string from seat number and passenger id.
*
* @param seatNumber 0 to 499 seat number
* @param passengerId passenger id
* @return csv formatted string like "1A,user123"
*/
public static String toCsvString(int seatNumber, String passengerId) {
return String.format("%d,%s", seatNumber, passengerId);
}
/**
* Get the service class for a given seat number.
*
* @param seatNumber 0 to 499 seat number
* @return service class
*/
public static ServiceClass getServiceClass(int seatNumber) {
if (seatNumber < FIRST_CLASS_SEATS) {
return ServiceClass.FIRST_CLASS;
} else if (seatNumber < FIRST_CLASS_SEATS + ECONOMY_PLUS_SEATS) {
return ServiceClass.ECONOMY_PLUS;
}
return ServiceClass.ECONOMY;
}
/**
* Get the price for a given seat number, based on the service class.
*
* @param seatNumber 0 to 499 seat number
* @return price
*/
public static int getSeatPrice(int seatNumber) {
switch (getServiceClass(seatNumber)) {
case FIRST_CLASS -> {
return FIRST_CLASS_PRICE;
}
case ECONOMY_PLUS -> {
return ECONOMY_PLUS_PRICE;
}
case ECONOMY -> {
return ECONOMY_PRICE;
}
}
return 0;
}
}

97
hw2/src/User.java Normal file
View File

@@ -0,0 +1,97 @@
import java.util.Base64;
/**
* A class representing a user in the system.
* <p>
* A user has an id, a type, a name, and a password.
* The id is a unique identifier (string) for the user.
* The type can be either ADMIN or PUBLIC.
* The name and password are stored as base64-encoded strings.
*
* @author Yuri Tatishchev
* @version 0.1 2025-02-24
*/
public class User {
public enum Type {
ADMIN,
PUBLIC,
}
private String id;
private Type type;
private String name;
private String password;
public User(String id, Type type, String name, String password) {
this.id = id;
this.type = type;
this.name = name;
this.password = password;
}
/**
* Parse a User object from a CSV string.
*
* @param line The CSV string, should be in the format:
* <code>id,type,base64(name),base64(password)</code>.
* with escaped backslashes and commas.
* @return a User object
*/
public static User fromCsvString(String line) {
String[] parts = line.split(",");
String id = parts[0];
Type type = Type.valueOf(parts[1]);
String name = new String(Base64.getDecoder().decode(parts[2]));
String password = new String(Base64.getDecoder().decode(parts[3]));
return new User(id, type, name, password);
}
/**
* Get a CSV formatted string from the user object.
*
* @return a CSV formatted string in the format:
* <code>id,type,base64(name),base64(password)</code>.
*/
public String toCsvString() {
// don't forget to escape commas in name and password
String csvName = Base64.getEncoder().encodeToString(name.getBytes());
String csvPassword = Base64.getEncoder().encodeToString(password.getBytes());
return String.join(",", id, type.toString(), csvName, csvPassword);
}
public Type getType() {
return type;
}
public String getId() {
return id;
}
public String getName() {
return name;
}
public String getPassword() {
return password;
}
/**
* Check if the given password matches the user's password.
*
* @param password the inputted password to check
* @return true if the password matches, false otherwise
*/
public boolean checkPassword(String password) {
return this.password.equals(password);
}
/**
* Check if the user is an admin.
*
* @return true if the user is an admin, false otherwise
*/
public boolean isAdmin() {
return type == Type.ADMIN;
}
}

29
hw3/.gitignore vendored Normal file
View File

@@ -0,0 +1,29 @@
### IntelliJ IDEA ###
out/
!**/src/main/**/out/
!**/src/test/**/out/
### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
### VS Code ###
.vscode/
### Mac OS ###
.DS_Store

8
hw3/.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

View File

@@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
</profile>
</component>

6
hw3/.idea/misc.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="openjdk-22" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

8
hw3/.idea/modules.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/hw3.iml" filepath="$PROJECT_DIR$/hw3.iml" />
</modules>
</component>
</project>

6
hw3/.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
</component>
</project>

79
hw3/README.md Normal file
View File

@@ -0,0 +1,79 @@
## Objectives
Design and implement a desktop-based photo album manager using Java Swing. The application should demonstrate the use of the Iterator pattern, the MVC pattern, and the Strategy pattern to allow flexible behavior for sorting and displaying images.
## Problem Statement
Create a photo album manager application that allows users to:
- Add, view, and delete photos in the album.
- Navigate through the album using next/previous buttons.
- Display the photos in three different sorting orders by name, date added, and file size.
- The sorting should be implemented using the Strategy pattern, allowing different sorting strategies to be selected.
- The album should be iterated through using the Iterator pattern.
- The application should follow the MVC (Model-View-Controller) pattern to separate business logic, user interface, and control flow.
## Functional Requirements
- Photo Management:
- The program manages real images.
- Users should be able to add new photos (with a name, file path, and date).
- Users can delete photos by name from the album.
- Display a list of photos (name and thumbnail preview).
- Display the current photo.
- Navigation:
- The "Next" and "Previous" buttons change the current photo to the next photo and the previous photo, respectively. The next and previous photo are determined by the current sorting order of the list of photos.
- The application must implement an Iterator to traverse the photo collection.
- Sorting Photos:
- Users can sort the list of photos by name, date added, or file size.
- The sorting behavior should be dynamic and implemented using the Strategy pattern.
- The system should handle the following edge cases: empty photo albums (e.g. can't delete from an empty photo album) and non-existent file paths.
## Use of Patterns
### MVC
- Model (`PhotoAlbumModel`) Represents the photo collection and the current state of the album. Notifies the view when there is a change in the photo collection (by adding or deleting). Photo class contains attributes like name, file path, and date added.
- View (`PhotoAlbumView`) A Swing-based UI that displays the up-to-date list of photos in the order of date by default and the current photo.
- Controller (`PhotoAlbumController`) Provides controls (buttons for adding, deleting, sorting, navigating). Has buttons like "Add Photo", "Delete Photo", "Next", "Previous", and "Sort By" (Name/Date/Size). Responds to user actions and modifies the model accordingly. Manages the connection between user input (e.g., button clicks) and the photo album (model).
### Iterator Pattern
Implement an AlbumIterator class (class that implements the AlbumIterator interface) to iterate over the photos in the album.
### Strategy Pattern
Create three different sorting strategies, specifically SortByName, SortByDate, SortBySize for sorting the photos. The user can select different strategies dynamically to change the sorting order of the photos.
## Rough Layout of the User Interface
You should adhere to the layout provided below as closely as possible, but some reasonable flexibility is allowed.
![Photo Manager](photomanager.png)
## Submission
Submit hw3.zip through the course web site. The file hw3.zip should contain the following:
- All source files
- PhotoAlbumApp.java (with main), Photo.java, PhotoAlbumController.java, PhotoAlbumModel.java, PhotoAlbumView.java, SortByDate.java, SortByName.java, SortBySize.java, SortingStrategy.java.
- Put javadoc comments in the source codes.
- Remove all package statements from the source files.
- All .java files (not .class files)
- googledoc.txt: In this text file, specify a link to your google doc, titled as CS151_HW3_YOUR ID. Be sure to make this document available to ANYONE WHO HAS THE LINK (There will be point deductions if this requirement is not met.) The google doc should not be modified after the due date . The followings are required to present in the google document.
- Screen 1: Initial screen without any photos.
- Screen 2: After adding the first photo, the list and the current photo change.
- Screen 3: After adding the second photo, the list updates. Photos are displayed in order of date, with the first photo in the list set as the current photo.
- Screen 4: After adding the third photo, the list updates. Photos are displayed in order of date, with the first photo in the list set as the current photo.
- Screen 5: After sorting the list by name, both the list and the current photo change.
- Screen 6: After sorting the list by size, both the list and the current photo change.
- Screen 7: After sorting the list by date, both the list and the current photo change.
- Screen 8: After the next button is clicked, the list remains the same but the current photo changes.
- Screen 9: After the previous button is clicked, the list remains the same but the current photo changes.
- Screen 10: After deleting one of the photos by name. The list changes. The current photo only changes if the deleted photo was the current one. If so, the first photo in the updated list becomes the new current photo.
- Show the correct use of patterns (copy and paste the specified code from your Java programs and label them clearly):
- MVC Pattern
- Controller: The action listener code for the "Add" button, which calls the mutator of the model.
- Model: The data structure and the mutator that updates the data structure and notifies the view.
- View: The code that calls the model's accessor and triggers a repaint.
- Strategy Pattern: A class diagram documenting the use of the Strategy pattern. One interface and three concrete classes are expected.
- Iterator Pattern: The Iterator class. (That is, a class that implements the AlbumnIterator class.)

1
hw3/googledoc.txt Normal file
View File

@@ -0,0 +1 @@
https://docs.google.com/document/d/1iuiCyrvFca-Ygdt_7QWl_Ie5XfvbfI1GYd8HVLfGGH0/edit?usp=sharing

11
hw3/hw3.iml Normal file
View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

BIN
hw3/photomanager.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 KiB

View File

@@ -0,0 +1,31 @@
import java.util.Iterator;
/**
* Interface for album iterators.
*/
public interface AlbumIterator extends Iterator<Photo> {
boolean hasNext(); // to check if there is a next element
/**
* Returns whether there is a previous photo in the album.
*
* @return {@code true} if there is a previous photo, {@code false} otherwise
*/
boolean hasPrevious(); // to check if there is a previous element
/**
* Returns the current photo in the album.
*
* @return the current photo
*/
Photo current(); // to get the photo at the current position
Photo next(); // to advance the iterator to the next position
/**
* Moves to and returns the previous photo in the album.
*
* @return the previous photo
*/
Photo previous(); // to advance the iterator to the previous position
}

20
hw3/src/Photo.java Normal file
View File

@@ -0,0 +1,20 @@
import java.util.Date;
/**
* A record class that represents a photo.
* <p>
* A photo has a:
* <ul>
* <li>name
* <li>file path
* <li>date added
* <li>file size
* </ul>
* <p>
* A photo is immutable.
*
* @author Yuri Tatishchev
* @version 0.1 2022-03-26
*/
public record Photo(String name, String filePath, Date dateAdded, long fileSize) {
}

View File

@@ -0,0 +1,22 @@
/**
* Photo Album Manager application entry point.
* <p>
* This class creates the model, view, and controller objects and initializes the view.
*
* @author Yuri Tatishchev
* @version 0.1 2025-03-26
*/
public class PhotoAlbumApp {
public static void main(String[] args) {
// Create MVC components
PhotoAlbumModel model = new PhotoAlbumModel();
PhotoAlbumView view = new PhotoAlbumView();
PhotoAlbumController controller = new PhotoAlbumController(model, view);
// Initialize view with the controller
view.setController(controller);
// Display the main window
javax.swing.SwingUtilities.invokeLater(() -> view.setVisible(true));
}
}

View File

@@ -0,0 +1,126 @@
import javax.swing.*;
import javax.swing.filechooser.FileNameExtensionFilter;
import java.io.File;
import java.util.Date;
/**
* A controller that manages interactions between the photo album model and view.
* <p>
* This class handles user actions from the view, such as adding and deleting photos,
* navigating through the album, and changing the sorting strategy.
* It acts as an intermediary between the {@link PhotoAlbumModel} and {@link PhotoAlbumView},
* translating user interface events into model operations.
*
* @author Yuri Tatishchev
* @version 0.1 2025-03-26
* @see PhotoAlbumModel
* @see PhotoAlbumView
* @see Photo
*/
public class PhotoAlbumController {
private final PhotoAlbumModel model;
private final PhotoAlbumView view;
/**
* Constructs a new photo album controller.
*
* @param model the photo album model to control
* @param view the view to handle user interactions
*/
public PhotoAlbumController(PhotoAlbumModel model, PhotoAlbumView view) {
this.model = model;
this.view = view;
}
/**
* Returns the photo album model being controlled.
*
* @return the photo album model
*/
public PhotoAlbumModel getModel() {
return model;
}
/**
* Handles the addition of a new photo to the album.
* Opens a file chooser dialog for selecting an image file and
* prompts for a photo name.
*/
public void handleAddPhoto() {
JFileChooser fileChooser = new JFileChooser();
fileChooser.setFileFilter(new FileNameExtensionFilter(
"Image files",
"jpg", "jpeg", "png", "gif"
));
if (fileChooser.showOpenDialog(view) == JFileChooser.APPROVE_OPTION) {
File file = fileChooser.getSelectedFile();
String name = JOptionPane.showInputDialog(view, "Enter photo name:");
if (name != null && !name.trim().isEmpty()) {
Photo photo = new Photo(
name,
file.getAbsolutePath(),
new Date(),
file.length()
);
model.addPhoto(photo);
}
}
}
/**
* Handles the deletion of a photo from the album.
* Prompts for the name of the photo to delete.
*/
public void handleDeletePhoto() {
String name = JOptionPane.showInputDialog(view, "Enter photo name to delete:");
if (name != null && !name.trim().isEmpty()) {
model.deletePhoto(name);
}
}
/**
* Handles navigation to the next photo in the album.
* Moves to the next photo if one exists.
*/
public void handleNext() {
if (model.hasNext()) {
model.next();
}
}
/**
* Handles navigation to the previous photo in the album.
* Moves to the previous photo if one exists.
*/
public void handlePrevious() {
if (model.hasPrevious()) {
model.previous();
}
}
/**
* Handles changing the sorting strategy for photos in the album.
*
* @param strategy the sorting strategy to use:
* <ul>
* <li>0 for name-based sorting,
* <li>1 for date-based sorting,
* <li>2 for size-based sorting
* </ul>
*/
public void handleSort(int strategy) {
switch (strategy) {
case 0:
model.setSortingStrategy(new SortByName());
break;
case 1:
model.setSortingStrategy(new SortByDate());
break;
case 2:
model.setSortingStrategy(new SortBySize());
break;
}
}
}

View File

@@ -0,0 +1,181 @@
import java.util.*;
/**
* A model that represents a photo album.
* <p>
* This class is responsible for managing the photos in the album, as well as
* the sorting strategy used to sort the photos.
* The model notifies its listeners when the model changes.
* It also provides methods to add and delete photos,
* as well as to navigate through the photos.
*
* @author Yuri Tatishchev
* @version 0.1 2025-03-26
* @see Photo
* @see SortingStrategy
* @see AlbumIterator
* @see PhotoIterator
* @see ModelChangeListener
*/
public class PhotoAlbumModel {
private List<Photo> photos;
private SortingStrategy<Photo> sortingStrategy;
private final List<ModelChangeListener> listeners;
private AlbumIterator iterator;
/**
* A listener interface for model changes.
* The method {@link #onModelChanged()} is called when the model changes.
*/
public interface ModelChangeListener {
/**
* Called when the model changes.
*/
void onModelChanged();
}
public PhotoAlbumModel() {
photos = new ArrayList<>();
listeners = new ArrayList<>();
iterator = new PhotoIterator(photos);
}
/**
* Adds a photo to the album.
* Resets the iterator and notifies the listeners.
*
* @param photo the photo to add
*/
public void addPhoto(Photo photo) {
photos.add(photo);
sortPhotos();
notifyListeners();
}
/**
* Deletes a photo from the album by name.
* If the deleted photo is the current photo or the album is empty,
* the iterator is reset.
*
* @param name the name of the photo to delete
*/
public void deletePhoto(String name) {
Photo currentPhoto = iterator.current();
photos.removeIf(photo -> photo.name().equals(name));
if (photos.isEmpty() || (currentPhoto != null && currentPhoto.name().equals(name))) {
iterator = new PhotoIterator(photos);
}
notifyListeners();
}
/**
* Sets the sorting strategy for the photos.
* Sorts the photos using the new strategy and resets the iterator.
*
* @param strategy the sorting strategy to set
*/
public void setSortingStrategy(SortingStrategy<Photo> strategy) {
this.sortingStrategy = strategy;
sortPhotos();
iterator = new PhotoIterator(photos);
notifyListeners();
}
private void sortPhotos() {
if (sortingStrategy != null) {
photos = sortingStrategy.sort(photos);
iterator = new PhotoIterator(photos);
}
}
/**
* Adds a listener for model changes.
*
* @param listener the listener to add
*/
public void addListener(ModelChangeListener listener) {
listeners.add(listener);
}
/**
* Notifies all listeners that the model has changed,
* calling the {@link ModelChangeListener#onModelChanged()} method.
*/
private void notifyListeners() {
for (ModelChangeListener listener : listeners) {
listener.onModelChanged();
}
}
/**
* Returns an unmodifiable list of photos in the album.
*
* @return an unmodifiable list of photos
*/
public List<Photo> getPhotos() {
return Collections.unmodifiableList(photos);
}
/**
* Returns the photo in the album that the iterator is currently pointing to.
*
* @return the current photo
*/
public Photo getCurrentPhoto() {
try {
return iterator.current();
} catch (NoSuchElementException e) {
return null;
}
}
/**
* Returns whether there is a next photo in the album.
*
* @return {@code true} if there is a next photo, {@code false} otherwise
*/
public boolean hasNext() {
return iterator.hasNext();
}
/**
* Returns whether there is a previous photo in the album.
*
* @return {@code true} if there is a previous photo, {@code false} otherwise
*/
public boolean hasPrevious() {
return iterator.hasPrevious();
}
/**
* Advances the iterator to the next photo in the album.
* Notifies the listeners.
*
* @return the next photo or {@code null} if there is no next photo
*/
public Photo next() {
try {
Photo next = iterator.next();
notifyListeners();
return next;
} catch (NoSuchElementException e) {
return null;
}
}
/**
* Advances the iterator to the previous photo in the album.
* Notifies the listeners.
*
* @return the previous photo or {@code null} if there is no previous photo
*/
public Photo previous() {
try {
Photo prev = iterator.previous();
notifyListeners();
return prev;
} catch (NoSuchElementException e) {
return null;
}
}
}

312
hw3/src/PhotoAlbumView.java Normal file
View File

@@ -0,0 +1,312 @@
import javax.swing.*;
import java.awt.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* A view that displays a photo album user interface.
* <p>
* This class is responsible for displaying the photo album's graphical interface,
* including the list of photos, current photo display, and control buttons.
* The view implements the {@link PhotoAlbumModel.ModelChangeListener} interface
* to receive notifications of model changes.
* <p>
* The view provides user interface components for:
* <ul>
* <li>Displaying a list of photos with thumbnails and details
* <li>Showing the currently selected photo
* <li>Navigation controls (previous/next)
* <li>Photo management (add/delete)
* <li>Sorting options
* </ul>
*
* @author Yuri Tatishchev
* @version 0.1 2025-03-26
* @see PhotoAlbumModel
* @see PhotoAlbumController
* @see Photo
*/
public class PhotoAlbumView extends JFrame implements PhotoAlbumModel.ModelChangeListener {
private PhotoAlbumModel model;
private JList<String> photoList;
private DefaultListModel<String> listModel;
private JLabel currentPhotoLabel;
private JButton addButton;
private JButton deleteButton;
private JButton previousButton;
private JButton nextButton;
private JComboBox<String> sortingCombo;
public PhotoAlbumView() {
initializeComponents();
setupLayout();
}
/**
* Initializes all UI components of the photo album view.
* <p>
* Sets up the frame properties, creates buttons, list components,
* and initializes them with default states.
*/
private void initializeComponents() {
setTitle("Photo Album Manager");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(800, 600);
listModel = new DefaultListModel<>();
photoList = new JList<>(listModel);
currentPhotoLabel = new JLabel("No photo selected", SwingConstants.CENTER);
addButton = new JButton("Add Photo");
deleteButton = new JButton("Delete Photo");
previousButton = new JButton("Previous");
nextButton = new JButton("Next");
// Disable navigation buttons by default
previousButton.setEnabled(false);
nextButton.setEnabled(false);
deleteButton.setEnabled(false);
String[] sortOptions = {"Sort by Name", "Sort by Date", "Sort by Size"};
sortingCombo = new JComboBox<>(sortOptions);
listModel = new DefaultListModel<>();
photoList = new JList<>(listModel);
photoList.setEnabled(false);
photoList.setCellRenderer(new PhotoListCellRenderer());
photoList.setFixedCellHeight(60); // Accommodate thumbnails
}
/**
* Sets up the layout of the photo album view.
* <p>
* Arranges the UI components using BorderLayout with:
* <ul>
* <li>Photo list in the WEST
* <li>Current photo display in the CENTER
* <li>Control buttons in the SOUTH
* </ul>
*/
private void setupLayout() {
setLayout(new BorderLayout());
// Left panel with photo list
JScrollPane listScrollPane = new JScrollPane(photoList);
listScrollPane.setPreferredSize(new Dimension(200, 0));
add(listScrollPane, BorderLayout.WEST);
// Center panel with current photo
JPanel centerPanel = new JPanel(new BorderLayout());
centerPanel.add(currentPhotoLabel, BorderLayout.CENTER);
add(centerPanel, BorderLayout.CENTER);
// Bottom panel with controls
JPanel controlPanel = new JPanel();
controlPanel.add(previousButton);
controlPanel.add(nextButton);
controlPanel.add(addButton);
controlPanel.add(deleteButton);
controlPanel.add(sortingCombo);
add(controlPanel, BorderLayout.SOUTH);
}
/**
* Sets the controller for the photo album view.
* <p>
* Attaches the controller to the view and sets up event listeners
* for the control buttons and sorting combo box.
*
* @param controller the controller to set
*/
public void setController(PhotoAlbumController controller) {
this.model = controller.getModel();
model.addListener(this);
addButton.addActionListener(e -> controller.handleAddPhoto());
deleteButton.addActionListener(e -> controller.handleDeletePhoto());
nextButton.addActionListener(e -> controller.handleNext());
previousButton.addActionListener(e -> controller.handlePrevious());
sortingCombo.addActionListener(e -> controller.handleSort(sortingCombo.getSelectedIndex()));
// Select default sorting option
sortingCombo.setSelectedIndex(1);
}
@Override
public void onModelChanged() {
updatePhotoList();
updateCurrentPhoto();
updateNavigationButtons();
}
/**
* Updates the list of photos displayed in the sidebar.
* <p>
* Clears the current list model and populates it with
* names of photos from the model.
*/
private void updatePhotoList() {
listModel.clear();
List<Photo> photos = model.getPhotos();
for (Photo photo : photos) {
listModel.addElement(photo.name());
}
}
/**
* Updates the display of the current photo in the main view area.
* <p>
* If a photo is selected, loads and displays its image.
* If loading fails, displays an error message.
* If no photo is selected, displays a default message.
*/
private void updateCurrentPhoto() {
Photo current = model.getCurrentPhoto();
if (current != null) {
ImageIcon icon = loadImage(current.filePath());
if (icon != null) {
currentPhotoLabel.setIcon(icon);
currentPhotoLabel.setText("");
} else {
currentPhotoLabel.setIcon(null);
currentPhotoLabel.setText("Unable to load image");
}
} else {
currentPhotoLabel.setIcon(null);
currentPhotoLabel.setText("No photo selected");
}
}
/**
* Updates the enabled state of navigation buttons.
* <p>
* Enables or disables the previous/next buttons based on
* the current position in the photo album.
* Enables the delete button only when the album is not empty.
*/
private void updateNavigationButtons() {
previousButton.setEnabled(model.hasPrevious());
nextButton.setEnabled(model.hasNext());
deleteButton.setEnabled(model.getCurrentPhoto() != null);
}
/**
* Loads and scales an image from the given path.
* <p>
* Creates a scaled version of the image suitable for the main display area.
*
* @param path the file path of the image to load
* @return a scaled ImageIcon, or null if loading fails
*/
private ImageIcon loadImage(String path) {
try {
ImageIcon icon = new ImageIcon(path);
Image img = icon.getImage();
Image scaledImg = img.getScaledInstance(400, 300, Image.SCALE_SMOOTH);
return new ImageIcon(scaledImg);
} catch (Exception e) {
return null;
}
}
/**
* A custom list cell renderer for displaying photos with thumbnails and details.
* <p>
* This inner class renders each photo in the list with:
* <ul>
* <li>A thumbnail image
* <li>The photo name
* <li>Additional details (date and file size)
* </ul>
* <p>
* It also implements thumbnail caching to improve performance.
*/
private class PhotoListCellRenderer extends JPanel implements ListCellRenderer<String> {
private final JLabel imageLabel = new JLabel();
private final JLabel textLabel = new JLabel();
private final JLabel detailsLabel = new JLabel();
private final Map<String, ImageIcon> thumbnailCache = new HashMap<>();
public PhotoListCellRenderer() {
setLayout(new BorderLayout(5, 0));
// Left side - image
imageLabel.setPreferredSize(new Dimension(50, 50));
add(imageLabel, BorderLayout.WEST);
// Center - name and details
JPanel textPanel = new JPanel(new GridLayout(2, 1));
textPanel.setOpaque(false);
textLabel.setFont(textLabel.getFont().deriveFont(Font.BOLD));
detailsLabel.setForeground(Color.GRAY);
detailsLabel.setFont(detailsLabel.getFont().deriveFont(10.0f));
textPanel.add(textLabel);
textPanel.add(detailsLabel);
add(textPanel, BorderLayout.CENTER);
setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
setOpaque(true);
}
@Override
public Component getListCellRendererComponent(JList<? extends String> list,
String name,
int index,
boolean isSelected,
boolean cellHasFocus
) {
textLabel.setText(name);
Photo photo = model.getPhotos().get(index);
ImageIcon thumbnail = thumbnailCache.computeIfAbsent(photo.filePath(), this::loadThumbnail);
// ImageIcon thumbnail = loadThumbnail(photo.filePath());
imageLabel.setIcon(thumbnail);
// Format date
String date = String.format("%tF", photo.dateAdded());
// Format file size
String size = formatFileSize(photo.fileSize());
detailsLabel.setText(date + "" + size);
return this;
}
/**
* Formats a file size in bytes to a human-readable string.
* <p>
* Converts the size to kilobytes or megabytes with one decimal place.
*
* @param size the file size in bytes
* @return a formatted string with the size in KB or MB
*/
private String formatFileSize(long size) {
if (size < 1024 * 1024) {
return String.format("%.1f KB", size / 1024.0);
} else {
return String.format("%.1f MB", size / (1024.0 * 1024));
}
}
/**
* Loads and scales a thumbnail image from the given path.
* <p>
* Creates a scaled version of the image suitable for the list display.
*
* @param path the file path of the image to load
* @return a scaled ImageIcon, or null if loading fails
*/
private ImageIcon loadThumbnail(String path) {
try {
ImageIcon icon = new ImageIcon(path);
Image img = icon.getImage();
Image scaledImg = img.getScaledInstance(50, 50, Image.SCALE_AREA_AVERAGING);
return new ImageIcon(scaledImg);
} catch (Exception e) {
return null;
}
}
}
}

View File

@@ -0,0 +1,75 @@
import java.util.List;
import java.util.NoSuchElementException;
/**
* An iterator implementation for navigating through photos in a photo album.
* <p>
* This class provides functionality to traverse a list of photos in both
* forward and backward directions. It maintains a current position and
* provides methods to check for the existence of next and previous photos,
* as well as to retrieve the current, next, and previous photos.
* <p>
* The iterator throws {@link NoSuchElementException} when attempting to
* access elements beyond the bounds of the photo list.
*
* @author Yuri Tatishchev
* @version 0.1 2025-03-26
* @see Photo
* @see AlbumIterator
* @see NoSuchElementException
*/
public class PhotoIterator implements AlbumIterator {
private final List<Photo> photos;
private int currentPosition;
/**
* Constructs a new photo iterator for the given list of photos.
* The iterator starts at position 0.
*
* @param photos the list of photos to iterate over
*/
public PhotoIterator(List<Photo> photos) {
this.photos = photos;
this.currentPosition = 0;
}
@Override
public boolean hasNext() {
return currentPosition < photos.size() - 1;
}
@Override
public boolean hasPrevious() {
return currentPosition > 0;
}
@Override
public Photo current() {
if (photos.isEmpty()) {
throw new NoSuchElementException("The photo album is empty.");
}
return photos.get(currentPosition);
}
/**
* @throws NoSuchElementException if there is no next photo
*/
@Override
public Photo next() {
if (!hasNext()) {
throw new NoSuchElementException("No next photo in the album.");
}
return photos.get(++currentPosition);
}
/**
* @throws NoSuchElementException if there is no previous photo
*/
@Override
public Photo previous() {
if (!hasPrevious()) {
throw new NoSuchElementException("No previous photo in the album.");
}
return photos.get(--currentPosition);
}
}

13
hw3/src/SortByDate.java Normal file
View File

@@ -0,0 +1,13 @@
import java.util.Comparator;
import java.util.List;
/**
* A sorting strategy that sorts photos by date.
*/
public class SortByDate implements SortingStrategy<Photo> {
@Override
public List<Photo> sort(List<Photo> photos) {
photos.sort(Comparator.comparing(Photo::dateAdded));
return photos;
}
}

13
hw3/src/SortByName.java Normal file
View File

@@ -0,0 +1,13 @@
import java.util.Comparator;
import java.util.List;
/**
* A sorting strategy that sorts photos by name.
*/
public class SortByName implements SortingStrategy<Photo> {
@Override
public List<Photo> sort(List<Photo> photos) {
photos.sort(Comparator.comparing(Photo::name));
return photos;
}
}

13
hw3/src/SortBySize.java Normal file
View File

@@ -0,0 +1,13 @@
import java.util.Comparator;
import java.util.List;
/**
* A sorting strategy that sorts photos by size.
*/
public class SortBySize implements SortingStrategy<Photo> {
@Override
public List<Photo> sort(List<Photo> photos) {
photos.sort(Comparator.comparing(Photo::fileSize));
return photos;
}
}

View File

@@ -0,0 +1,10 @@
import java.util.List;
/**
* Interface for sorting strategies.
*
* @param <T> the type of elements to be sorted
*/
public interface SortingStrategy<T> {
List<T> sort(List<T> photos);
}

29
midterm1/.gitignore vendored Normal file
View File

@@ -0,0 +1,29 @@
### IntelliJ IDEA ###
out/
!**/src/main/**/out/
!**/src/test/**/out/
### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
### VS Code ###
.vscode/
### Mac OS ###
.DS_Store

8
midterm1/.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

View File

@@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
</profile>
</component>

6
midterm1/.idea/misc.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="openjdk-22" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

8
midterm1/.idea/modules.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/midterm1.iml" filepath="$PROJECT_DIR$/midterm1.iml" />
</modules>
</component>
</project>

6
midterm1/.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
</component>
</project>

11
midterm1/midterm1.iml Normal file
View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@@ -0,0 +1,30 @@
/**
* Animation Program that draws and moves a simple shape (e.g. butterfly, kite, etc.) on a timer event.
*/
public class Animation {
public static void main(String[] args) {
javax.swing.SwingUtilities.invokeLater(() -> {
javax.swing.JFrame frame = new javax.swing.JFrame("Animation");
frame.setDefaultCloseOperation(javax.swing.JFrame.EXIT_ON_CLOSE);
frame.setSize(400, 400);
javax.swing.JPanel panel = new javax.swing.JPanel() {
@Override
protected void paintComponent(java.awt.Graphics g) {
super.paintComponent(g);
g.setColor(java.awt.Color.RED);
g.fillOval(getX(), getY(), 30, 30);
}
};
frame.add(panel);
javax.swing.Timer timer = new javax.swing.Timer(100, (e) -> {
panel.setLocation((panel.getX() + 1) % 20, (panel.getY() + 1) % 20);
panel.repaint();
});
timer.start();
frame.setVisible(true);
});
}
}

58
midterm1/src/C4Q14.java Normal file
View File

@@ -0,0 +1,58 @@
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
/**
* Write a program that shows a frame with three buttons labeled “Red”,
* “Green”, and “Blue”, and a label containing an icon showing a circle that is
* initially red. As the user clicks the buttons, the fill color of the circle should
* change. When you change the color, you need to invoke the repaint
* method on the label. The call to repaint ensures that the paintIcon method
* is called so that the icon can be repainted with the new color.
*/
public class C4Q14 {
public static void main(String[] args) {
javax.swing.SwingUtilities.invokeLater(() -> {
javax.swing.JFrame frame = new javax.swing.JFrame("C4Q14");
frame.setDefaultCloseOperation(javax.swing.JFrame.EXIT_ON_CLOSE);
frame.setSize(400, 400);
// frame.setLayout(new java.awt.BorderLayout());
javax.swing.JLabel label = new javax.swing.JLabel(new CircleIcon(java.awt.Color.RED, 300));
frame.add(label, java.awt.BorderLayout.CENTER);
javax.swing.JPanel panel = new javax.swing.JPanel();
panel.setLayout(new java.awt.FlowLayout());
javax.swing.JButton redButton = new javax.swing.JButton("Red");
redButton.addActionListener((e) -> {
label.setIcon(new CircleIcon(java.awt.Color.RED, 300));
label.repaint();
});
panel.add(redButton);
javax.swing.JButton greenButton = new javax.swing.JButton("Green");
greenButton.addActionListener((e) -> {
label.setIcon(new CircleIcon(java.awt.Color.GREEN, 300));
label.repaint();
});
greenButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
label.setIcon(new CircleIcon(java.awt.Color.GREEN, 300));
label.repaint();
}
});
panel.add(greenButton);
javax.swing.JButton blueButton = new javax.swing.JButton("Blue");
blueButton.addActionListener((e) -> {
label.setIcon(new CircleIcon(java.awt.Color.BLUE, 300));
label.repaint();
});
panel.add(blueButton);
// frame.add(panel, java.awt.BorderLayout.SOUTH);
frame.setVisible(true);
});
}
}

29
midterm1/src/C4Q6.java Normal file
View File

@@ -0,0 +1,29 @@
import java.util.ArrayList;
import java.util.Comparator;
public class C4Q6 {
public static void main(String[] args) {
ArrayList<String> a = new ArrayList<>(10);
a.add("a");
a.add("aa");
a.add("aaa");
a.add("aaaaa");
a.add("aaaa");
String max = maximum(a, (s1, s2) -> {
return Integer.compare(s1.length(), s2.length());
});
System.out.println(max);
}
public static String maximum(ArrayList<String> a, Comparator<String> c) {
String max = a.getFirst();
for (String s : a) {
if (c.compare(s, max) > 0) {
max = s;
}
}
return max;
}
}

21
midterm1/src/C4Q9.java Normal file
View File

@@ -0,0 +1,21 @@
import java.util.Arrays;
import java.util.stream.Stream;
public class C4Q9 {
public static void main(String[] args) {
String[] a = {"a", "aa", "aaa", "aaaa", "aaaaa"};
String[] res = filter(a, (s) -> {
return s.length() <= 3;
});
for (String s : res) {
System.out.println(s);
}
}
public static String[] filter(String[] a, Filter f) {
return Arrays.stream(a).filter((s) -> {
return f.accept(s);
}).toArray(String[]::new);
}
}

View File

@@ -0,0 +1,29 @@
import javax.swing.*;
import java.awt.*;
public class CircleIcon implements Icon {
private Color color;
private int size;
public CircleIcon(Color color, int size) {
this.color = color;
this.size = size;
}
public void paintIcon(Component c, Graphics g, int x, int y) {
g.setColor(color);
g.fillOval(x, y, size, size);
}
public int getIconWidth() {
return size;
}
public int getIconHeight() {
return size;
}
public void setColor(Color color) {
this.color = color;
}
}

3
midterm1/src/Filter.java Normal file
View File

@@ -0,0 +1,3 @@
public interface Filter {
boolean accept(String x);
}

View File

@@ -0,0 +1,24 @@
import javax.swing.*;
import java.awt.*;
import java.awt.geom.Ellipse2D;
import java.util.ArrayList;
/**
*
*/
public class MidtermQ1 {
public static void main(String[] args) {
}
}
class CirclePanel extends JPanel {
private Color color;
public void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
g2.setColor(color);
g2.draw(new Ellipse2D.Double(0, 0, 100, 100));
}
}