From 20c545063112c6c4c344960c642a8b4f1af8d899 Mon Sep 17 00:00:00 2001 From: Ksan Date: Thu, 10 Jul 2025 16:41:54 +0200 Subject: [PATCH] fixed buggs and added state saving --- .gitignore | 2 + build.gradle.kts | 1 + src/main/java/dev/ksan/DataStore.java | 97 ++++++++++++++----- src/main/java/dev/ksan/DataThread.java | 80 +++++++++++++--- src/main/java/dev/ksan/ETFScraper.java | 39 ++++++-- src/main/java/dev/ksan/Main.java | 42 ++------- src/main/java/dev/ksan/SubjectEntry.java | 22 ++++- src/main/java/dev/ksan/Subscription.java | 3 +- src/main/java/dev/ksan/User.java | 57 ++++-------- src/main/java/dev/ksan/Users.java | 114 +++++++++++++++++++++++ 10 files changed, 338 insertions(+), 119 deletions(-) create mode 100644 src/main/java/dev/ksan/Users.java diff --git a/.gitignore b/.gitignore index 3633b07..f245f76 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ .gradle +*.ser +*.json .idea/ # Ignore Gradle wrapper files (if you already have gradlew and gradlew.bat) gradle/wrapper/ diff --git a/build.gradle.kts b/build.gradle.kts index a907d12..e5d9d0a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -18,6 +18,7 @@ dependencies { implementation("net.sourceforge.htmlunit:htmlunit:2.70.0") testImplementation(platform("org.junit:junit-bom:5.10.0")) testImplementation("org.junit.jupiter:junit-jupiter") + implementation("com.fasterxml.jackson.core:jackson-databind:2.15.2") } tasks.test { diff --git a/src/main/java/dev/ksan/DataStore.java b/src/main/java/dev/ksan/DataStore.java index 5275819..28765f9 100644 --- a/src/main/java/dev/ksan/DataStore.java +++ b/src/main/java/dev/ksan/DataStore.java @@ -3,15 +3,44 @@ package dev.ksan; import java.io.*; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; -public class DataStore implements AutoCloseable { +public class DataStore{ public static Map subjectSubscriptions = new ConcurrentHashMap<>(); private static final String DATA_FILE = "subjects_data.ser"; - static{ + private static final long SAVE_INTERVAL_MS = 10000; + + private static final ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor(); + private static volatile boolean running = true; + static { loadSubjectsFromFile(); + + executorService.scheduleAtFixedRate(DataStore::saveSubjectsToFile, 0, SAVE_INTERVAL_MS, TimeUnit.MILLISECONDS); + + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + running = false; + saveSubjectsToFile(); + executorService.shutdown(); + })); } + public static void stop(){ + running = false; + executorService.shutdownNow(); + try { + if(!executorService.awaitTermination(3000, TimeUnit.MILLISECONDS)){ + executorService.shutdownNow(); + } + } catch (InterruptedException e) { + executorService.shutdownNow(); + throw new RuntimeException(e); + }finally { + saveSubjectsToFile(); + } + } public static void notifySubject(Subject subject, SubjectEntry entry) { if (subject != null && entry != null) { Object subscription = subjectSubscriptions.get(subject); @@ -31,7 +60,15 @@ public class DataStore implements AutoCloseable { } public static synchronized void subscribeUserToSubject(User user, Subject subject) { - if (user != null && subject != null && subjectSubscriptions.containsKey(subject)) { + if (user != null && subject != null) { + if(!subjectSubscriptions.containsKey(subject)){ + Subscription subscription = new Subscription(subject); + + subscription.subscribe(user); + subjectSubscriptions.put(subject,subscription); + user.addSubject(subject); + return; + } subjectSubscriptions.get(subject).subscribe(user); user.addSubject(subject); } @@ -45,31 +82,43 @@ public class DataStore implements AutoCloseable { } } } - private static void loadSubjectsFromFile() { - try (ObjectInputStream in = new ObjectInputStream(new FileInputStream(DATA_FILE))) { - subjectSubscriptions = (Map) in.readObject(); - for (Map.Entry entry : subjectSubscriptions.entrySet()) { - System.out.println("Subject " + entry.getKey() + " has notified " + entry.getValue()); - } - System.out.println("Loaded subjects from " + DATA_FILE); - try{ - Thread.sleep(1000); - }catch (InterruptedException e){} - } catch (IOException | ClassNotFoundException e) { - System.out.println("Error loading subjects data: " + e.getMessage()); - } - } + private static void saveSubjectsToFile() { - try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(DATA_FILE))) { - out.writeObject(subjectSubscriptions); - } catch (IOException e) { - System.out.println("Error saving subjects data: " + e.getMessage()); + if (running) { + try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(DATA_FILE))) { + oos.writeObject(subjectSubscriptions); + System.out.println("DataStore state saved to file."); + } catch (IOException e) { + System.err.println("Failed to save DataStore state: " + e.getMessage()); + } } } - @Override - public void close() throws Exception { - saveSubjectsToFile(); + @SuppressWarnings("unchecked") + private static void loadSubjectsFromFile() { + File file = new File(DATA_FILE); + if(!file.exists()){ + + try { + file.createNewFile(); + System.out.println("Created new file: " + DATA_FILE); + return; + } catch (IOException e) { + throw new RuntimeException(e); + } + } + if (file.exists()) { + try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file))) { + Object readObject = ois.readObject(); + if (readObject instanceof Map) { + subjectSubscriptions = new ConcurrentHashMap<>((Map) readObject); + System.out.println("DataStore state loaded from file."); + } + } catch (IOException | ClassNotFoundException e) { + System.err.println("Failed to load DataStore state: " + e.getMessage()); + } + } } + } diff --git a/src/main/java/dev/ksan/DataThread.java b/src/main/java/dev/ksan/DataThread.java index 94d7844..0e086e6 100644 --- a/src/main/java/dev/ksan/DataThread.java +++ b/src/main/java/dev/ksan/DataThread.java @@ -5,15 +5,20 @@ import java.util.List; import java.util.Set; import java.io.*; -import java.util.*; - +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import com.fasterxml.jackson.databind.ObjectMapper; public class DataThread implements Runnable { private List allEntries = new ArrayList<>(); private boolean running; private Set subjects = Subject.generateSubjects(); - private static final String FILE_NAME = "allEntries.dat"; + private static final String FILE_NAME = "allEntries.json"; + private static final long SAVE_INTERVAL_MS = 10000; + private static final ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor(); + private static final ObjectMapper objectMapper = new ObjectMapper(); @Override public void run() { running = true; @@ -23,8 +28,8 @@ public class DataThread implements Runnable { }catch (Exception e) { e.printStackTrace(); } - - while (running) { + executorService.scheduleAtFixedRate(this::saveEntriesToFile, 0, SAVE_INTERVAL_MS, TimeUnit.MILLISECONDS); + while (running && !Thread.currentThread().isInterrupted()) { try { System.out.println("comparing: " + compare(ETFScraper.getEntries())); @@ -32,27 +37,33 @@ public class DataThread implements Runnable { } catch (InterruptedException e) { System.out.println("Thread interrupted, stopping..."); running = false; + executorService.shutdown(); Thread.currentThread().interrupt(); + return; } catch (Exception e) { e.printStackTrace(); } } saveEntriesToFile(); + + executorService.shutdown(); } - public boolean compare(List subjectEntries) { + synchronized private boolean compare(List subjectEntries) { if (subjectEntries.size() == 0) { System.out.println("No entries found"); return false; } for (SubjectEntry subjectEntry : subjectEntries) { + // If entry is new and not already present if (!allEntries.contains(subjectEntry)) { + allEntries.add(subjectEntry); for (Subject subject : subjects) { if (subject.matchesTitle(subjectEntry.getTitle())) { +// addNewEntry(subject,subjectEntry); DataStore.notifySubject(subject, subjectEntry); - allEntries.add(subjectEntry); break; } } @@ -60,21 +71,27 @@ public class DataThread implements Runnable { } return true; } +/* + synchronized private void addNewEntry(Subject subject, SubjectEntry subjectEntry) { + allEntries.add(subjectEntry); + DataStore.notifySubject(subject, subjectEntry); + } + */ public void stop() { running = false; } - +/* private void saveEntriesToFile() { try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(FILE_NAME))) { oos.writeObject(allEntries); - System.out.println("Entries saved to file."); } catch (IOException e) { - e.printStackTrace(); - } + System.err.println("Failed to save entries: " + e.getMessage()); } } + + private void readEntriesFromFile() throws IOException { File file = new File(FILE_NAME); @@ -108,4 +125,45 @@ public class DataThread implements Runnable { e.printStackTrace(); } } +*/ + private void saveEntriesToFile() { + try { + // Serialize the entries to JSON + String json = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(allEntries); + + // Write the JSON to a file + try (BufferedWriter writer = new BufferedWriter(new FileWriter(FILE_NAME))) { + writer.write(json); + System.out.println("All entries saved to JSON file."); + } + } catch (IOException e) { + System.err.println("Failed to save entries to JSON: " + e.getMessage()); + } + + } + private void readEntriesFromFile() { + File file = new File(FILE_NAME); + if (file.exists()) { + try (BufferedReader reader = new BufferedReader(new FileReader(file))) { + allEntries = objectMapper.readValue(reader, objectMapper.getTypeFactory().constructCollectionType(List.class, SubjectEntry.class)); + System.out.println("Entries loaded from JSON file."); + } catch (IOException e) { + System.err.println("Failed to load entries from JSON: " + e.getMessage()); + e.printStackTrace(); + } + } else { + try { + // Create the file and write an empty list to it + file.createNewFile(); + try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) { + writer.write("[]"); + System.out.println("No previous entries found. Created an empty JSON file."); + } + } catch (IOException e) { + System.err.println("Failed to create empty JSON file: " + e.getMessage()); + } + allEntries = new ArrayList<>(); + } + } + } diff --git a/src/main/java/dev/ksan/ETFScraper.java b/src/main/java/dev/ksan/ETFScraper.java index bd274a7..705a491 100644 --- a/src/main/java/dev/ksan/ETFScraper.java +++ b/src/main/java/dev/ksan/ETFScraper.java @@ -1,7 +1,9 @@ package dev.ksan; import com.gargoylesoftware.htmlunit.BrowserVersion; +import com.gargoylesoftware.htmlunit.IncorrectnessListener; import com.gargoylesoftware.htmlunit.WebClient; +import com.gargoylesoftware.htmlunit.WebClientOptions; import com.gargoylesoftware.htmlunit.html.DomElement; import com.gargoylesoftware.htmlunit.html.HtmlAnchor; import com.gargoylesoftware.htmlunit.html.HtmlElement; @@ -9,13 +11,23 @@ import com.gargoylesoftware.htmlunit.html.HtmlPage; import java.util.ArrayList; import java.util.List; +import java.util.logging.ConsoleHandler; +import java.util.logging.Handler; +import java.util.logging.Level; +import java.util.logging.Logger; public class ETFScraper implements Runnable { private static List entries = new ArrayList<>(); + + private WebClient webClient; private volatile boolean running = true; public ETFScraper() { + this.webClient = new WebClient(BrowserVersion.CHROME); + webClient.getOptions().setJavaScriptEnabled(true); + webClient.getOptions().setCssEnabled(false); + webClient.getOptions().setThrowExceptionOnScriptError(false); } private static String getTextOrEmpty(HtmlElement parent, String xPath) { HtmlElement element = parent.getFirstByXPath(xPath); @@ -26,15 +38,25 @@ public class ETFScraper implements Runnable { return new ArrayList<>(entries); } } + private void configureHtmlUnitLogging() { + // Get the HtmlUnit logger and set its level to SEVERE to suppress warnings + Logger htmlUnitLogger = Logger.getLogger("com.gargoylesoftware.htmlunit"); + htmlUnitLogger.setLevel(Level.SEVERE); + Handler consoleHandler = new ConsoleHandler(); + consoleHandler.setLevel(Level.SEVERE); + htmlUnitLogger.addHandler(consoleHandler); + + } @Override public void run() { - while(running) { - try (final WebClient webClient = new WebClient(BrowserVersion.CHROME)) { + configureHtmlUnitLogging(); + while(running && !Thread.currentThread().isInterrupted()) { + + try { + System.out.println("Performing WebClient task..."); + - webClient.getOptions().setJavaScriptEnabled(true); - webClient.getOptions().setCssEnabled(false); - webClient.getOptions().setThrowExceptionOnScriptError(false); HtmlPage mainPage = webClient.getPage("https://efee.etf.unibl.org/oglasi/"); webClient.waitForBackgroundJavaScript(1000); @@ -74,7 +96,7 @@ public class ETFScraper implements Runnable { for (HtmlElement pTag : pTags) { paragraphs.add(pTag.asNormalizedText()); } - SubjectEntry entry = new SubjectEntry(title, date, info, paragraphs); + SubjectEntry entry = new SubjectEntry(title,groupName, date, info, paragraphs); entries.add(entry); @@ -86,9 +108,12 @@ public class ETFScraper implements Runnable { //Thread.sleep(20000); - } catch (Exception e) { + } + catch (Exception e) { e.printStackTrace(); System.out.println("ERROR: " + e.getMessage()); + } finally { + this.webClient.close(); } } System.out.println("WebScraper thread stopped"); diff --git a/src/main/java/dev/ksan/Main.java b/src/main/java/dev/ksan/Main.java index 1ac1ea3..9ed6644 100644 --- a/src/main/java/dev/ksan/Main.java +++ b/src/main/java/dev/ksan/Main.java @@ -1,25 +1,22 @@ package dev.ksan; -import com.gargoylesoftware.htmlunit.WebClient; -import com.gargoylesoftware.htmlunit.BrowserVersion; -import com.gargoylesoftware.htmlunit.html.HtmlPage; -import com.gargoylesoftware.htmlunit.html.*; -import java.io.IOException; import java.util.*; public class Main { public static void main(String[] args) { boolean running = true; - User user = new User("djordje@ksan.dev","123"); + + User user = Users.createUser("djordje@ksan.dev","123"); + boolean istrue = user.addSubject("Filozofija"); user.addSubject("Programiranje 2"); System.out.println(istrue); System.out.println(user.getEmail() + user.getId()); - Set subjectSet = user.getSubjectSet(); + System.out.println(Users.getUserCount()+1); List entries = new ArrayList<>(); List newEntries = new ArrayList<>(); @@ -36,6 +33,7 @@ public class Main { Scanner scanner = new Scanner(System.in); dataThread.start(); + DataStore.subscribeUserToSubject(user,new Subject("Osnovi elektrotehnike 2")); try { while (running) { @@ -48,6 +46,8 @@ public class Main { task.stop(); webClientThread.interrupt(); running = false; + DataStore.stop(); + Users.stop(); System.out.println("Stopping..."); break; case "list": @@ -58,37 +58,13 @@ public class Main { } }catch (Exception e) { - scanner.close(); e.printStackTrace(); - } - scanner.close(); - //temp - SubjectEntry mail = new SubjectEntry("TAAAA","asd", "Test", Arrays.asList("Testing mail broj 2", "ne znam zas je u spamu")); - try { - //user.sendEmail(mail); - System.out.println("AAAAAAAAAAAAAAAAAAAA"); - } catch (Exception e) { - throw new RuntimeException(e); + }finally { + scanner.close(); } - entries=task.getEntries(); - System.out.println("BBBBBBBBBBBBb"); - System.out.println(entries.size()); - for(SubjectEntry e : task.getEntries()) { - //System.out.println(e); - } - for(Subject subject : user.getSubjectSet()) { - for(SubjectEntry entry : entries){ - if(subject.isSubject(entry.getTitle())){ - //user.sendEmail(entry); - System.out.println("Subject " + subject.getTitle() + " was found"); - } - } - - } - //temp } } \ No newline at end of file diff --git a/src/main/java/dev/ksan/SubjectEntry.java b/src/main/java/dev/ksan/SubjectEntry.java index c342322..d856860 100644 --- a/src/main/java/dev/ksan/SubjectEntry.java +++ b/src/main/java/dev/ksan/SubjectEntry.java @@ -1,19 +1,32 @@ package dev.ksan; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.io.Serializable; import java.util.List; -public class SubjectEntry { +public class SubjectEntry implements Serializable { + private String group; private String title; private String date; private String info; private List paragraphs; - public SubjectEntry(String title, String date, String info, List paragraphs) { + @JsonCreator + public SubjectEntry(@JsonProperty("title") String title, + @JsonProperty("group") String group, + @JsonProperty("date") String date, + @JsonProperty("info") String info, + @JsonProperty("paragraphs") List paragraphs + ) { this.title = title; + this.group = group; this.date = date; this.info = info; this.paragraphs = paragraphs; } + public String getTitle() { return title; } @@ -23,6 +36,9 @@ public class SubjectEntry { public String getInfo() { return info; } + public String getGroup() { + return group; + } public List getParagraphs() { return paragraphs; } @@ -38,7 +54,7 @@ public class SubjectEntry { } @Override public String toString() { - return title + " " + date + " " + info + "\n\t" + paragraphs + "\n"; + return title + " " + group + " " + date + " " + info + "\n\t" + paragraphs + "\n"; } } diff --git a/src/main/java/dev/ksan/Subscription.java b/src/main/java/dev/ksan/Subscription.java index f6fa6e0..782927a 100644 --- a/src/main/java/dev/ksan/Subscription.java +++ b/src/main/java/dev/ksan/Subscription.java @@ -1,9 +1,10 @@ package dev.ksan; +import java.io.Serializable; import java.util.ArrayList; import java.util.List; -public class Subscription { +public class Subscription implements Serializable { private Subject subject; private List users = new ArrayList<>(); diff --git a/src/main/java/dev/ksan/User.java b/src/main/java/dev/ksan/User.java index 42f198a..631d5fa 100644 --- a/src/main/java/dev/ksan/User.java +++ b/src/main/java/dev/ksan/User.java @@ -8,9 +8,11 @@ import org.simplejavamail.email.EmailBuilder; import org.simplejavamail.mailer.MailerBuilder; import java.io.*; import java.util.*; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; public class User implements Serializable { - private static Set usedIds = new HashSet<>(); + private String id; private String email; @@ -18,8 +20,10 @@ public class User implements Serializable { private NotificationMethod notificationMethod; private Set subjectSet = new HashSet<>(); - public User(String email, String password) { - this.id = generateId(); + + + public User(String email, String password, String id) { + this.id = id; this.email = email; this.password = password; this.notificationMethod = NotificationMethod.EMAIL; @@ -28,39 +32,19 @@ public class User implements Serializable { public void setNotificationMethod(NotificationMethod notificationMethod) { this.notificationMethod = notificationMethod; } - public void serializeToFile(String filename) throws IOException { - try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filename))) { - oos.writeObject(this); - } - } - - public static User deserializeFromFile(String filename) throws IOException, ClassNotFoundException { - try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename))) { - return (User) ois.readObject(); - } - } - public static void saveUsedIds(String filename) throws IOException { - try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filename))) { - oos.writeObject(usedIds); - } - } - - @SuppressWarnings("unchecked") - public static void loadUsedIds(String filename) throws IOException, ClassNotFoundException { - try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename))) { - usedIds = (Set) ois.readObject(); - } - } - public void sendNotification(SubjectEntry entry){ + if(notificationMethod == NotificationMethod.EMAIL){ System.out.println("Sending an e-mail notification"); - // sendEmail(entry); + sendEmail(entry); + return; } else if (notificationMethod == NotificationMethod.PUSH_NOTIFICATION) { pushNotification(entry); + return; } + System.out.println("Sending a notification failed"); } private void pushNotification(SubjectEntry entry){ //TODO @@ -76,11 +60,11 @@ public class User implements Serializable { } public void addSubject(Subject subject) { - subjectSet.add(subject); - } - public void addSubjects(Set subjects) { - this.subjectSet.addAll(subjects); + if(!subjectSet.contains(subject)){ + subjectSet.add(subject); + } } + public boolean addSubject(String subject) { Set subjects = new HashSet<>(); subjects = Subject.generateSubjects(); @@ -107,13 +91,6 @@ public class User implements Serializable { public void setPassword(String password) { this.password = password; } - private String generateId() { - String id; - do{ - id = UUID.randomUUID().toString(); - }while(usedIds.contains(id)); - usedIds.add(id); - return id; - } + } diff --git a/src/main/java/dev/ksan/Users.java b/src/main/java/dev/ksan/Users.java new file mode 100644 index 0000000..d1c67d6 --- /dev/null +++ b/src/main/java/dev/ksan/Users.java @@ -0,0 +1,114 @@ +package dev.ksan; + +import java.io.*; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +public class Users { + + private static final ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor(); + private static final long SAVE_INTERVAL_MS = 10000; + + private static final String USERS_FILE_NAME = "users.ser"; + private static Map users = new ConcurrentHashMap<>(); + + static{ + loadUsersFromFile(); + startPeriodicSave(); + Runtime.getRuntime().addShutdownHook(new Thread(Users::stopPeriodicSave)); + } + + public static void stop(){ + executorService.shutdownNow(); + try { + if (!executorService.awaitTermination(3000, TimeUnit.MILLISECONDS)) { + executorService.shutdownNow(); + } + }catch(InterruptedException e){ + executorService.shutdownNow(); + throw new RuntimeException("Error during manual shutdown"); + }finally { + saveUsersToFile(); + } + + } + public static int getUserCount() { + return users.size(); + } + public static User createUser(String email, String password) { + User user = new User(email, password, generateId()); + addUser(user); + return user; + } + public static void addUser(User user){ + users.put(user.getId(),user); + } + public static User getUser(String id){ + return users.get(id); + } + public static void removeUser(String id){ + users.remove(id); + } + private static void startPeriodicSave() { + executorService.scheduleAtFixedRate(Users::saveUsersToFile,0,SAVE_INTERVAL_MS, TimeUnit.MILLISECONDS); + } + public static void stopPeriodicSave() { + saveUsersToFile(); + executorService.shutdown(); + } + private static void saveUsersToFile() { + try(ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(USERS_FILE_NAME))){ + oos.writeObject(users); + System.out.println("Users saved to " + USERS_FILE_NAME); + }catch (IOException e){ + e.printStackTrace(); + System.err.println("Failed to save users to " + USERS_FILE_NAME); + } + } + private static void loadUsersFromFile() { + File file = new File(USERS_FILE_NAME); + if(!file.exists()){ + try { + file.createNewFile(); + System.out.println("Users file created: " + USERS_FILE_NAME); + } catch (IOException e) { + System.err.println("Failed to create users file" + USERS_FILE_NAME); + throw new RuntimeException(e); + } + return; + } + if(file.exists()){ + try(ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file))){ + Object readObject = ois.readObject(); + + if(ois instanceof Map){ + + users.clear(); + users.putAll((Map) readObject); + System.out.println("Users loaded from " + USERS_FILE_NAME); + } + }catch(EOFException e){ + System.err.println("Users file is empty"); + users.clear(); + System.err.println("Resetting users due to EOFException"); + } catch (IOException | ClassNotFoundException e) { + System.err.println("Failed to load users from " + USERS_FILE_NAME); + e.printStackTrace(); + throw new RuntimeException(e); + } + } + + } + public static String generateId() { + String id; + do{ + id = UUID.randomUUID().toString(); + }while(users.containsKey(id)); + return id; + } +}