From 67b9ea7a85ab2b0e02a1747b32055222507daed2 Mon Sep 17 00:00:00 2001 From: Reggie Reuss Date: Sat, 14 Mar 2026 18:12:34 -0400 Subject: [PATCH 1/2] Fix IndexOutOfBoundsException when slotTimers is empty or undersized If slotTimers is deserialized as an empty list rather than null (e.g. from a recovered or manually constructed JSON file), hydrateSlotTimers() skips initialization because it only checks for null. The three unguarded .get(slot) calls in screenOfferEvent() then throw IndexOutOfBoundsException on every GE offer event, silently dropping all real-time trades with no user-visible error. - AccountData.hydrateSlotTimers: reinitialize if size != 8, not just null - NewOfferEventPipelineHandler.screenOfferEvent: early return with log warning if slotActivityTimers is missing or undersized - FlippingPlugin.setWidgetsOnSlotTimers: skip loop if list undersized --- .../com/flippingutilities/controller/FlippingPlugin.java | 3 +++ .../controller/NewOfferEventPipelineHandler.java | 5 +++++ src/main/java/com/flippingutilities/model/AccountData.java | 2 +- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/flippingutilities/controller/FlippingPlugin.java b/src/main/java/com/flippingutilities/controller/FlippingPlugin.java index 24d19cc..055bef7 100644 --- a/src/main/java/com/flippingutilities/controller/FlippingPlugin.java +++ b/src/main/java/com/flippingutilities/controller/FlippingPlugin.java @@ -747,6 +747,9 @@ public void setWidgetsOnSlotTimers() { return; } + if (accountData.getSlotTimers().size() < 8) { + return; + } for (int slotIndex = 0; slotIndex < 8; slotIndex++) { SlotActivityTimer timer = accountData.getSlotTimers().get(slotIndex); if (timer == null) { diff --git a/src/main/java/com/flippingutilities/controller/NewOfferEventPipelineHandler.java b/src/main/java/com/flippingutilities/controller/NewOfferEventPipelineHandler.java index 5628e1d..334c5c2 100644 --- a/src/main/java/com/flippingutilities/controller/NewOfferEventPipelineHandler.java +++ b/src/main/java/com/flippingutilities/controller/NewOfferEventPipelineHandler.java @@ -117,6 +117,11 @@ public Optional screenOfferEvent(OfferEvent newOfferEvent) { Map lastOfferEventForEachSlot = plugin.getDataHandler().getAccountData(plugin.getCurrentlyLoggedInAccount()).getLastOffers(); List slotActivityTimers = plugin.getDataHandler().getAccountData(plugin.getCurrentlyLoggedInAccount()).getSlotTimers(); + if (slotActivityTimers == null || slotActivityTimers.size() < 8) { + log.warn("slotActivityTimers is missing or undersized ({}), skipping offer screening", + slotActivityTimers == null ? "null" : slotActivityTimers.size()); + return Optional.empty(); + } OfferEvent lastOfferEvent = lastOfferEventForEachSlot.get(newOfferEvent.getSlot()); //completely useless updates diff --git a/src/main/java/com/flippingutilities/model/AccountData.java b/src/main/java/com/flippingutilities/model/AccountData.java index 00965b7..ea6f92a 100644 --- a/src/main/java/com/flippingutilities/model/AccountData.java +++ b/src/main/java/com/flippingutilities/model/AccountData.java @@ -134,7 +134,7 @@ private void hydrateRecipeFlipGroups(FlippingPlugin plugin) { } private void hydrateSlotTimers(FlippingPlugin plugin) { - if (slotTimers == null) { + if (slotTimers == null || slotTimers.size() != 8) { slotTimers = setupSlotTimers(plugin); } else { slotTimers.forEach(timer -> { From 9327b9f8e6c3396fbf4685141096f68e6bf68e25 Mon Sep 17 00:00:00 2001 From: Reggie Reuss Date: Wed, 25 Mar 2026 21:07:49 -0400 Subject: [PATCH 2/2] Add bounds check in startSlotTimers for consistency forEach on an empty list will not throw, but if slotTimers is undersized the timer updates will silently do nothing. Guard added for consistency with the other three call sites and to surface the condition via log warning rather than silent no-op. --- .../controller/FlippingPlugin.java | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/flippingutilities/controller/FlippingPlugin.java b/src/main/java/com/flippingutilities/controller/FlippingPlugin.java index 055bef7..120edd4 100644 --- a/src/main/java/com/flippingutilities/controller/FlippingPlugin.java +++ b/src/main/java/com/flippingutilities/controller/FlippingPlugin.java @@ -1002,16 +1002,22 @@ public void deleteAccount(String displayName) { } private ScheduledFuture startSlotTimers() { - return executor.scheduleAtFixedRate(() -> - dataHandler.viewAccountData(currentlyLoggedInAccount).getSlotTimers().forEach(slotWidgetTimer -> - clientThread.invokeLater(() -> { - try { - slotsPanel.updateTimerDisplays(slotWidgetTimer.getSlotIndex(), slotWidgetTimer.createFormattedTimeString()); - slotWidgetTimer.updateTimerDisplay(); - } catch (Exception e) { - log.error("exception when trying to update timer", e); - } - })), 1000, 1000, TimeUnit.MILLISECONDS); + return executor.scheduleAtFixedRate(() -> { + List slotTimers = dataHandler.viewAccountData(currentlyLoggedInAccount).getSlotTimers(); + if (slotTimers == null || slotTimers.size() < 8) { + log.warn("slotTimers missing or undersized in startSlotTimers, skipping update"); + return; + } + slotTimers.forEach(slotWidgetTimer -> + clientThread.invokeLater(() -> { + try { + slotsPanel.updateTimerDisplays(slotWidgetTimer.getSlotIndex(), slotWidgetTimer.createFormattedTimeString()); + slotWidgetTimer.updateTimerDisplay(); + } catch (Exception e) { + log.error("exception when trying to update timer", e); + } + })); + }, 1000, 1000, TimeUnit.MILLISECONDS); } private ScheduledFuture startAutoSave() {