From 070db1cbbc0266a4e7062e25bc359a284958b051 Mon Sep 17 00:00:00 2001 From: zml Date: Sat, 14 Jan 2023 11:59:21 -0800 Subject: [PATCH 1/8] feat(api): Add emulated callback click event --- .../adventure/text/event/ClickCallback.java | 145 ++++++++++++++++++ .../text/event/ClickCallbackOptionsImpl.java | 99 ++++++++++++ .../event/ClickCallbackProviderLookup.java | 43 ++++++ .../adventure/text/event/ClickEvent.java | 42 +++++ 4 files changed, 329 insertions(+) create mode 100644 api/src/main/java/net/kyori/adventure/text/event/ClickCallback.java create mode 100644 api/src/main/java/net/kyori/adventure/text/event/ClickCallbackOptionsImpl.java create mode 100644 api/src/main/java/net/kyori/adventure/text/event/ClickCallbackProviderLookup.java diff --git a/api/src/main/java/net/kyori/adventure/text/event/ClickCallback.java b/api/src/main/java/net/kyori/adventure/text/event/ClickCallback.java new file mode 100644 index 000000000..42c422506 --- /dev/null +++ b/api/src/main/java/net/kyori/adventure/text/event/ClickCallback.java @@ -0,0 +1,145 @@ +/* + * This file is part of adventure, licensed under the MIT License. + * + * Copyright (c) 2017-2023 KyoriPowered + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package net.kyori.adventure.text.event; + +import java.time.Duration; +import java.time.temporal.TemporalAmount; +import net.kyori.adventure.audience.Audience; +import net.kyori.adventure.builder.AbstractBuilder; +import net.kyori.adventure.util.PlatformAPI; +import net.kyori.examination.Examinable; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; + +/** + * A handler for callback click events. + * + * @since 4.13.0 + */ +@FunctionalInterface +public interface ClickCallback { + /** + * Perform an action for this event. + * + * @param audience the single-user audience who is attempting to execute this callback function. + * @since 4.13.0 + */ + void accept(final Audience audience); + + /** + * Options to configure how a callback can be executed. + * + * @since 4.13.0 + */ + @ApiStatus.NonExtendable + interface Options extends Examinable { + + /** + * Create a new builder. + * + * @return the new builder + * @since 4.13.0 + */ + static @NotNull Builder builder() { + return new ClickCallbackOptionsImpl.BuilderImpl(); + } + + /** + * Create a new builder populating from existing options. + * + * @param existing the existing options to populate this builder with + * @return the new builder + * @since 4.13.0 + */ + static @NotNull Builder builder(final @NotNull Options existing) { + return new ClickCallbackOptionsImpl.BuilderImpl(existing); + } + + /** + * Whether the callback can be executed multiple times. + * + *

By default callbacks are single-use.

+ * + * @return whether this callback is multi-use + * @since 4.13.0 + */ + boolean multiUse(); + + /** + * How long this callback will last until it is made invalid. + * + *

By default callbacks last 12 hours..

+ * + * @return the duration of this callback + * @since 4.13.0 + */ + @NotNull Duration lifetime(); + + /** + * A builder for callback options. + * + * @since 4.13.0 + */ + @ApiStatus.NonExtendable + interface Builder extends AbstractBuilder { + /** + * Set whether the callback can be multi-use. + * + * @param multiUse whether multiple clicks are allowed + * @return this builder + * @since 4.13.0 + */ + @NotNull Builder multiUse(boolean multiUse); + + /** + * Set how long the callback should last from sending. + * + * @param duration the duration of this callback, from the time it is sent + * @return this builder + * @since 4.13.0 + */ + @NotNull Builder lifetime(final @NotNull TemporalAmount duration); + } + } + + /** + * A provider for actually producing click callbacks. + * + * @since 4.13.0 + */ + @PlatformAPI + @ApiStatus.Internal + interface Provider { + + /** + * Create a real click event based on the provided parameters. + * + * @param callback the callback to execute + * @param options the options to apply to this callback + * @return a created click event that will execute the provided callback with options + * @since 4.13.0 + */ + @NotNull ClickEvent create(final @NotNull ClickCallback callback, final ClickCallback.@NotNull Options options); + } +} diff --git a/api/src/main/java/net/kyori/adventure/text/event/ClickCallbackOptionsImpl.java b/api/src/main/java/net/kyori/adventure/text/event/ClickCallbackOptionsImpl.java new file mode 100644 index 000000000..a68db1b56 --- /dev/null +++ b/api/src/main/java/net/kyori/adventure/text/event/ClickCallbackOptionsImpl.java @@ -0,0 +1,99 @@ +/* + * This file is part of adventure, licensed under the MIT License. + * + * Copyright (c) 2017-2023 KyoriPowered + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package net.kyori.adventure.text.event; + +import java.time.Duration; +import java.time.temporal.TemporalAmount; +import java.util.stream.Stream; +import net.kyori.adventure.internal.Internals; +import net.kyori.examination.ExaminableProperty; +import org.jetbrains.annotations.NotNull; + +import static java.util.Objects.requireNonNull; + +final class ClickCallbackOptionsImpl implements ClickCallback.Options { + static final ClickCallback.Options DEFAULT = new ClickCallbackOptionsImpl.BuilderImpl().build(); + + private final boolean multiUse; + private final Duration lifetime; + + ClickCallbackOptionsImpl(final boolean multiUse, final Duration lifetime) { + this.multiUse = multiUse; + this.lifetime = lifetime; + } + + @Override + public boolean multiUse() { + return this.multiUse; + } + + @Override + public @NotNull Duration lifetime() { + return this.lifetime; + } + + @Override + public @NotNull Stream examinableProperties() { + return Stream.of( + ExaminableProperty.of("multiUse", this.multiUse), + ExaminableProperty.of("expiration", this.lifetime) + ); + } + + @Override + public String toString() { + return Internals.toString(this); + } + + static final class BuilderImpl implements Builder { + private boolean multiUse; + private Duration lifetime; + + BuilderImpl() { + this.lifetime = Duration.ofHours(12); + } + + BuilderImpl(final ClickCallback.@NotNull Options existing) { + this.multiUse = existing.multiUse(); + this.lifetime = existing.lifetime(); + } + + @Override + public ClickCallback.@NotNull Options build() { + return new ClickCallbackOptionsImpl(this.multiUse, this.lifetime); + } + + @Override + public @NotNull Builder multiUse(final boolean multiUse) { + this.multiUse = multiUse; + return this; + } + + @Override + public @NotNull Builder lifetime(final @NotNull TemporalAmount lifetime) { + this.lifetime = lifetime instanceof Duration ? (Duration) lifetime : Duration.from(requireNonNull(lifetime, "lifetime")); + return this; + } + } +} diff --git a/api/src/main/java/net/kyori/adventure/text/event/ClickCallbackProviderLookup.java b/api/src/main/java/net/kyori/adventure/text/event/ClickCallbackProviderLookup.java new file mode 100644 index 000000000..b4eefced3 --- /dev/null +++ b/api/src/main/java/net/kyori/adventure/text/event/ClickCallbackProviderLookup.java @@ -0,0 +1,43 @@ +/* + * This file is part of adventure, licensed under the MIT License. + * + * Copyright (c) 2017-2023 KyoriPowered + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package net.kyori.adventure.text.event; + +import net.kyori.adventure.util.Services; +import org.jetbrains.annotations.NotNull; + +final class ClickCallbackProviderLookup { + + private ClickCallbackProviderLookup() { + } + + static final ClickCallback.Provider PROVIDER = Services.service(ClickCallback.Provider.class) + .orElseGet(Fallback::new); + + static final class Fallback implements ClickCallback.Provider { + @Override + public @NotNull ClickEvent create(final @NotNull ClickCallback callback, final ClickCallback.@NotNull Options options) { + return ClickEvent.suggestCommand("Callbacks are not supported on this platform!"); + } + } +} diff --git a/api/src/main/java/net/kyori/adventure/text/event/ClickEvent.java b/api/src/main/java/net/kyori/adventure/text/event/ClickEvent.java index 83cd3da57..0541cf8c9 100644 --- a/api/src/main/java/net/kyori/adventure/text/event/ClickEvent.java +++ b/api/src/main/java/net/kyori/adventure/text/event/ClickEvent.java @@ -25,7 +25,9 @@ import java.net.URL; import java.util.Objects; +import java.util.function.Consumer; import java.util.stream.Stream; +import net.kyori.adventure.builder.AbstractBuilder; import net.kyori.adventure.internal.Internals; import net.kyori.adventure.text.format.Style; import net.kyori.adventure.text.format.StyleBuilderApplicable; @@ -136,6 +138,46 @@ public final class ClickEvent implements Examinable, StyleBuilderApplicable { return new ClickEvent(Action.COPY_TO_CLIPBOARD, text); } + /** + * Create a click event that, when clicked, will schedule a callback function to be executed on the server. + * + *

By default, this will be a single-use function that expires after 12 hours. Exact lifetime is subject to change.

+ * + * @param function the function to execute + * @return a callback click event + * @since 4.13.0 + */ + public static @NotNull ClickEvent callback(final @NotNull ClickCallback function) { + return ClickCallbackProviderLookup.PROVIDER.create(requireNonNull(function, "function"), ClickCallbackOptionsImpl.DEFAULT); + } + + /** + * Create a click event that, when clicked, will schedule a callback function to be executed on the server. + * + * @param function the function to execute + * @param options options to control how the callback will be stored on the server. + * @return a callback click event + * @since 4.13.0 + */ + public static @NotNull ClickEvent callback(final @NotNull ClickCallback function, final ClickCallback.@NotNull Options options) { + return ClickCallbackProviderLookup.PROVIDER.create(requireNonNull(function, "function"), requireNonNull(options, "options")); + } + + /** + * Create a click event that, when clicked, will schedule a callback function to be executed on the server. + * + * @param function the function to execute + * @param optionsBuilder function that will be called to configure the click callback options + * @return a callback click event + * @since 4.13.0 + */ + public static @NotNull ClickEvent callback(final @NotNull ClickCallback function, final @NotNull Consumer optionsBuilder) { + return ClickCallbackProviderLookup.PROVIDER.create( + requireNonNull(function, "function"), + AbstractBuilder.configureAndBuild(ClickCallback.Options.builder(), requireNonNull(optionsBuilder, "optionsBuilder")) + ); + } + /** * Creates a click event. * From 434ee79e743c013b9595b8caa19939d4746eeff7 Mon Sep 17 00:00:00 2001 From: zml Date: Sat, 14 Jan 2023 12:23:17 -0800 Subject: [PATCH 2/8] feat(api): Add convenience methods to callback functions --- .../adventure/text/event/ClickCallback.java | 109 +++++++++++++++++- .../event/ClickCallbackProviderLookup.java | 3 +- .../adventure/text/event/ClickEvent.java | 7 +- 3 files changed, 112 insertions(+), 7 deletions(-) diff --git a/api/src/main/java/net/kyori/adventure/text/event/ClickCallback.java b/api/src/main/java/net/kyori/adventure/text/event/ClickCallback.java index 42c422506..e68bafbf6 100644 --- a/api/src/main/java/net/kyori/adventure/text/event/ClickCallback.java +++ b/api/src/main/java/net/kyori/adventure/text/event/ClickCallback.java @@ -25,27 +25,130 @@ import java.time.Duration; import java.time.temporal.TemporalAmount; +import java.util.function.Predicate; import net.kyori.adventure.audience.Audience; import net.kyori.adventure.builder.AbstractBuilder; +import net.kyori.adventure.permission.PermissionChecker; +import net.kyori.adventure.text.Component; import net.kyori.adventure.util.PlatformAPI; +import net.kyori.adventure.util.TriState; import net.kyori.examination.Examinable; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; /** * A handler for callback click events. * + * @param audience type * @since 4.13.0 */ @FunctionalInterface -public interface ClickCallback { +public interface ClickCallback { + /** + * Adjust this callback to accept any audience, and perform the appropriate filtering. + * + * @param the wider type + * @param the narrower type + * @param original the original callback of a narrower audience type + * @param type the audience type to accept + * @param failureMessage the message to send to the audience if it is not of the appropriate type + * @return a new callback + * @since 4.13.0 + */ + static @NotNull ClickCallback widen(final @NotNull ClickCallback original, final @NotNull Class type, final @Nullable Component failureMessage) { + return audience -> { + if (type.isInstance(audience)) { + original.accept(type.cast(audience)); + } else if (failureMessage != null) { + audience.sendMessage(failureMessage); + } + }; + } + + /** + * Adjust this callback to accept any audience, and perform the appropriate filtering. + * + *

No message will be sent if the audience is not of the appropriate type.

+ * + * @param the wider type + * @param the narrower type + * @param original the original callback of a narrower audience type + * @param type the audience type to accept + * @return a new callback + * @since 4.13.0 + */ + static @NotNull ClickCallback widen(final @NotNull ClickCallback original, final @NotNull Class type) { + return ClickCallback.widen(original, type, null); + } + /** * Perform an action for this event. * * @param audience the single-user audience who is attempting to execute this callback function. * @since 4.13.0 */ - void accept(final Audience audience); + void accept(final @NotNull T audience); + + /** + * Filter audiences that receive this click callback. + * + *

Actions from audiences that do not match this predicate will be silently ignored.

+ * + * @param filter the filter to test audiences with + * @return a filtered callback action + * @since 4.13.0 + */ + default @NotNull ClickCallback filter(final @NotNull Predicate filter) { + return this.filter(filter, null); + } + + /** + * Filter audiences that receive this click callback. + * + * @param filter the filter to test audiences with + * @param failureMessage a message to send if the conditions are not met + * @return a filtered callback action + * @since 4.13.0 + */ + default @NotNull ClickCallback filter(final @NotNull Predicate filter, final @Nullable Component failureMessage) { + return audience -> { + if (filter.test(audience)) { + this.accept(audience); + } else if (failureMessage != null) { + audience.sendMessage(failureMessage); + } + }; + } + + /** + * Require that audiences receiving this callback have a certain permission. + * + *

For audiences without permissions information, this test will always pass.

+ * + *

Actions from audiences that do not match this predicate will be silently ignored.

+ * + * @param permission the permission to check + * @return a modified callback + * @since 4.13.0 + */ + default @NotNull ClickCallback requiringPermission(final @NotNull String permission) { + return this.requiringPermission(permission, null); + } + + /** + * Require that audiences receiving this callback have a certain permission. + * + *

For audiences without permissions information, this test will always pass.

+ * + * @param permission the permission to check + * @param failureMessage a message to send if the conditions are not met + * @return a modified callback + * @since 4.13.0 + */ + default @NotNull ClickCallback requiringPermission(final @NotNull String permission, final @Nullable Component failureMessage) { + return this.filter(audience -> audience.getOrDefault(PermissionChecker.POINTER, PermissionChecker.always(TriState.TRUE)).test(permission), failureMessage); + } /** * Options to configure how a callback can be executed. @@ -140,6 +243,6 @@ interface Provider { * @return a created click event that will execute the provided callback with options * @since 4.13.0 */ - @NotNull ClickEvent create(final @NotNull ClickCallback callback, final ClickCallback.@NotNull Options options); + @NotNull ClickEvent create(final @NotNull ClickCallback callback, final ClickCallback.@NotNull Options options); } } diff --git a/api/src/main/java/net/kyori/adventure/text/event/ClickCallbackProviderLookup.java b/api/src/main/java/net/kyori/adventure/text/event/ClickCallbackProviderLookup.java index b4eefced3..83e02804b 100644 --- a/api/src/main/java/net/kyori/adventure/text/event/ClickCallbackProviderLookup.java +++ b/api/src/main/java/net/kyori/adventure/text/event/ClickCallbackProviderLookup.java @@ -23,6 +23,7 @@ */ package net.kyori.adventure.text.event; +import net.kyori.adventure.audience.Audience; import net.kyori.adventure.util.Services; import org.jetbrains.annotations.NotNull; @@ -36,7 +37,7 @@ private ClickCallbackProviderLookup() { static final class Fallback implements ClickCallback.Provider { @Override - public @NotNull ClickEvent create(final @NotNull ClickCallback callback, final ClickCallback.@NotNull Options options) { + public @NotNull ClickEvent create(final @NotNull ClickCallback callback, final ClickCallback.@NotNull Options options) { return ClickEvent.suggestCommand("Callbacks are not supported on this platform!"); } } diff --git a/api/src/main/java/net/kyori/adventure/text/event/ClickEvent.java b/api/src/main/java/net/kyori/adventure/text/event/ClickEvent.java index 0541cf8c9..6bd1a659f 100644 --- a/api/src/main/java/net/kyori/adventure/text/event/ClickEvent.java +++ b/api/src/main/java/net/kyori/adventure/text/event/ClickEvent.java @@ -27,6 +27,7 @@ import java.util.Objects; import java.util.function.Consumer; import java.util.stream.Stream; +import net.kyori.adventure.audience.Audience; import net.kyori.adventure.builder.AbstractBuilder; import net.kyori.adventure.internal.Internals; import net.kyori.adventure.text.format.Style; @@ -147,7 +148,7 @@ public final class ClickEvent implements Examinable, StyleBuilderApplicable { * @return a callback click event * @since 4.13.0 */ - public static @NotNull ClickEvent callback(final @NotNull ClickCallback function) { + public static @NotNull ClickEvent callback(final @NotNull ClickCallback function) { return ClickCallbackProviderLookup.PROVIDER.create(requireNonNull(function, "function"), ClickCallbackOptionsImpl.DEFAULT); } @@ -159,7 +160,7 @@ public final class ClickEvent implements Examinable, StyleBuilderApplicable { * @return a callback click event * @since 4.13.0 */ - public static @NotNull ClickEvent callback(final @NotNull ClickCallback function, final ClickCallback.@NotNull Options options) { + public static @NotNull ClickEvent callback(final @NotNull ClickCallback function, final ClickCallback.@NotNull Options options) { return ClickCallbackProviderLookup.PROVIDER.create(requireNonNull(function, "function"), requireNonNull(options, "options")); } @@ -171,7 +172,7 @@ public final class ClickEvent implements Examinable, StyleBuilderApplicable { * @return a callback click event * @since 4.13.0 */ - public static @NotNull ClickEvent callback(final @NotNull ClickCallback function, final @NotNull Consumer optionsBuilder) { + public static @NotNull ClickEvent callback(final @NotNull ClickCallback function, final @NotNull Consumer optionsBuilder) { return ClickCallbackProviderLookup.PROVIDER.create( requireNonNull(function, "function"), AbstractBuilder.configureAndBuild(ClickCallback.Options.builder(), requireNonNull(optionsBuilder, "optionsBuilder")) From 2263df6684ebcacd6cdf0cdada0860b52fb79227 Mon Sep 17 00:00:00 2001 From: zml Date: Sat, 14 Jan 2023 12:32:13 -0800 Subject: [PATCH 3/8] chore: lines --- .../main/java/net/kyori/adventure/text/event/ClickCallback.java | 2 -- .../kyori/adventure/text/event/ClickCallbackProviderLookup.java | 1 - 2 files changed, 3 deletions(-) diff --git a/api/src/main/java/net/kyori/adventure/text/event/ClickCallback.java b/api/src/main/java/net/kyori/adventure/text/event/ClickCallback.java index e68bafbf6..d12a03688 100644 --- a/api/src/main/java/net/kyori/adventure/text/event/ClickCallback.java +++ b/api/src/main/java/net/kyori/adventure/text/event/ClickCallback.java @@ -157,7 +157,6 @@ public interface ClickCallback { */ @ApiStatus.NonExtendable interface Options extends Examinable { - /** * Create a new builder. * @@ -234,7 +233,6 @@ interface Builder extends AbstractBuilder { @PlatformAPI @ApiStatus.Internal interface Provider { - /** * Create a real click event based on the provided parameters. * diff --git a/api/src/main/java/net/kyori/adventure/text/event/ClickCallbackProviderLookup.java b/api/src/main/java/net/kyori/adventure/text/event/ClickCallbackProviderLookup.java index 83e02804b..74a59ecf9 100644 --- a/api/src/main/java/net/kyori/adventure/text/event/ClickCallbackProviderLookup.java +++ b/api/src/main/java/net/kyori/adventure/text/event/ClickCallbackProviderLookup.java @@ -28,7 +28,6 @@ import org.jetbrains.annotations.NotNull; final class ClickCallbackProviderLookup { - private ClickCallbackProviderLookup() { } From 384c746be32158cd7628d4275ae5367c41b56ff3 Mon Sep 17 00:00:00 2001 From: zml Date: Tue, 17 Jan 2023 12:28:12 -0800 Subject: [PATCH 4/8] address review comments --- .../adventure/text/event/ClickCallback.java | 28 +++++++++++-------- ...ookup.java => ClickCallbackInternals.java} | 8 ++++-- .../text/event/ClickCallbackOptionsImpl.java | 23 +++++++-------- .../adventure/text/event/ClickEvent.java | 6 ++-- 4 files changed, 38 insertions(+), 27 deletions(-) rename api/src/main/java/net/kyori/adventure/text/event/{ClickCallbackProviderLookup.java => ClickCallbackInternals.java} (87%) diff --git a/api/src/main/java/net/kyori/adventure/text/event/ClickCallback.java b/api/src/main/java/net/kyori/adventure/text/event/ClickCallback.java index d12a03688..0887fbe2b 100644 --- a/api/src/main/java/net/kyori/adventure/text/event/ClickCallback.java +++ b/api/src/main/java/net/kyori/adventure/text/event/ClickCallback.java @@ -31,7 +31,6 @@ import net.kyori.adventure.permission.PermissionChecker; import net.kyori.adventure.text.Component; import net.kyori.adventure.util.PlatformAPI; -import net.kyori.adventure.util.TriState; import net.kyori.examination.Examinable; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; @@ -124,7 +123,7 @@ public interface ClickCallback { /** * Require that audiences receiving this callback have a certain permission. * - *

For audiences without permissions information, this test will always pass.

+ *

For audiences without permissions information, this test will always fail.

* *

Actions from audiences that do not match this predicate will be silently ignored.

* @@ -139,7 +138,7 @@ public interface ClickCallback { /** * Require that audiences receiving this callback have a certain permission. * - *

For audiences without permissions information, this test will always pass.

+ *

For audiences without permissions information, this test will always fail.

* * @param permission the permission to check * @param failureMessage a message to send if the conditions are not met @@ -147,7 +146,7 @@ public interface ClickCallback { * @since 4.13.0 */ default @NotNull ClickCallback requiringPermission(final @NotNull String permission, final @Nullable Component failureMessage) { - return this.filter(audience -> audience.getOrDefault(PermissionChecker.POINTER, PermissionChecker.always(TriState.TRUE)).test(permission), failureMessage); + return this.filter(audience -> audience.getOrDefault(PermissionChecker.POINTER, ClickCallbackInternals.ALWAYS_FALSE).test(permission), failureMessage); } /** @@ -157,6 +156,13 @@ public interface ClickCallback { */ @ApiStatus.NonExtendable interface Options extends Examinable { + /** + * Indicate that a callback should have unlimited uses. + * + * @since 4.13.0 + */ + int UNLIMITED_USES = -1; + /** * Create a new builder. * @@ -179,19 +185,19 @@ interface Options extends Examinable { } /** - * Whether the callback can be executed multiple times. + * The number of times this callback can be executed. * *

By default callbacks are single-use.

* - * @return whether this callback is multi-use + * @return allowable use count, or {@link #UNLIMITED_USES} * @since 4.13.0 */ - boolean multiUse(); + int uses(); /** * How long this callback will last until it is made invalid. * - *

By default callbacks last 12 hours..

+ *

By default callbacks last 12 hours.

* * @return the duration of this callback * @since 4.13.0 @@ -206,13 +212,13 @@ interface Options extends Examinable { @ApiStatus.NonExtendable interface Builder extends AbstractBuilder { /** - * Set whether the callback can be multi-use. + * Set the number of uses allowed for this callback. * - * @param multiUse whether multiple clicks are allowed + * @param useCount the number of allowed uses, or {@link Options#UNLIMITED_USES} * @return this builder * @since 4.13.0 */ - @NotNull Builder multiUse(boolean multiUse); + @NotNull Builder uses(int useCount); /** * Set how long the callback should last from sending. diff --git a/api/src/main/java/net/kyori/adventure/text/event/ClickCallbackProviderLookup.java b/api/src/main/java/net/kyori/adventure/text/event/ClickCallbackInternals.java similarity index 87% rename from api/src/main/java/net/kyori/adventure/text/event/ClickCallbackProviderLookup.java rename to api/src/main/java/net/kyori/adventure/text/event/ClickCallbackInternals.java index 74a59ecf9..4a7fb4014 100644 --- a/api/src/main/java/net/kyori/adventure/text/event/ClickCallbackProviderLookup.java +++ b/api/src/main/java/net/kyori/adventure/text/event/ClickCallbackInternals.java @@ -24,13 +24,17 @@ package net.kyori.adventure.text.event; import net.kyori.adventure.audience.Audience; +import net.kyori.adventure.permission.PermissionChecker; import net.kyori.adventure.util.Services; +import net.kyori.adventure.util.TriState; import org.jetbrains.annotations.NotNull; -final class ClickCallbackProviderLookup { - private ClickCallbackProviderLookup() { +final class ClickCallbackInternals { + private ClickCallbackInternals() { } + static final PermissionChecker ALWAYS_FALSE = PermissionChecker.always(TriState.FALSE); + static final ClickCallback.Provider PROVIDER = Services.service(ClickCallback.Provider.class) .orElseGet(Fallback::new); diff --git a/api/src/main/java/net/kyori/adventure/text/event/ClickCallbackOptionsImpl.java b/api/src/main/java/net/kyori/adventure/text/event/ClickCallbackOptionsImpl.java index a68db1b56..308abbe76 100644 --- a/api/src/main/java/net/kyori/adventure/text/event/ClickCallbackOptionsImpl.java +++ b/api/src/main/java/net/kyori/adventure/text/event/ClickCallbackOptionsImpl.java @@ -35,17 +35,17 @@ final class ClickCallbackOptionsImpl implements ClickCallback.Options { static final ClickCallback.Options DEFAULT = new ClickCallbackOptionsImpl.BuilderImpl().build(); - private final boolean multiUse; + private final int uses; private final Duration lifetime; - ClickCallbackOptionsImpl(final boolean multiUse, final Duration lifetime) { - this.multiUse = multiUse; + ClickCallbackOptionsImpl(final int uses, final Duration lifetime) { + this.uses = uses; this.lifetime = lifetime; } @Override - public boolean multiUse() { - return this.multiUse; + public int uses() { + return this.uses; } @Override @@ -56,7 +56,7 @@ public boolean multiUse() { @Override public @NotNull Stream examinableProperties() { return Stream.of( - ExaminableProperty.of("multiUse", this.multiUse), + ExaminableProperty.of("uses", this.uses), ExaminableProperty.of("expiration", this.lifetime) ); } @@ -67,26 +67,27 @@ public String toString() { } static final class BuilderImpl implements Builder { - private boolean multiUse; + private int uses; private Duration lifetime; BuilderImpl() { + this.uses = 1; this.lifetime = Duration.ofHours(12); } BuilderImpl(final ClickCallback.@NotNull Options existing) { - this.multiUse = existing.multiUse(); + this.uses = existing.uses(); this.lifetime = existing.lifetime(); } @Override public ClickCallback.@NotNull Options build() { - return new ClickCallbackOptionsImpl(this.multiUse, this.lifetime); + return new ClickCallbackOptionsImpl(this.uses, this.lifetime); } @Override - public @NotNull Builder multiUse(final boolean multiUse) { - this.multiUse = multiUse; + public @NotNull Builder uses(final int uses) { + this.uses = uses; return this; } diff --git a/api/src/main/java/net/kyori/adventure/text/event/ClickEvent.java b/api/src/main/java/net/kyori/adventure/text/event/ClickEvent.java index 6bd1a659f..dfc79b528 100644 --- a/api/src/main/java/net/kyori/adventure/text/event/ClickEvent.java +++ b/api/src/main/java/net/kyori/adventure/text/event/ClickEvent.java @@ -149,7 +149,7 @@ public final class ClickEvent implements Examinable, StyleBuilderApplicable { * @since 4.13.0 */ public static @NotNull ClickEvent callback(final @NotNull ClickCallback function) { - return ClickCallbackProviderLookup.PROVIDER.create(requireNonNull(function, "function"), ClickCallbackOptionsImpl.DEFAULT); + return ClickCallbackInternals.PROVIDER.create(requireNonNull(function, "function"), ClickCallbackOptionsImpl.DEFAULT); } /** @@ -161,7 +161,7 @@ public final class ClickEvent implements Examinable, StyleBuilderApplicable { * @since 4.13.0 */ public static @NotNull ClickEvent callback(final @NotNull ClickCallback function, final ClickCallback.@NotNull Options options) { - return ClickCallbackProviderLookup.PROVIDER.create(requireNonNull(function, "function"), requireNonNull(options, "options")); + return ClickCallbackInternals.PROVIDER.create(requireNonNull(function, "function"), requireNonNull(options, "options")); } /** @@ -173,7 +173,7 @@ public final class ClickEvent implements Examinable, StyleBuilderApplicable { * @since 4.13.0 */ public static @NotNull ClickEvent callback(final @NotNull ClickCallback function, final @NotNull Consumer optionsBuilder) { - return ClickCallbackProviderLookup.PROVIDER.create( + return ClickCallbackInternals.PROVIDER.create( requireNonNull(function, "function"), AbstractBuilder.configureAndBuild(ClickCallback.Options.builder(), requireNonNull(optionsBuilder, "optionsBuilder")) ); From 2fa6d3034dfb156a97191a49ec695f13024686a0 Mon Sep 17 00:00:00 2001 From: Riley Park Date: Fri, 17 Feb 2023 21:09:32 -0800 Subject: [PATCH 5/8] small changes --- .../kyori/adventure/audience/Audiences.java | 27 ++++++++++++++++--- .../adventure/text/event/ClickCallback.java | 26 +++++++++--------- 2 files changed, 36 insertions(+), 17 deletions(-) diff --git a/api/src/main/java/net/kyori/adventure/audience/Audiences.java b/api/src/main/java/net/kyori/adventure/audience/Audiences.java index 6ef32682d..ae560eef8 100644 --- a/api/src/main/java/net/kyori/adventure/audience/Audiences.java +++ b/api/src/main/java/net/kyori/adventure/audience/Audiences.java @@ -25,15 +25,34 @@ import java.util.ArrayList; import java.util.Collections; +import java.util.function.Consumer; import java.util.stream.Collector; import java.util.stream.Collectors; +import net.kyori.adventure.text.ComponentLike; +import org.jetbrains.annotations.NotNull; -final class Audiences { - private Audiences() { - } - +/** + * {@link Audience}-related utilities. + * + * @since 4.13.0 + */ +public final class Audiences { static final Collector COLLECTOR = Collectors.collectingAndThen( Collectors.toCollection(ArrayList::new), audiences -> Audience.audience(Collections.unmodifiableCollection(audiences)) ); + + private Audiences() { + } + + /** + * Creates an action to send a message. + * + * @param message the message to send + * @return an action to send a message + * @since 4.13.0 + */ + public static @NotNull Consumer sendingMessage(final @NotNull ComponentLike message) { + return audience -> audience.sendMessage(message); + } } diff --git a/api/src/main/java/net/kyori/adventure/text/event/ClickCallback.java b/api/src/main/java/net/kyori/adventure/text/event/ClickCallback.java index 0887fbe2b..f91a02d1f 100644 --- a/api/src/main/java/net/kyori/adventure/text/event/ClickCallback.java +++ b/api/src/main/java/net/kyori/adventure/text/event/ClickCallback.java @@ -25,11 +25,11 @@ import java.time.Duration; import java.time.temporal.TemporalAmount; +import java.util.function.Consumer; import java.util.function.Predicate; import net.kyori.adventure.audience.Audience; import net.kyori.adventure.builder.AbstractBuilder; import net.kyori.adventure.permission.PermissionChecker; -import net.kyori.adventure.text.Component; import net.kyori.adventure.util.PlatformAPI; import net.kyori.examination.Examinable; import org.jetbrains.annotations.ApiStatus; @@ -51,16 +51,16 @@ public interface ClickCallback { * @param the narrower type * @param original the original callback of a narrower audience type * @param type the audience type to accept - * @param failureMessage the message to send to the audience if it is not of the appropriate type + * @param otherwise the action to perform on the audience if it is not of the appropriate type * @return a new callback * @since 4.13.0 */ - static @NotNull ClickCallback widen(final @NotNull ClickCallback original, final @NotNull Class type, final @Nullable Component failureMessage) { + static @NotNull ClickCallback widen(final @NotNull ClickCallback original, final @NotNull Class type, final @Nullable Consumer otherwise) { return audience -> { if (type.isInstance(audience)) { original.accept(type.cast(audience)); - } else if (failureMessage != null) { - audience.sendMessage(failureMessage); + } else if (otherwise != null) { + otherwise.accept(audience); } }; } @@ -78,7 +78,7 @@ public interface ClickCallback { * @since 4.13.0 */ static @NotNull ClickCallback widen(final @NotNull ClickCallback original, final @NotNull Class type) { - return ClickCallback.widen(original, type, null); + return widen(original, type, null); } /** @@ -106,16 +106,16 @@ public interface ClickCallback { * Filter audiences that receive this click callback. * * @param filter the filter to test audiences with - * @param failureMessage a message to send if the conditions are not met + * @param otherwise the action to perform on the audience if the conditions are not met * @return a filtered callback action * @since 4.13.0 */ - default @NotNull ClickCallback filter(final @NotNull Predicate filter, final @Nullable Component failureMessage) { + default @NotNull ClickCallback filter(final @NotNull Predicate filter, final @Nullable Consumer otherwise) { return audience -> { if (filter.test(audience)) { this.accept(audience); - } else if (failureMessage != null) { - audience.sendMessage(failureMessage); + } else if (otherwise != null) { + otherwise.accept(audience); } }; } @@ -141,12 +141,12 @@ public interface ClickCallback { *

For audiences without permissions information, this test will always fail.

* * @param permission the permission to check - * @param failureMessage a message to send if the conditions are not met + * @param otherwise the action to perform on the audience if the conditions are not met * @return a modified callback * @since 4.13.0 */ - default @NotNull ClickCallback requiringPermission(final @NotNull String permission, final @Nullable Component failureMessage) { - return this.filter(audience -> audience.getOrDefault(PermissionChecker.POINTER, ClickCallbackInternals.ALWAYS_FALSE).test(permission), failureMessage); + default @NotNull ClickCallback requiringPermission(final @NotNull String permission, final @Nullable Consumer otherwise) { + return this.filter(audience -> audience.getOrDefault(PermissionChecker.POINTER, ClickCallbackInternals.ALWAYS_FALSE).test(permission), otherwise); } /** From c0519ea852d2849003857f4cf93bf3a003b89eee Mon Sep 17 00:00:00 2001 From: zml Date: Sun, 19 Feb 2023 21:31:32 -0800 Subject: [PATCH 6/8] respond to suggestions --- .../net/kyori/adventure/text/event/ClickCallback.java | 11 +++++++++-- .../text/event/ClickCallbackOptionsImpl.java | 2 +- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/api/src/main/java/net/kyori/adventure/text/event/ClickCallback.java b/api/src/main/java/net/kyori/adventure/text/event/ClickCallback.java index f91a02d1f..9b2475861 100644 --- a/api/src/main/java/net/kyori/adventure/text/event/ClickCallback.java +++ b/api/src/main/java/net/kyori/adventure/text/event/ClickCallback.java @@ -44,6 +44,13 @@ */ @FunctionalInterface public interface ClickCallback { + /** + * The default lifetime of a callback after creating it. + * + * @since 4.13.0 + */ + Duration DEFAULT_LIFETIME = Duration.ofHours(12); + /** * Adjust this callback to accept any audience, and perform the appropriate filtering. * @@ -197,7 +204,7 @@ interface Options extends Examinable { /** * How long this callback will last until it is made invalid. * - *

By default callbacks last 12 hours.

+ *

By default callbacks last {@link #DEFAULT_LIFETIME 12 hours}.

* * @return the duration of this callback * @since 4.13.0 @@ -247,6 +254,6 @@ interface Provider { * @return a created click event that will execute the provided callback with options * @since 4.13.0 */ - @NotNull ClickEvent create(final @NotNull ClickCallback callback, final ClickCallback.@NotNull Options options); + @NotNull ClickEvent create(final @NotNull ClickCallback callback, final @NotNull Options options); } } diff --git a/api/src/main/java/net/kyori/adventure/text/event/ClickCallbackOptionsImpl.java b/api/src/main/java/net/kyori/adventure/text/event/ClickCallbackOptionsImpl.java index 308abbe76..c96643d26 100644 --- a/api/src/main/java/net/kyori/adventure/text/event/ClickCallbackOptionsImpl.java +++ b/api/src/main/java/net/kyori/adventure/text/event/ClickCallbackOptionsImpl.java @@ -72,7 +72,7 @@ static final class BuilderImpl implements Builder { BuilderImpl() { this.uses = 1; - this.lifetime = Duration.ofHours(12); + this.lifetime = ClickCallback.DEFAULT_LIFETIME; } BuilderImpl(final ClickCallback.@NotNull Options existing) { From da283ef016a16aa135d18fbb7f2d1d8335282975 Mon Sep 17 00:00:00 2001 From: zml Date: Sat, 25 Feb 2023 17:16:13 -0800 Subject: [PATCH 7/8] further review comments --- .../adventure/text/event/ClickCallback.java | 34 +++++++++++++------ .../adventure/text/event/ClickEvent.java | 2 +- 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/api/src/main/java/net/kyori/adventure/text/event/ClickCallback.java b/api/src/main/java/net/kyori/adventure/text/event/ClickCallback.java index 9b2475861..a11bdd38b 100644 --- a/api/src/main/java/net/kyori/adventure/text/event/ClickCallback.java +++ b/api/src/main/java/net/kyori/adventure/text/event/ClickCallback.java @@ -33,6 +33,8 @@ import net.kyori.adventure.util.PlatformAPI; import net.kyori.examination.Examinable; import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.CheckReturnValue; +import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -45,12 +47,19 @@ @FunctionalInterface public interface ClickCallback { /** - * The default lifetime of a callback after creating it. + * The default lifetime of a callback after creating it, 12 hours. * * @since 4.13.0 */ Duration DEFAULT_LIFETIME = Duration.ofHours(12); + /** + * Indicate that a callback should have unlimited uses. + * + * @since 4.13.0 + */ + int UNLIMITED_USES = -1; + /** * Adjust this callback to accept any audience, and perform the appropriate filtering. * @@ -62,6 +71,8 @@ public interface ClickCallback { * @return a new callback * @since 4.13.0 */ + @CheckReturnValue + @Contract(pure = true) static @NotNull ClickCallback widen(final @NotNull ClickCallback original, final @NotNull Class type, final @Nullable Consumer otherwise) { return audience -> { if (type.isInstance(audience)) { @@ -84,6 +95,8 @@ public interface ClickCallback { * @return a new callback * @since 4.13.0 */ + @CheckReturnValue + @Contract(pure = true) static @NotNull ClickCallback widen(final @NotNull ClickCallback original, final @NotNull Class type) { return widen(original, type, null); } @@ -105,6 +118,8 @@ public interface ClickCallback { * @return a filtered callback action * @since 4.13.0 */ + @CheckReturnValue + @Contract(pure = true) default @NotNull ClickCallback filter(final @NotNull Predicate filter) { return this.filter(filter, null); } @@ -117,6 +132,8 @@ public interface ClickCallback { * @return a filtered callback action * @since 4.13.0 */ + @CheckReturnValue + @Contract(pure = true) default @NotNull ClickCallback filter(final @NotNull Predicate filter, final @Nullable Consumer otherwise) { return audience -> { if (filter.test(audience)) { @@ -138,6 +155,8 @@ public interface ClickCallback { * @return a modified callback * @since 4.13.0 */ + @CheckReturnValue + @Contract(pure = true) default @NotNull ClickCallback requiringPermission(final @NotNull String permission) { return this.requiringPermission(permission, null); } @@ -152,6 +171,8 @@ public interface ClickCallback { * @return a modified callback * @since 4.13.0 */ + @CheckReturnValue + @Contract(pure = true) default @NotNull ClickCallback requiringPermission(final @NotNull String permission, final @Nullable Consumer otherwise) { return this.filter(audience -> audience.getOrDefault(PermissionChecker.POINTER, ClickCallbackInternals.ALWAYS_FALSE).test(permission), otherwise); } @@ -163,13 +184,6 @@ public interface ClickCallback { */ @ApiStatus.NonExtendable interface Options extends Examinable { - /** - * Indicate that a callback should have unlimited uses. - * - * @since 4.13.0 - */ - int UNLIMITED_USES = -1; - /** * Create a new builder. * @@ -204,7 +218,7 @@ interface Options extends Examinable { /** * How long this callback will last until it is made invalid. * - *

By default callbacks last {@link #DEFAULT_LIFETIME 12 hours}.

+ *

By default callbacks last the value of {@link #DEFAULT_LIFETIME}.

* * @return the duration of this callback * @since 4.13.0 @@ -221,7 +235,7 @@ interface Builder extends AbstractBuilder { /** * Set the number of uses allowed for this callback. * - * @param useCount the number of allowed uses, or {@link Options#UNLIMITED_USES} + * @param useCount the number of allowed uses, or {@link ClickCallback#UNLIMITED_USES} * @return this builder * @since 4.13.0 */ diff --git a/api/src/main/java/net/kyori/adventure/text/event/ClickEvent.java b/api/src/main/java/net/kyori/adventure/text/event/ClickEvent.java index dfc79b528..1a6bee3a2 100644 --- a/api/src/main/java/net/kyori/adventure/text/event/ClickEvent.java +++ b/api/src/main/java/net/kyori/adventure/text/event/ClickEvent.java @@ -142,7 +142,7 @@ public final class ClickEvent implements Examinable, StyleBuilderApplicable { /** * Create a click event that, when clicked, will schedule a callback function to be executed on the server. * - *

By default, this will be a single-use function that expires after 12 hours. Exact lifetime is subject to change.

+ *

By default, this will be a single-use function that expires after the value of {@link ClickCallback#DEFAULT_LIFETIME}.

* * @param function the function to execute * @return a callback click event From f6512026e085f8f649d5054d392b6d32f34ba3ca Mon Sep 17 00:00:00 2001 From: zml Date: Thu, 9 Mar 2023 17:25:15 -0800 Subject: [PATCH 8/8] chore(api): Add constant for callback default uses --- .../kyori/adventure/text/event/ClickCallbackOptionsImpl.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/api/src/main/java/net/kyori/adventure/text/event/ClickCallbackOptionsImpl.java b/api/src/main/java/net/kyori/adventure/text/event/ClickCallbackOptionsImpl.java index c96643d26..55bd330fa 100644 --- a/api/src/main/java/net/kyori/adventure/text/event/ClickCallbackOptionsImpl.java +++ b/api/src/main/java/net/kyori/adventure/text/event/ClickCallbackOptionsImpl.java @@ -67,11 +67,13 @@ public String toString() { } static final class BuilderImpl implements Builder { + private static final int DEFAULT_USES = 1; + private int uses; private Duration lifetime; BuilderImpl() { - this.uses = 1; + this.uses = DEFAULT_USES; this.lifetime = ClickCallback.DEFAULT_LIFETIME; }