Skip to content

Commit

Permalink
Add error handling for GUIs
Browse files Browse the repository at this point in the history
  • Loading branch information
CyberedCake committed Jun 19, 2024
1 parent 5be2973 commit 3722183
Show file tree
Hide file tree
Showing 3 changed files with 113 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
package net.cybercake.cyberapi.spigot.inventory;

import com.google.common.base.Preconditions;
import net.cybercake.cyberapi.common.basic.Pair;
import net.cybercake.cyberapi.spigot.CyberAPI;
import net.cybercake.cyberapi.spigot.basic.BetterStackTraces;
import net.cybercake.cyberapi.spigot.inventory.exceptions.GUIErrorHandler;
import net.cybercake.cyberapi.spigot.inventory.exceptions.VariantSpecialSlotsFailed;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.event.Event;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.inventory.InventoryCloseEvent;
import org.bukkit.event.inventory.InventoryOpenEvent;
Expand All @@ -19,6 +24,7 @@
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

Expand All @@ -32,7 +38,7 @@
* @see CustomGUI#CustomGUI(InventoryType, String)
* @see CustomGUI#CustomGUI(Function)
*/
@SuppressWarnings("unused")
@SuppressWarnings({"unused", "unchecked"})
public abstract class CustomGUI extends UpdateGUIAction implements InventoryHolder {

//<editor-fold desc="static">
Expand Down Expand Up @@ -72,6 +78,12 @@ public abstract class CustomGUI extends UpdateGUIAction implements InventoryHold
*/
final List<GUIConsumer<InventoryCloseEvent>> closeEvents = new ArrayList<>();

/**
* The list of {@link GUIErrorHandler gui error handlers} that will be called when an exception is thrown
* @since 179
*/
final Map<Class<? extends Event>, List<GUIErrorHandler<?>>> errorHandlers = new HashMap<>();

final Inventory inventory;
//</editor-fold>

Expand Down Expand Up @@ -245,6 +257,23 @@ protected CustomGUI(@NotNull InventoryType type) {
public void clearAllCloseEvents() { this.closeEvents.clear(); }
//</editor-fold>

/**
* Adds an error handler. These will be called if an exception occurs while handling an {@link InventoryClickEvent}, {@link InventoryCloseEvent}, or {@link InventoryOpenEvent}
* @param handler the handler, which will supply the {@link Exception exception that occurred}, the {@link Event event}, and the {@link Player player}
* @since 179
*/
public <E extends Event> void addErrorHandler(Class<E> event, GUIErrorHandler<?> handler) {
List<Class<?>> allowedTypes = List.of(InventoryClickEvent.class, InventoryOpenEvent.class, InventoryCloseEvent.class);
Preconditions.checkArgument(
allowedTypes.contains(event),
"Event type must be of " + allowedTypes.stream().map(Class::getCanonicalName).collect(Collectors.joining(", "))
);

if (this.errorHandlers.putIfAbsent(event, new ArrayList<>(List.of(handler))) == null) {
this.errorHandlers.get(event).add(handler);
}
}

//<editor-fold desc="special slot return types">
/**
* @return all slots in the currently opened menu
Expand All @@ -260,6 +289,8 @@ protected CustomGUI(@NotNull InventoryType type) {
*/
public int[] borders() { return SpecialSlots.BORDERS.getSlotsForSize(this.getInventory().getSize()); }

public int[] horizontalBorders() { return SpecialSlots.HORIZONTAL_BORDERS.getSlotsForSize(this.getInventory().getSize()); }

/**
* @return only the slots for the corners of the currently opened menu
* @since 143
Expand Down Expand Up @@ -399,7 +430,20 @@ public Map<Integer, ItemStack> getContents() {
@ApiStatus.Internal void open(InventoryOpenEvent event, Player player) {
try {
onInventoryOpen(event, player);
} catch (UnsupportedOperationException ignored) { } // no implementation
} catch (UnsupportedOperationException ignored) {
// no implementation
} catch (Exception exception) {
for (GUIErrorHandler<?> handler : errorHandlers.get(InventoryOpenEvent.class)) {
try {
GUIErrorHandler<InventoryOpenEvent> castHandler = (GUIErrorHandler<InventoryOpenEvent>) handler;
castHandler.handle(exception, event, player);
} catch (Exception exception2) {
CyberAPI.getInstance().getAPILogger().error("Failed to call exception handler! This is likely not your fault, please create a ticket on github.com/CyberedCake/CyberAPI/issues!");
exception2.initCause(exception);
BetterStackTraces.print(exception2);
}
}
}

this.openEvents.stream()
.filter(Objects::nonNull)
Expand All @@ -417,7 +461,20 @@ public Map<Integer, ItemStack> getContents() {
@ApiStatus.Internal void click(InventoryClickEvent event, Player player) {
try {
onInventoryClick(event, player);
} catch (UnsupportedOperationException ignored) { } // no implementation
} catch (UnsupportedOperationException ignored) {
// no implementation
} catch (Exception exception) {
for (GUIErrorHandler<?> handler : errorHandlers.get(InventoryClickEvent.class)) {
try {
GUIErrorHandler<InventoryClickEvent> castHandler = (GUIErrorHandler<InventoryClickEvent>) handler;
castHandler.handle(exception, event, player);
} catch (Exception exception2) {
CyberAPI.getInstance().getAPILogger().error("Failed to call exception handler! This is likely not your fault, please create a ticket on github.com/CyberedCake/CyberAPI/issues!");
exception2.initCause(exception);
BetterStackTraces.print(exception2);
}
}
}

this.clickEvents.stream()
.filter(Objects::nonNull)
Expand All @@ -435,7 +492,20 @@ public Map<Integer, ItemStack> getContents() {
@ApiStatus.Internal void close(InventoryCloseEvent event, Player player) {
try {
onInventoryClose(event, player);
} catch (UnsupportedOperationException ignored) { } // no implementation
} catch (UnsupportedOperationException ignored) {
// no implementation
} catch (Exception exception) {
for (GUIErrorHandler<?> handler : errorHandlers.get(InventoryCloseEvent.class)) {
try {
GUIErrorHandler<InventoryCloseEvent> castHandler = (GUIErrorHandler<InventoryCloseEvent>) handler;
castHandler.handle(exception, event, player);
} catch (Exception exception2) {
CyberAPI.getInstance().getAPILogger().error("Failed to call exception handler! This is likely not your fault, please create a ticket on github.com/CyberedCake/CyberAPI/issues!");
exception2.initCause(exception);
BetterStackTraces.print(exception2);
}
}
}

this.closeEvents.stream()
.filter(Objects::nonNull)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package net.cybercake.cyberapi.spigot.inventory;

import net.cybercake.cyberapi.common.basic.NumberUtils;
import org.jetbrains.annotations.ApiStatus;

import java.util.function.Function;
Expand Down Expand Up @@ -35,6 +36,30 @@ public enum SpecialSlots {
.toArray()
),

/**
* Returns only the vertical borders of the GUI (i.e., the borders that run up and down on either side of the menu).
* @since 179
*/
VERTICAL_BORDERS((size) ->
IntStream.range(0, size).filter(integer ->
integer % 9 == 0
|| (integer + 1) % 9 == 0
).toArray()
),

/**
* Returns only the horizontal borders of the GUI (i.e., the borders that run side to side on either side of the menu).
* @since 179
*/
HORIZONTAL_BORDERS((size) ->
IntStream.range(0, size).filter(integer ->
NumberUtils.isBetweenEquals(integer, 0, 9)
|| NumberUtils.isBetweenEquals(integer,
size - 10, size - 1)
)
.toArray()
),

/**
* Returns only the corners of a GUI based on its size. If a GUI is one row,
* it will only appear to mark the left and right corners.
Expand Down Expand Up @@ -119,8 +144,7 @@ public enum SpecialSlots {
return result;
}
else return getIndicesFromRow((int) Math.ceil((double) rows / 2)); // odd
})
;
});

private final Function<Integer, int[]> slots;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package net.cybercake.cyberapi.spigot.inventory.exceptions;

import org.bukkit.entity.Player;
import org.bukkit.event.Event;

public interface GUIErrorHandler<E extends Event> {

void handle(Exception exception,
E event,
Player player
);

}

0 comments on commit 3722183

Please sign in to comment.