From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Jake Potrebic Date: Wed, 2 Mar 2022 13:36:21 -0800 Subject: [PATCH] Registry Modification API diff --git a/src/main/java/io/papermc/paper/registry/RegistryBuilder.java b/src/main/java/io/papermc/paper/registry/RegistryBuilder.java new file mode 100644 index 0000000000000000000000000000000000000000..6edf300c1d81c1001756141c9efd022ba0e372cd --- /dev/null +++ b/src/main/java/io/papermc/paper/registry/RegistryBuilder.java @@ -0,0 +1,13 @@ +package io.papermc.paper.registry; + +import org.jetbrains.annotations.ApiStatus; + +/** + * To be implemented by any type used for modifying registries. + * + * @param registry value type + */ +@ApiStatus.NonExtendable +@ApiStatus.Experimental +public interface RegistryBuilder { +} diff --git a/src/main/java/io/papermc/paper/registry/event/RegistryEntryAddEvent.java b/src/main/java/io/papermc/paper/registry/event/RegistryEntryAddEvent.java new file mode 100644 index 0000000000000000000000000000000000000000..a5d7385eae9dfb88b52aed0e42c09a10ef807385 --- /dev/null +++ b/src/main/java/io/papermc/paper/registry/event/RegistryEntryAddEvent.java @@ -0,0 +1,47 @@ +package io.papermc.paper.registry.event; + +import io.papermc.paper.registry.RegistryBuilder; +import io.papermc.paper.registry.TypedKey; +import io.papermc.paper.registry.tag.Tag; +import io.papermc.paper.registry.tag.TagKey; +import org.bukkit.Keyed; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.jetbrains.annotations.ApiStatus; + +/** + * Event object for {@link RegistryEventProvider#entryAdd()}. This + * event is fired right before a specific entry is registered in/added to registry. + * It provides a way for plugins to modify parts of this entry. + * + * @param registry entry type + * @param registry entry builder type + */ +@ApiStatus.Experimental +@ApiStatus.NonExtendable +public interface RegistryEntryAddEvent> extends RegistryEvent { + + /** + * Gets the builder for the entry being added to the registry. + * + * @return the object builder + */ + @NonNull B builder(); + + /** + * Gets the key for this entry in the registry. + * + * @return the key + */ + @NonNull TypedKey key(); + + /** + * Gets or creates a tag for the given tag key. This tag + * is then required to be filled either from the built-in or + * custom datapack. + * + * @param tagKey the tag key + * @return the tag + * @param the tag value type + */ + @NonNull Tag getOrCreateTag(@NonNull TagKey tagKey); +} diff --git a/src/main/java/io/papermc/paper/registry/event/RegistryEvent.java b/src/main/java/io/papermc/paper/registry/event/RegistryEvent.java new file mode 100644 index 0000000000000000000000000000000000000000..498c5920926158e86c2aec2bd2129d5e8b8613a3 --- /dev/null +++ b/src/main/java/io/papermc/paper/registry/event/RegistryEvent.java @@ -0,0 +1,23 @@ +package io.papermc.paper.registry.event; + +import io.papermc.paper.plugin.lifecycle.event.LifecycleEvent; +import io.papermc.paper.registry.RegistryKey; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.jetbrains.annotations.ApiStatus; + +/** + * Base type for all registry events. + * + * @param registry entry type + */ +@ApiStatus.Experimental +@ApiStatus.NonExtendable +public interface RegistryEvent extends LifecycleEvent { + + /** + * Get the key for the registry this event pertains to. + * + * @return the registry key + */ + @NonNull RegistryKey registryKey(); +} diff --git a/src/main/java/io/papermc/paper/registry/event/RegistryEventProvider.java b/src/main/java/io/papermc/paper/registry/event/RegistryEventProvider.java new file mode 100644 index 0000000000000000000000000000000000000000..477ed0fd5acc923d429980529876f0dd7dd3a52a --- /dev/null +++ b/src/main/java/io/papermc/paper/registry/event/RegistryEventProvider.java @@ -0,0 +1,58 @@ +package io.papermc.paper.registry.event; + +import io.papermc.paper.plugin.bootstrap.BootstrapContext; +import io.papermc.paper.plugin.lifecycle.event.handler.LifecycleEventHandler; +import io.papermc.paper.plugin.lifecycle.event.handler.configuration.LifecycleEventHandlerConfiguration; +import io.papermc.paper.plugin.lifecycle.event.handler.configuration.PrioritizedLifecycleEventHandlerConfiguration; +import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEventType; +import io.papermc.paper.registry.RegistryBuilder; +import io.papermc.paper.registry.RegistryKey; +import io.papermc.paper.registry.event.type.RegistryEntryAddEventType; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.jetbrains.annotations.ApiStatus; + +/** + * Provider for registry events for a specific registry. + *

+ * Supported events are: + *

    + *
  • {@link RegistryEntryAddEvent} (via {@link #entryAdd()})
  • + *
  • {@link RegistryFreezeEvent} (via {@link #freeze()})
  • + *
+ * + * @param registry entry type + * @param registry entry builder type + */ +@ApiStatus.Experimental +@ApiStatus.NonExtendable +public interface RegistryEventProvider> { + + /** + * Gets the event type for {@link RegistryEntryAddEvent} which is fired just before + * an object is added to a registry. + *

+ * Can be used in {@link io.papermc.paper.plugin.lifecycle.event.LifecycleEventManager#registerEventHandler(LifecycleEventType, LifecycleEventHandler)} + * to register a handler for {@link RegistryEntryAddEvent}. + * + * @return the registry entry add event type + */ + @NonNull RegistryEntryAddEventType entryAdd(); + + /** + * Gets the event type for {@link RegistryFreezeEvent} which is fired just before + * a registry is frozen. It allows for the registration of new objects. + *

+ * Can be used in {@link io.papermc.paper.plugin.lifecycle.event.LifecycleEventManager#registerEventHandler(LifecycleEventType, LifecycleEventHandler)} + * to register a handler for {@link RegistryFreezeEvent}. + * + * @return the registry freeze event type + */ + LifecycleEventType.@NonNull Prioritizable> freeze(); + + /** + * Gets the registry key associated with this event type provider. + * + * @return the registry key + */ + @NonNull RegistryKey registryKey(); +} diff --git a/src/main/java/io/papermc/paper/registry/event/RegistryEventProviderImpl.java b/src/main/java/io/papermc/paper/registry/event/RegistryEventProviderImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..cfe47c8bd0888db6d00867acfefc8db42ef314aa --- /dev/null +++ b/src/main/java/io/papermc/paper/registry/event/RegistryEventProviderImpl.java @@ -0,0 +1,30 @@ +package io.papermc.paper.registry.event; + +import io.papermc.paper.plugin.bootstrap.BootstrapContext; +import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEventType; +import io.papermc.paper.registry.RegistryBuilder; +import io.papermc.paper.registry.RegistryKey; +import io.papermc.paper.registry.event.type.RegistryEntryAddEventType; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.framework.qual.DefaultQualifier; +import org.jetbrains.annotations.ApiStatus; + +@ApiStatus.Internal +@DefaultQualifier(NonNull.class) +record RegistryEventProviderImpl>(RegistryKey registryKey) implements RegistryEventProvider { + + static > RegistryEventProvider create(final RegistryKey registryKey) { + return new RegistryEventProviderImpl<>(registryKey); + } + + @Override + public RegistryEntryAddEventType entryAdd() { + return RegistryEventTypeProvider.provider().registryEntryAdd(this); + } + + @Override + public LifecycleEventType.Prioritizable> freeze() { + return RegistryEventTypeProvider.provider().registryFreeze(this); + } + +} diff --git a/src/main/java/io/papermc/paper/registry/event/RegistryEventTypeProvider.java b/src/main/java/io/papermc/paper/registry/event/RegistryEventTypeProvider.java new file mode 100644 index 0000000000000000000000000000000000000000..d807bd2f42c98e37a96cf110ad77820dfffc8398 --- /dev/null +++ b/src/main/java/io/papermc/paper/registry/event/RegistryEventTypeProvider.java @@ -0,0 +1,24 @@ +package io.papermc.paper.registry.event; + +import io.papermc.paper.plugin.bootstrap.BootstrapContext; +import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEventType; +import io.papermc.paper.registry.RegistryBuilder; +import io.papermc.paper.registry.event.type.RegistryEntryAddEventType; +import java.util.Optional; +import java.util.ServiceLoader; +import org.jetbrains.annotations.ApiStatus; + +@ApiStatus.Internal +interface RegistryEventTypeProvider { + + Optional PROVIDER = ServiceLoader.load(RegistryEventTypeProvider.class) + .findFirst(); + + static RegistryEventTypeProvider provider() { + return PROVIDER.orElseThrow(() -> new IllegalStateException("Could not find a %s service implementation".formatted(RegistryEventTypeProvider.class.getSimpleName()))); + } + + > RegistryEntryAddEventType registryEntryAdd(RegistryEventProvider type); + + > LifecycleEventType.Prioritizable> registryFreeze(RegistryEventProvider type); +} diff --git a/src/main/java/io/papermc/paper/registry/event/RegistryEvents.java b/src/main/java/io/papermc/paper/registry/event/RegistryEvents.java new file mode 100644 index 0000000000000000000000000000000000000000..1f89945be2ed68f52a544f41f7a151b8fdfe113e --- /dev/null +++ b/src/main/java/io/papermc/paper/registry/event/RegistryEvents.java @@ -0,0 +1,14 @@ +package io.papermc.paper.registry.event; + +import org.jetbrains.annotations.ApiStatus; + +/** + * Holds providers for {@link RegistryEntryAddEvent} and {@link RegistryFreezeEvent} + * handlers for each applicable registry. + */ +@ApiStatus.Experimental +public final class RegistryEvents { + + private RegistryEvents() { + } +} diff --git a/src/main/java/io/papermc/paper/registry/event/RegistryFreezeEvent.java b/src/main/java/io/papermc/paper/registry/event/RegistryFreezeEvent.java new file mode 100644 index 0000000000000000000000000000000000000000..12ec7e794a5047a30354a485acd40fa0f3438eea --- /dev/null +++ b/src/main/java/io/papermc/paper/registry/event/RegistryFreezeEvent.java @@ -0,0 +1,39 @@ +package io.papermc.paper.registry.event; + +import io.papermc.paper.registry.RegistryBuilder; +import io.papermc.paper.registry.tag.Tag; +import io.papermc.paper.registry.tag.TagKey; +import org.bukkit.Keyed; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.jetbrains.annotations.ApiStatus; + +/** + * Event object for {@link RegistryEventProvider#freeze()}. This + * event is fired right before a registry is frozen disallowing further changes. + * It provides a way for plugins to add new objects to the registry. + * + * @param registry entry type + * @param registry entry builder type + */ +@ApiStatus.Experimental +@ApiStatus.NonExtendable +public interface RegistryFreezeEvent> extends RegistryEvent { + + /** + * Get the writable registry. + * + * @return a writable registry + */ + @NonNull WritableRegistry registry(); + + /** + * Gets or creates a tag for the given tag key. This tag + * is then required to be filled either from the built-in or + * custom datapack. + * + * @param tagKey the tag key + * @return the tag + * @param the tag value type + */ + @NonNull Tag getOrCreateTag(@NonNull TagKey tagKey); +} diff --git a/src/main/java/io/papermc/paper/registry/event/WritableRegistry.java b/src/main/java/io/papermc/paper/registry/event/WritableRegistry.java new file mode 100644 index 0000000000000000000000000000000000000000..6de377275097f065c38dd59c6db9704018ac81fc --- /dev/null +++ b/src/main/java/io/papermc/paper/registry/event/WritableRegistry.java @@ -0,0 +1,27 @@ +package io.papermc.paper.registry.event; + +import io.papermc.paper.registry.RegistryBuilder; +import io.papermc.paper.registry.TypedKey; +import java.util.function.Consumer; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.jetbrains.annotations.ApiStatus; + +/** + * A registry which supports registering new objects. + * + * @param registry entry type + * @param registry entry builder type + */ +@ApiStatus.NonExtendable +@ApiStatus.Experimental +public interface WritableRegistry> { + + /** + * Register a new value with the specified key. This will + * fire a {@link RegistryEntryAddEvent} for the new entry. + * + * @param key the entry's key (must be unique from others) + * @param value a consumer for the entry's builder + */ + void register(@NonNull TypedKey key, @NonNull Consumer value); +} diff --git a/src/main/java/io/papermc/paper/registry/event/type/RegistryEntryAddConfiguration.java b/src/main/java/io/papermc/paper/registry/event/type/RegistryEntryAddConfiguration.java new file mode 100644 index 0000000000000000000000000000000000000000..c5b484ee5f4a0f283a8d46f5490afac4b58b06be --- /dev/null +++ b/src/main/java/io/papermc/paper/registry/event/type/RegistryEntryAddConfiguration.java @@ -0,0 +1,42 @@ +package io.papermc.paper.registry.event.type; + +import io.papermc.paper.plugin.bootstrap.BootstrapContext; +import io.papermc.paper.plugin.lifecycle.event.handler.configuration.PrioritizedLifecycleEventHandlerConfiguration; +import io.papermc.paper.registry.TypedKey; +import java.util.function.Predicate; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.jetbrains.annotations.Contract; + +/** + * Specific configuration for {@link io.papermc.paper.registry.event.RegistryEntryAddEvent}s. + * + * @param registry entry type + */ +public interface RegistryEntryAddConfiguration extends PrioritizedLifecycleEventHandlerConfiguration { + + /** + * Only call the handler if the value being added matches the specified key. + * + * @param key the key to match + * @return this configuration + */ + @Contract(value = "_ -> this", mutates = "this") + default @NonNull RegistryEntryAddConfiguration filter(final @NonNull TypedKey key) { + return this.filter(key::equals); + } + + /** + * Only call the handler if the value being added passes the provided filter. + * + * @param filter the predicate to match the key against + * @return this configuration + */ + @Contract(value = "_ -> this", mutates = "this") + @NonNull RegistryEntryAddConfiguration filter(@NonNull Predicate> filter); + + @Override + @NonNull RegistryEntryAddConfiguration priority(int priority); + + @Override + @NonNull RegistryEntryAddConfiguration monitor(); +} diff --git a/src/main/java/io/papermc/paper/registry/event/type/RegistryEntryAddEventType.java b/src/main/java/io/papermc/paper/registry/event/type/RegistryEntryAddEventType.java new file mode 100644 index 0000000000000000000000000000000000000000..f4d4ebf6cbed1b4a9955ceb2d0586782181d97e5 --- /dev/null +++ b/src/main/java/io/papermc/paper/registry/event/type/RegistryEntryAddEventType.java @@ -0,0 +1,18 @@ +package io.papermc.paper.registry.event.type; + +import io.papermc.paper.plugin.bootstrap.BootstrapContext; +import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEventType; +import io.papermc.paper.registry.RegistryBuilder; +import io.papermc.paper.registry.event.RegistryEntryAddEvent; +import org.jetbrains.annotations.ApiStatus; + +/** + * Lifecycle event type for {@link RegistryEntryAddEvent}s. + * + * @param registry entry type + * @param registry entry builder type + */ +@ApiStatus.Experimental +@ApiStatus.NonExtendable +public interface RegistryEntryAddEventType> extends LifecycleEventType, RegistryEntryAddConfiguration> { +} diff --git a/src/main/java/io/papermc/paper/registry/set/RegistryKeySet.java b/src/main/java/io/papermc/paper/registry/set/RegistryKeySet.java new file mode 100644 index 0000000000000000000000000000000000000000..b891101b43148f63c96b7dd611914c85d7b29dbf --- /dev/null +++ b/src/main/java/io/papermc/paper/registry/set/RegistryKeySet.java @@ -0,0 +1,50 @@ +package io.papermc.paper.registry.set; + +import io.papermc.paper.registry.TypedKey; +import java.util.Collection; +import java.util.Iterator; +import org.bukkit.Keyed; +import org.bukkit.Registry; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Unmodifiable; + +@ApiStatus.Experimental +@ApiStatus.NonExtendable +public non-sealed interface RegistryKeySet extends Iterable>, RegistrySet { // TODO remove Keyed + + @Override + default int size() { + return this.values().size(); + } + + /** + * Get the keys for the values in this set. + * + * @return the keys + */ + @NonNull @Unmodifiable Collection> values(); + + /** + * Resolve this set into a collection of values. Prefer using + * {@link #values()}. + * + * @param registry the registry to resolve the values from (must match {@link #registryKey()}) + * @return the resolved values + * @see RegistryKeySet#values() + */ + @NonNull @Unmodifiable Collection resolve(final @NonNull Registry registry); + + /** + * Checks if this set contains the value with the given key. + * + * @param valueKey the key to check + * @return true if the value is in this set + */ + boolean contains(@NonNull TypedKey valueKey); + + @Override + default @NonNull Iterator> iterator() { + return this.values().iterator(); + } +} diff --git a/src/main/java/io/papermc/paper/registry/set/RegistryKeySetImpl.java b/src/main/java/io/papermc/paper/registry/set/RegistryKeySetImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..c712181ad4c6a9d00bc04f8a48515a388c692f48 --- /dev/null +++ b/src/main/java/io/papermc/paper/registry/set/RegistryKeySetImpl.java @@ -0,0 +1,53 @@ +package io.papermc.paper.registry.set; + +import com.google.common.base.Preconditions; +import io.papermc.paper.registry.RegistryAccess; +import io.papermc.paper.registry.RegistryKey; +import io.papermc.paper.registry.TypedKey; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import org.bukkit.Keyed; +import org.bukkit.NamespacedKey; +import org.bukkit.Registry; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.framework.qual.DefaultQualifier; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Nullable; + +@ApiStatus.Internal +@DefaultQualifier(NonNull.class) +record RegistryKeySetImpl(RegistryKey registryKey, List> values) implements RegistryKeySet { // TODO remove Keyed + + static RegistryKeySet create(final RegistryKey registryKey, final Iterable values) { // TODO remove Keyed + final Registry registry = RegistryAccess.registryAccess().getRegistry(registryKey); + final ArrayList> keys = new ArrayList<>(); + for (final T value : values) { + final @Nullable NamespacedKey key = registry.getKey(value); + Preconditions.checkArgument(key != null, value + " does not have a key in " + registryKey); + keys.add(TypedKey.create(registryKey, key)); + } + return new RegistryKeySetImpl<>(registryKey, keys); + } + + RegistryKeySetImpl { + values = List.copyOf(values); + } + + @Override + public boolean contains(final TypedKey valueKey) { + return this.values.contains(valueKey); + } + + @Override + public Collection resolve(final Registry registry) { + final List values = new ArrayList<>(this.values.size()); + for (final TypedKey key : this.values) { + final @Nullable T value = registry.get(key.key()); + Preconditions.checkState(value != null, "Trying to access unbound TypedKey: " + key); + values.add(value); + } + return Collections.unmodifiableList(values); + } +} diff --git a/src/main/java/io/papermc/paper/registry/set/RegistrySet.java b/src/main/java/io/papermc/paper/registry/set/RegistrySet.java new file mode 100644 index 0000000000000000000000000000000000000000..108df480e16131781a52c7bbba2ca6581e4c1ca1 --- /dev/null +++ b/src/main/java/io/papermc/paper/registry/set/RegistrySet.java @@ -0,0 +1,112 @@ +package io.papermc.paper.registry.set; + +import com.google.common.collect.Lists; +import io.papermc.paper.registry.RegistryKey; +import io.papermc.paper.registry.TypedKey; +import io.papermc.paper.registry.tag.Tag; +import org.bukkit.Keyed; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Contract; + +/** + * Represents a collection tied to a registry. + *

+ * There are 2 types of registry sets: + *

    + *
  • {@link Tag} which is a tag from vanilla or a datapack. + * These are obtained via {@link org.bukkit.Registry#getTag(io.papermc.paper.registry.tag.TagKey)}.
  • + *
  • {@link RegistryKeySet} which is a set of of keys linked to values that are present in the registry. These are + * created via {@link #keySet(RegistryKey, Iterable)} or {@link #keySetFromValues(RegistryKey, Iterable)}.
  • + * + *
+ * + * @param registry value type + */ +@ApiStatus.Experimental +public sealed interface RegistrySet permits RegistryKeySet, RegistryValueSet { + + // TODO uncomment when direct holder sets need to be exposed to the API + // /** + // * Creates a {@link RegistryValueSet} from anonymous values. + // *

All values provided must not have keys in the given registry.

+ // * + // * @param registryKey the registry key for the type of these values + // * @param values the values + // * @return a new registry set + // * @param the type of the values + // */ + // @Contract(value = "_, _ -> new", pure = true) + // static @NonNull RegistryValueSet valueSet(final @NonNull RegistryKey registryKey, final @NonNull Iterable values) { + // return RegistryValueSetImpl.create(registryKey, values); + // } + + /** + * Creates a {@link RegistryKeySet} from registry-backed values. + *

All values provided must have keys in the given registry. + *

+ *

If references to actual objects are not available yet, use {@link #keySet(RegistryKey, Iterable)} to + * create an equivalent {@link RegistryKeySet} using just {@link TypedKey TypedKeys}.

+ * + * @param registryKey the registry key for the owner of these values + * @param values the values + * @return a new registry set + * @param the type of the values + * @throws IllegalArgumentException if the registry isn't available yet or if any value doesn't have a key in that registry + */ + @Contract(value = "_, _ -> new", pure = true) + static @NonNull RegistryKeySet keySetFromValues(final @NonNull RegistryKey registryKey, final @NonNull Iterable values) { // TODO remove Keyed + return RegistryKeySetImpl.create(registryKey, values); + } + + /** + * Creates a direct {@link RegistrySet} from {@link TypedKey TypedKeys}. + * + * @param registryKey the registry key for the owner of these keys + * @param keys the keys for the values + * @return a new registry set + * @param the type of the values + */ + @SafeVarargs + static RegistryKeySet keySet(final @NonNull RegistryKey registryKey, final @NonNull TypedKey @NonNull... keys) { // TODO remove Keyed + return keySet(registryKey, Lists.newArrayList(keys)); + } + + /** + * Creates a direct {@link RegistrySet} from {@link TypedKey TypedKeys}. + * + * @param registryKey the registry key for the owner of these keys + * @param keys the keys for the values + * @return a new registry set + * @param the type of the values + */ + @SuppressWarnings("BoundedWildcard") + @Contract(value = "_, _ -> new", pure = true) + static @NonNull RegistryKeySet keySet(final @NonNull RegistryKey registryKey, final @NonNull Iterable> keys) { // TODO remove Keyed + return new RegistryKeySetImpl<>(registryKey, Lists.newArrayList(keys)); + } + + /** + * Get the registry key for this set. + * + * @return the registry key + */ + @NonNull RegistryKey registryKey(); + + /** + * Get the size of this set. + * + * @return the size + */ + int size(); + + /** + * Checks if the registry set is empty. + * + * @return true, if empty + */ + default boolean isEmpty() { + return this.size() == 0; + } +} diff --git a/src/main/java/io/papermc/paper/registry/set/RegistryValueSet.java b/src/main/java/io/papermc/paper/registry/set/RegistryValueSet.java new file mode 100644 index 0000000000000000000000000000000000000000..58e2e42737d48b243854466eb7f7d3a844a86b6e --- /dev/null +++ b/src/main/java/io/papermc/paper/registry/set/RegistryValueSet.java @@ -0,0 +1,34 @@ +package io.papermc.paper.registry.set; + +import java.util.Collection; +import java.util.Iterator; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Unmodifiable; + +/** + * A collection of anonymous values relating to a registry. These + * are values of the same type as the registry, but will not be found + * in the registry, hence, anonymous. + * @param registry value type + */ +@ApiStatus.Experimental +public sealed interface RegistryValueSet extends Iterable, RegistrySet permits RegistryValueSetImpl { + + @Override + default int size() { + return this.values().size(); + } + + /** + * Get the collection of values in this direct set. + * + * @return the values + */ + @NonNull @Unmodifiable Collection values(); + + @Override + default @NonNull Iterator iterator() { + return this.values().iterator(); + } +} diff --git a/src/main/java/io/papermc/paper/registry/set/RegistryValueSetImpl.java b/src/main/java/io/papermc/paper/registry/set/RegistryValueSetImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..4ce5b26a1fcaae7b28ac8ed3c25014b66c266318 --- /dev/null +++ b/src/main/java/io/papermc/paper/registry/set/RegistryValueSetImpl.java @@ -0,0 +1,18 @@ +package io.papermc.paper.registry.set; + +import com.google.common.collect.Lists; +import io.papermc.paper.registry.RegistryKey; +import java.util.List; +import org.jetbrains.annotations.ApiStatus; + +@ApiStatus.Internal +record RegistryValueSetImpl(RegistryKey registryKey, List values) implements RegistryValueSet { + + RegistryValueSetImpl { + values = List.copyOf(values); + } + + static RegistryValueSet create(final RegistryKey registryKey, final Iterable values) { + return new RegistryValueSetImpl<>(registryKey, Lists.newArrayList(values)); + } +} diff --git a/src/main/java/io/papermc/paper/registry/tag/Tag.java b/src/main/java/io/papermc/paper/registry/tag/Tag.java new file mode 100644 index 0000000000000000000000000000000000000000..ae374f68ef9baa16ed90c371f1622de0c0159873 --- /dev/null +++ b/src/main/java/io/papermc/paper/registry/tag/Tag.java @@ -0,0 +1,25 @@ +package io.papermc.paper.registry.tag; + +import io.papermc.paper.registry.set.RegistryKeySet; +import org.bukkit.Keyed; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.jetbrains.annotations.ApiStatus; + +/** + * A named {@link RegistryKeySet} which are created + * via the datapack tag system. + * + * @param + * @see org.bukkit.Tag + * @see org.bukkit.Registry#getTag(TagKey) + */ +@ApiStatus.Experimental +public interface Tag extends RegistryKeySet { // TODO remove Keyed + + /** + * Get the identifier for this named set. + * + * @return the tag key identifier + */ + @NonNull TagKey tagKey(); +} diff --git a/src/main/java/io/papermc/paper/registry/tag/TagKey.java b/src/main/java/io/papermc/paper/registry/tag/TagKey.java new file mode 100644 index 0000000000000000000000000000000000000000..a49d328e95f7fda6567ee6c4f5f1878a2c187277 --- /dev/null +++ b/src/main/java/io/papermc/paper/registry/tag/TagKey.java @@ -0,0 +1,32 @@ +package io.papermc.paper.registry.tag; + +import io.papermc.paper.registry.RegistryKey; +import net.kyori.adventure.key.Key; +import net.kyori.adventure.key.Keyed; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Contract; + +@ApiStatus.Experimental +public sealed interface TagKey extends Keyed permits TagKeyImpl { + + /** + * Creates a new tag key for a registry. + * + * @param registryKey the registry for the tag + * @param key the specific key for the tag + * @return a new tag key + * @param the registry value type + */ + @Contract(value = "_, _ -> new", pure = true) + static @NonNull TagKey create(final @NonNull RegistryKey registryKey, final @NonNull Key key) { + return new TagKeyImpl<>(registryKey, key); + } + + /** + * Get the registry key for this tag key. + * + * @return the registry key + */ + @NonNull RegistryKey registryKey(); +} diff --git a/src/main/java/io/papermc/paper/registry/tag/TagKeyImpl.java b/src/main/java/io/papermc/paper/registry/tag/TagKeyImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..5fde17e97eddcd1d01ee39694c9159d0ff2a09e3 --- /dev/null +++ b/src/main/java/io/papermc/paper/registry/tag/TagKeyImpl.java @@ -0,0 +1,17 @@ +package io.papermc.paper.registry.tag; + +import io.papermc.paper.registry.RegistryKey; +import net.kyori.adventure.key.Key; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.framework.qual.DefaultQualifier; +import org.jetbrains.annotations.ApiStatus; + +@ApiStatus.Internal +@DefaultQualifier(NonNull.class) +record TagKeyImpl(RegistryKey registryKey, Key key) implements TagKey { + + @Override + public String toString() { + return "#" + this.key + " (in " + this.registryKey + ")"; + } +} diff --git a/src/main/java/org/bukkit/Registry.java b/src/main/java/org/bukkit/Registry.java index 82381ea98af1c9216a2989ea83ba4aa90ba69741..d81f687cba38ec930c555b001afc0df93c21731b 100644 --- a/src/main/java/org/bukkit/Registry.java +++ b/src/main/java/org/bukkit/Registry.java @@ -366,6 +366,27 @@ public interface Registry extends Iterable { */ @Nullable T get(@NotNull NamespacedKey key); + // Paper start + /** + * Get the object by its key. + * + * @param key non-null key + * @return item or null if it does not exist + */ + default @Nullable T get(final net.kyori.adventure.key.@NotNull Key key) { + return key instanceof final NamespacedKey nsKey ? this.get(nsKey) : this.get(new NamespacedKey(key.namespace(), key.value())); + } + + /** + * Get the object by its typed key. + * + * @param typedKey non-null typed key + * @return item or null if it does not exist + */ + default @Nullable T get(final io.papermc.paper.registry.@NotNull TypedKey typedKey) { + return this.get(typedKey.key()); + } + // Paper end // Paper start - improve Registry /** @@ -440,6 +461,34 @@ public interface Registry extends Iterable { } // Paper end - improve Registry + // Paper start - RegistrySet API + /** + * Checks if this registry has a tag with the given key. + * + * @param key the key to check for + * @return true if this registry has a tag with the given key, false otherwise + * @see #getTag(io.papermc.paper.registry.tag.TagKey) + */ + @ApiStatus.Experimental + default boolean hasTag(final io.papermc.paper.registry.tag.@NotNull TagKey key) { + throw new UnsupportedOperationException(this + " doesn't have tags"); + } + + /** + * Gets the named registry set (tag) for the given key. + * + * @param key the key to get the tag for + * @return the tag for the key + * @throws java.util.NoSuchElementException if no tag with the given key is found + * @throws UnsupportedOperationException if this registry doesn't have or support tags + * @see #hasTag(io.papermc.paper.registry.tag.TagKey) + */ + @ApiStatus.Experimental + default @NotNull io.papermc.paper.registry.tag.Tag getTag(final io.papermc.paper.registry.tag.@NotNull TagKey key) { + throw new UnsupportedOperationException(this + " doesn't have tags"); + } + // Paper end - RegistrySet API + /** * Returns a new stream, which contains all registry items, which are registered to the registry. * @@ -521,5 +570,23 @@ public interface Registry extends Iterable { return value.getKey(); } // Paper end - improve Registry + + // Paper start - RegistrySet API + @SuppressWarnings("deprecation") + @Override + public boolean hasTag(final io.papermc.paper.registry.tag.@NotNull TagKey key) { + return Bukkit.getUnsafe().getTag(key) != null; + } + + @SuppressWarnings("deprecation") + @Override + public io.papermc.paper.registry.tag.@NotNull Tag getTag(final io.papermc.paper.registry.tag.@NotNull TagKey key) { + final io.papermc.paper.registry.tag.Tag tag = Bukkit.getUnsafe().getTag(key); + if (tag == null) { + throw new java.util.NoSuchElementException("No tag " + key + " found"); + } + return tag; + } + // Paper end - RegistrySet API } } diff --git a/src/main/java/org/bukkit/UnsafeValues.java b/src/main/java/org/bukkit/UnsafeValues.java index 31217b38e769f97801fa1afefeb223d1c755cabd..9bdba60fa96edbc4be5dcf54a815579db887048b 100644 --- a/src/main/java/org/bukkit/UnsafeValues.java +++ b/src/main/java/org/bukkit/UnsafeValues.java @@ -283,4 +283,6 @@ public interface UnsafeValues { // Paper end - lifecycle event API @NotNull java.util.List computeTooltipLines(@NotNull ItemStack itemStack, @NotNull io.papermc.paper.inventory.tooltip.TooltipContext tooltipContext, @Nullable org.bukkit.entity.Player player); // Paper - expose itemstack tooltip lines + + io.papermc.paper.registry.tag.@Nullable Tag getTag(io.papermc.paper.registry.tag.@NotNull TagKey tagKey); // Paper - hack to get tags for non-server backed registries }