slotModifier) {
125 | this.slotModifier = slotModifier;
126 | }
127 |
128 | Slot modify(Slot input) {
129 | return slotModifier.apply(input);
130 | }
131 |
132 | /**
133 | * Checks if the given slot is outside the boundary slot.
134 | *
135 | * @param slot the slot to check
136 | * @param boundary the boundary against which the slot is checked
137 | * @return true if the slot is outside the boundary, false otherwise
138 | */
139 | abstract boolean isOutsideBoundarySlot(Slot slot, Slot boundary);
140 | }
141 |
--------------------------------------------------------------------------------
/src/main/java/io/github/mqzen/menus/misc/button/actions/ButtonClickAction.java:
--------------------------------------------------------------------------------
1 | package io.github.mqzen.menus.misc.button.actions;
2 |
3 | import io.github.mqzen.menus.base.MenuView;
4 | import io.github.mqzen.menus.misc.DataRegistry;
5 | import org.bukkit.event.inventory.InventoryClickEvent;
6 | import org.jetbrains.annotations.Nullable;
7 |
8 | import java.util.ArrayList;
9 | import java.util.List;
10 | import java.util.regex.Matcher;
11 | import java.util.regex.Pattern;
12 | import java.util.stream.Collectors;
13 |
14 | /**
15 | * Interface defining actions triggered by button clicks in a menu.
16 | *
17 | * A ButtonClickAction is identified by a distinct tag and executed by providing
18 | * a specific implementation of the {@link ActionExecutor}.
19 | *
20 | * The interface offers utilities to:
21 | * - Check if a given action string is targeted to the current action
22 | * - Retrieve preferable actions based on click events and data registry
23 | * - Extract action-specific data from a given action string
24 | *
25 | * @author Cobeine
26 | * @author Mqzen (modified after Cobeine)
27 | */
28 | public interface ButtonClickAction {
29 |
30 | /**
31 | * Pattern for capturing content inside parentheses.
32 | * Used to extract action-specific data from a given action string.
33 | */
34 | Pattern PATTERN = Pattern.compile("\\((.*?)\\)");
35 |
36 | /**
37 | * Creates a ButtonClickAction with the specified tag and executor.
38 | *
39 | * @param tag the tag identifying the button click action
40 | * @param executor the ActionExecutor responsible for executing the action
41 | * @return a new ButtonClickAction with the specified tag and executor
42 | */
43 | static ButtonClickAction of(String tag, ActionExecutor executor) {
44 | return new ButtonClickAction() {
45 | @Override
46 | public String tag() {
47 | return tag;
48 | }
49 |
50 | @Override
51 | public void execute(MenuView> menu, InventoryClickEvent event) {
52 | executor.execute(menu, event);
53 | }
54 | };
55 | }
56 |
57 | /**
58 | * Retrieves the tag that identifies this ButtonClickAction.
59 | *
60 | * @return The tag associated with this action.
61 | */
62 | String tag();
63 | /**
64 | * Executes an action triggered by a button click in a menu.
65 | *
66 | * @param menu the MenuView instance where the click event occurred
67 | * @param event the InventoryClickEvent instance representing the click event
68 | */
69 | void execute(MenuView> menu, InventoryClickEvent event);
70 |
71 | /**
72 | * Checks if a given action string matches the current action based on its tag.
73 | *
74 | * @param e the action string to be checked
75 | * @return true if the action string starts with the tag of the current action, false otherwise
76 | */
77 | default boolean isTargetedAction(String e) {
78 | return e.startsWith(tag());
79 | }
80 |
81 | /**
82 | * Retrieves a list of preferable actions based on the given click event and data registry.
83 | *
84 | * @param menu The inventory click event, which provides context such as the slot clicked.
85 | * @param data The data registry containing potential actions associated with the inventory.
86 | * @return A list of preferable action strings extracted and processed from the registry.
87 | */
88 | default List getPreferableActions(InventoryClickEvent menu, DataRegistry data) {
89 |
90 | List actions = new ArrayList<>(data.getData("BTN:" + menu.getSlot()));
91 | actions.removeIf(e -> !isTargetedAction(e));
92 | return actions.stream().map(e-> {
93 | Matcher matcher = PATTERN.matcher(e);
94 | if (!matcher.find()) return null;
95 | return matcher.group(1);
96 | }).collect(Collectors.toList());
97 |
98 | }
99 |
100 | /**
101 | * Extracts specific data from the given action string based on a predefined pattern.
102 | *
103 | * @param action the action string from which data is to be extracted
104 | * @return the extracted data if the pattern matches, or null otherwise
105 | */
106 | default @Nullable String getData(String action) {
107 | Matcher matcher = PATTERN.matcher(action);
108 | if (matcher.find()) {
109 | return matcher.group(1);
110 | }
111 | return null;
112 | }
113 | /**
114 | * Creates a ButtonClickAction with an empty tag and the specified ActionExecutor.
115 | *
116 | * @param executor The ActionExecutor to be executed when the button is clicked.
117 | * @return A new ButtonClickAction instance with an empty tag and the provided ActionExecutor.
118 | */
119 | static ButtonClickAction plain(ActionExecutor executor) {
120 | return ButtonClickAction.of("", executor);
121 | }
122 |
123 | /**
124 | * Represents an executor that defines the action to be performed when a button
125 | * click event occurs within a menu interface.
126 | */
127 | interface ActionExecutor {
128 | /**
129 | * Executes an action associated with a button click event within a menu interface.
130 | *
131 | * @param menu the menu interface where the button click event occurred
132 | * @param event the event representing the button click
133 | */
134 | void execute(MenuView> menu, InventoryClickEvent event);
135 | }
136 |
137 |
138 | }
139 |
--------------------------------------------------------------------------------
/src/main/java/io/github/mqzen/menus/Reflections.java:
--------------------------------------------------------------------------------
1 | package io.github.mqzen.menus;
2 |
3 | import org.jetbrains.annotations.NotNull;
4 |
5 | import java.lang.reflect.Constructor;
6 | import java.lang.reflect.Field;
7 |
8 | final class Reflections {
9 |
10 | /**
11 | * Retrieve a field accessor for a specific field type and name.
12 | *
13 | * @param target - the targetToLoad type.
14 | * @param name - the name of the field, or NULL to ignore.
15 | * @param fieldType - a compatible field type.
16 | * @return The field accessor.
17 | */
18 | public static FieldAccessor getField(Class> target, String name, Class fieldType) {
19 | return getField(target, name, fieldType, 0);
20 | }
21 |
22 | /**
23 | * Retrieve a field accessor for a specific field type and name.
24 | *
25 | * @param className - lookup name of the class, see {@link #getClass(String)}.
26 | * @param name - the name of the field, or NULL to ignore.
27 | * @param fieldType - a compatible field type.
28 | * @return The field accessor.
29 | */
30 | public static FieldAccessor getField(String className, String name, Class fieldType) {
31 | return getField(getClass(className), name, fieldType, 0);
32 | }
33 |
34 | /**
35 | * Retrieve a field accessor for a specific field type and name.
36 | *
37 | * @param target - the targetToLoad type.
38 | * @param fieldType - a compatible field type.
39 | * @return The field accessor.
40 | */
41 | public static FieldAccessor getField(Class> target, Class fieldType) {
42 | return getField(target, null, fieldType, 0);
43 | }
44 |
45 | /**
46 | * Retrieve a field accessor for a specific field type and name.
47 | *
48 | * @param target - the targetToLoad type.
49 | * @param fieldType - a compatible field type.
50 | * @param index - the number of compatible fields to skip.
51 | * @return The field accessor.
52 | */
53 | public static FieldAccessor getField(Class> target, Class fieldType, int index) {
54 | return getField(target, null, fieldType, index);
55 | }
56 |
57 | /**
58 | * Retrieve a field accessor for a specific field type and name.
59 | *
60 | * @param className - lookup name of the class, see {@link #getClass(String)}.
61 | * @param fieldType - a compatible field type.
62 | * @param index - the number of compatible fields to skip.
63 | * @return The field accessor.
64 | */
65 | public static FieldAccessor getField(String className, Class fieldType, int index) {
66 | return getField(getClass(className), fieldType, index);
67 | }
68 |
69 | private static FieldAccessor getField(
70 | final Class> target,
71 | final String name,
72 | final Class fieldType,
73 | int index
74 | ) {
75 | if (target == null) {
76 | throw new IllegalArgumentException("Target class is null");
77 | }
78 | for (final Field field : target.getDeclaredFields()) {
79 | if ((name == null || field.getName().equals(name)) && fieldType.isAssignableFrom(field.getType()) && index-- <= 0) {
80 | field.setAccessible(true);
81 |
82 | // A function for retrieving a specific field value
83 | return new FieldAccessor() {
84 |
85 | @Override
86 | @SuppressWarnings("unchecked")
87 | public T get(final Object target) {
88 | try {
89 | return (T) field.get(target);
90 | } catch (IllegalAccessException e) {
91 | throw new RuntimeException("Cannot access reflection.", e);
92 | }
93 | }
94 |
95 | @Override
96 | public void set(final Object target, final Object value) {
97 | try {
98 | field.set(target, value);
99 | } catch (IllegalAccessException e) {
100 | throw new RuntimeException("Cannot access reflection.", e);
101 | }
102 | }
103 |
104 | @Override
105 | public boolean hasField(final Object target) {
106 | // targetToLoad instanceof DeclaringClass
107 | return field.getDeclaringClass().isAssignableFrom(target.getClass());
108 | }
109 | };
110 | }
111 | }
112 |
113 | // Search in parent classes
114 | if (target.getSuperclass() != null)
115 | return getField(target.getSuperclass(), name, fieldType, index);
116 |
117 | throw new IllegalArgumentException("Cannot find field with type " + fieldType);
118 | }
119 |
120 | /**
121 | * Finds any {@link Class} of the provided paths
122 | *
123 | * @param paths all possible class paths
124 | * @return false if the {@link Class} was NOT found
125 | */
126 | public static boolean findClass(final String... paths) {
127 | for (final String path : paths) {
128 | if (getClass(path) != null) return true;
129 | }
130 | return false;
131 | }
132 |
133 | /**
134 | * A nullable {@link Class#forName(String)} instead of throwing exceptions
135 | *
136 | * @return null if the {@link Class} was NOT found
137 | */
138 | public static Class> getClass(@NotNull final String path) {
139 | try {
140 | return Class.forName(path);
141 | } catch (final Exception ignored) {
142 | return null;
143 | }
144 | }
145 |
146 | /**
147 | * A nullable {@link Class#getDeclaredConstructor(Class[])} instead of throwing exceptions
148 | *
149 | * @return null if the {@link Constructor} was NOT found
150 | */
151 | public static Constructor> getConstructor(@NotNull final Class> clazz, final Class>... classes) {
152 | try {
153 | return clazz.getDeclaredConstructor(classes);
154 | } catch (final Exception ignored) {
155 | return null;
156 | }
157 | }
158 |
159 | }
--------------------------------------------------------------------------------
/src/main/java/io/github/mqzen/menus/misc/itembuilder/ItemBuilder.java:
--------------------------------------------------------------------------------
1 | package io.github.mqzen.menus.misc.itembuilder;
2 |
3 | import org.bukkit.Material;
4 | import org.bukkit.enchantments.Enchantment;
5 | import org.bukkit.inventory.ItemFlag;
6 | import org.bukkit.inventory.ItemStack;
7 | import org.bukkit.inventory.meta.ItemMeta;
8 |
9 | import java.util.Arrays;
10 | import java.util.List;
11 | import java.util.function.Consumer;
12 | import java.util.stream.Collectors;
13 |
14 | /**
15 | * An abstract builder class for creating and modifying ItemStack objects with a fluent API.
16 | *
17 | * @param the type of text object used for display names and lore
18 | * @param the type of the builder itself, used for fluent method chaining
19 | */
20 | @SuppressWarnings("unchecked")
21 | public abstract class ItemBuilder> {
22 |
23 | private final ItemStack itemStack;
24 | protected ItemMeta itemMeta;
25 |
26 | protected ItemBuilder(ItemStack itemStack) {
27 | this.itemStack = itemStack;
28 | this.itemMeta = itemStack.getItemMeta();
29 | }
30 |
31 | protected ItemBuilder(Material material, int amount, short data) {
32 | if(amount < 0 || amount > 64) {
33 | throw new IllegalArgumentException("Invalid ItemStack amount '" + amount + "'");
34 | }
35 | this.itemStack = new ItemStack(material, amount, data);
36 | this.itemMeta = itemStack.getItemMeta();
37 | }
38 |
39 | protected ItemBuilder(Material material, int amount) {
40 | this(material, amount, (short) 0);
41 | }
42 |
43 |
44 | protected ItemBuilder(Material material) {
45 | this(material, 1);
46 | }
47 |
48 | /**
49 | * Converts the given text of generic type T to its string representation.
50 | *
51 | * @param textType The text of generic type T that needs to be converted to a string.
52 | * @return The string representation of the given text.
53 | */
54 | protected abstract String toString(T textType);
55 |
56 | /**
57 | * Sets the display name for the item represented by this builder.
58 | *
59 | * @param display The display name to be set, represented by a generic type T.
60 | * @return The updated builder instance for chaining method calls.
61 | */
62 | public B setDisplay(T display) {
63 | itemMeta.setDisplayName(toString(display));
64 | return (B) this;
65 | }
66 |
67 | /**
68 | * Sets the lore of the item.
69 | *
70 | * @param lore the array of lore entries to set, where each entry is converted
71 | * to a string using the {@link #toString(Object)} method.
72 | * @return the current instance of the builder for method chaining.
73 | */
74 | public B setLore(T... lore) {
75 | itemMeta.setLore(Arrays.stream(lore).map(this::toString).collect(Collectors.toList()));
76 | return (B) this;
77 | }
78 |
79 | /**
80 | * Sets the lore (description) of the item with the given list of lore entries.
81 | *
82 | * @param lore A list of lore entries to set for the item, where each entry represents a line in the lore.
83 | * @return The current instance of the item builder for method chaining.
84 | */
85 | public B setLore(List lore) {
86 | itemMeta.setLore(lore.stream().map(this::toString).collect(Collectors.toList()));
87 | return (B) this;
88 | }
89 |
90 | /**
91 | * Modifies the metadata of the item based on the provided meta class and consumer.
92 | *
93 | * @param the type of the item metadata
94 | * @param metaClass the class of the item metadata to modify
95 | * @param metaConsumer the consumer that defines how the metadata should be modified
96 | * @return the updated ItemBuilder instance
97 | */
98 | public B modifyMeta(Class metaClass, Consumer metaConsumer) {
99 | try {
100 | M meta = metaClass.cast(itemMeta);
101 | metaConsumer.accept(meta);
102 | this.itemMeta = meta;
103 | }catch (ClassCastException ignored) {}
104 | return (B) this;
105 | }
106 |
107 | /**
108 | * Adds one or more {@link ItemFlag} to the item.
109 | *
110 | * @param flags the item flags to be added
111 | * @return this {@link ItemBuilder} with the added item flags
112 | */
113 | public B addFlags(ItemFlag... flags) {
114 | this.itemMeta.addItemFlags(flags);
115 | return (B) this;
116 | }
117 |
118 | /**
119 | * Adds an enchantment to the item with the specified level.
120 | *
121 | * @param enchantment The enchantment to add.
122 | * @param level The level of the enchantment.
123 | * @return The current ItemBuilder instance with the added enchantment.
124 | */
125 | public B enchant(Enchantment enchantment, int level) {
126 | this.itemMeta.addEnchant(enchantment, level, true);
127 | return (B) this;
128 | }
129 |
130 | /**
131 | * Removes a specified enchantment from the item.
132 | *
133 | * @param enchantment the enchantment to remove from the item
134 | * @return the current instance of {@code ItemBuilder} for chaining
135 | */
136 | public B unEnchant(Enchantment enchantment) {
137 | this.itemMeta.removeEnchant(enchantment);
138 | return (B) this;
139 | }
140 |
141 | /**
142 | * Finalizes the construction of the ItemStack by applying the itemMeta.
143 | *
144 | * @return The constructed ItemStack with the applied meta.
145 | */
146 | public final ItemStack build() {
147 | this.itemStack.setItemMeta(itemMeta);
148 | return itemStack;
149 | }
150 |
151 | public static LegacyItemBuilder legacy(ItemStack itemStack) {
152 | return new LegacyItemBuilder(itemStack);
153 | }
154 |
155 | public static LegacyItemBuilder legacy(Material material, int amount, short data) {
156 | return new LegacyItemBuilder(material, amount, data);
157 | }
158 |
159 | public static LegacyItemBuilder legacy(Material material, int amount) {
160 | return new LegacyItemBuilder(material, amount);
161 | }
162 |
163 | public static LegacyItemBuilder legacy(Material material) {
164 | return new LegacyItemBuilder(material);
165 | }
166 |
167 | public static ComponentItemBuilder modern(ItemStack itemStack) {
168 | return new ComponentItemBuilder(itemStack);
169 | }
170 |
171 | public static ComponentItemBuilder modern(Material material, int amount, short data) {
172 | return new ComponentItemBuilder(material, amount, data);
173 | }
174 |
175 | public static ComponentItemBuilder modern(Material material, int amount) {
176 | return new ComponentItemBuilder(material, amount);
177 | }
178 |
179 | public static ComponentItemBuilder modern(Material material) {
180 | return new ComponentItemBuilder(material);
181 | }
182 |
183 | }
184 |
--------------------------------------------------------------------------------
/src/main/java/io/github/mqzen/menus/misc/button/Button.java:
--------------------------------------------------------------------------------
1 | package io.github.mqzen.menus.misc.button;
2 |
3 | import io.github.mqzen.menus.base.MenuView;
4 | import io.github.mqzen.menus.misc.DataRegistry;
5 | import io.github.mqzen.menus.misc.button.actions.ButtonClickAction;
6 | import lombok.Getter;
7 | import org.bukkit.event.inventory.InventoryClickEvent;
8 | import org.bukkit.inventory.ItemStack;
9 | import org.jetbrains.annotations.NotNull;
10 | import org.jetbrains.annotations.Nullable;
11 |
12 | import java.util.function.BiFunction;
13 |
14 | /**
15 | * Represents a button that can be displayed in a menu view. A button can have an item associated with it and
16 | * can perform an action when clicked.
17 | */
18 | @Getter
19 | public class Button {
20 |
21 | private @Nullable ItemStack item;
22 | private @Nullable ButtonClickAction action = null;
23 |
24 | protected DataRegistry data = DataRegistry.empty();
25 |
26 | protected Button(@Nullable ItemStack item) {
27 | this.item = item;
28 | }
29 |
30 | //create constructor for each possible combination of field
31 | protected Button(@Nullable ItemStack item, @Nullable ButtonClickAction action) {
32 | this(item);
33 | this.action = action;
34 | }
35 |
36 | protected Button(@Nullable ItemStack item, @Nullable ButtonClickAction action, @NotNull DataRegistry data) {
37 | this(item, action);
38 | this.data = data;
39 | }
40 |
41 | /**
42 | * Creates a button with the provided item and no action.
43 | *
44 | * @param item the ItemStack associated with the button
45 | * @return a new Button instance with the specified item
46 | */
47 | public static Button empty(ItemStack item) {
48 | return new Button(item);
49 | }
50 |
51 | /**
52 | * Creates a clickable Button with the provided ItemStack and a ButtonClickAction.
53 | * This method facilitates the creation of a Button that performs a specific action when clicked in a menu view.
54 | *
55 | * @param item The ItemStack to be associated with the Button. It represents the visual representation of the Button in the menu.
56 | * @param action The ButtonClickAction to be executed when the Button is clicked. It can be null if no action is required.
57 | * @return A new Button configured with the specified ItemStack and ButtonClickAction.
58 | */
59 | public static Button clickable(ItemStack item, @Nullable ButtonClickAction action) {
60 | return new Button(item, action);
61 | }
62 |
63 | /**
64 | * Creates a new transformer button.
65 | * The transformer button changes its behavior or properties based on user interactions.
66 | *
67 | * @param item the initial ItemStack represented by this button
68 | * @param transformer a BiFunction that takes in a MenuView and an InventoryClickEvent, and returns a new Button
69 | * @return the newly created transformer button
70 | */
71 | public static Button transformerButton(ItemStack item, BiFunction, InventoryClickEvent, Button> transformer) {
72 | return new Button(item, ButtonClickAction.plain((menu, click) -> menu.replaceClickedButton(click, transformer.apply(menu, click))));
73 | }
74 |
75 |
76 | /**
77 | * Creates a new Button instance with a specified item and a transformer function. The transformer function
78 | * allows dynamic modification of the ItemStack when the button is clicked.
79 | *
80 | * @param item the ItemStack associated with the Button.
81 | * @param transformer a BiFunction that takes a MenuView and an InventoryClickEvent and returns an
82 | * ItemStack. This function defines the transformation to be applied to the ItemStack
83 | * when the button is clicked.
84 | * @return a new Button instance configured with the specified item and transformer function.
85 | */
86 | public static Button transformerItem(ItemStack item,
87 | BiFunction, InventoryClickEvent, ItemStack> transformer) {
88 | return new Button(item, ButtonClickAction.plain((view, click) ->
89 | view.replaceClickedItemStack(click, transformer.apply(view, click), false)));
90 | }
91 |
92 | /**
93 | * Determines if the button is clickable by checking if an action is associated with it.
94 | *
95 | * @return {@code true} if the button has an associated action and can be clicked,
96 | * {@code false} otherwise.
97 | */
98 | public boolean isClickable() {
99 | return action != null;
100 | }
101 |
102 |
103 | /**
104 | * Sets the action to be performed when the button is clicked.
105 | *
106 | * @param action the action to be performed on button click; can be {@code null} if no action is needed
107 | * @return the current instance of the Button for method chaining
108 | */
109 | public Button setAction(@Nullable ButtonClickAction action) {
110 | this.action = action;
111 | return this;
112 | }
113 |
114 | /**
115 | * Sets the item associated with the button.
116 | *
117 | * @param item the {@link ItemStack} to set; can be null to unset the item
118 | * @return the current instance of {@link Button} for chaining
119 | */
120 | public Button setItem(@Nullable ItemStack item) {
121 | this.item = item;
122 | return this;
123 | }
124 |
125 | /**
126 | * Executes the click action associated with this button, if any.
127 | *
128 | * @param menu the menu view in which the button resides.
129 | * @param event the inventory click event triggered when the button is clicked.
130 | */
131 | public void executeOnClick(MenuView> menu, InventoryClickEvent event) {
132 | //if (action != null) action.execute(menu, event);
133 | if (action != null) action.execute(menu, event);
134 | }
135 |
136 | /**
137 | * Creates a copy of the current Button instance. The copied Button retains the same item and action as the original.
138 | *
139 | * @return A new Button instance with identical item and action properties as this Button.
140 | */
141 | public Button copy() {
142 | return new Button(this.item, this.action, this.data);
143 | }
144 |
145 | /**
146 | * Sets a named piece of data associated with this button.
147 | *
148 | * @param name the name of the data to be set
149 | * @param data the data to associate with the given name
150 | * @return the current instance of the Button, allowing for chaining of method calls
151 | */
152 | public Button setNamedData(String name, Object data) {
153 | this.data.setData(name, data);
154 | return this;
155 | }
156 |
157 | /**
158 | * Retrieves the data associated with the specified name.
159 | *
160 | * @param The type of the data to be returned.
161 | * @param name The name of the data to retrieve.
162 | * @return The data associated with the given name, or null if no data is found.
163 | */
164 | public T getNamedData(String name) {
165 | return data.getData(name);
166 | }
167 |
168 | /**
169 | * Retrieves the data registry associated with this button.
170 | *
171 | * @return the data registry containing data tied to this button
172 | */
173 | public DataRegistry getDataRegistry() {
174 | return data;
175 | }
176 |
177 | }
178 |
--------------------------------------------------------------------------------
/src/main/java/io/github/mqzen/menus/base/MenuContentImpl.java:
--------------------------------------------------------------------------------
1 | package io.github.mqzen.menus.base;
2 |
3 | import com.google.common.collect.Lists;
4 | import io.github.mqzen.menus.misc.Capacity;
5 | import io.github.mqzen.menus.misc.Slot;
6 | import io.github.mqzen.menus.misc.Slots;
7 | import io.github.mqzen.menus.misc.button.Button;
8 | import io.github.mqzen.menus.misc.button.ButtonCondition;
9 | import org.bukkit.inventory.ItemStack;
10 | import org.jetbrains.annotations.NotNull;
11 | import java.util.*;
12 | import java.util.concurrent.ConcurrentHashMap;
13 | import java.util.function.BiConsumer;
14 | import java.util.function.Consumer;
15 | import java.util.stream.Stream;
16 |
17 | final class MenuContentImpl implements Content {
18 |
19 | private final Capacity capacity;
20 | private final ConcurrentHashMap map = new ConcurrentHashMap<>();
21 |
22 | public MenuContentImpl(Capacity capacity) {
23 | this.capacity = capacity;
24 | }
25 |
26 | @Override
27 | public Capacity capacity() {
28 | return capacity;
29 | }
30 |
31 | @Override
32 | public Optional