From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
Date: Wed, 2 Mar 2022 13:33:08 -0800
Subject: [PATCH] Add PaperRegistry

PaperRegistry is a server-backed impl of bukkit's Registry interface

diff --git a/src/main/java/io/papermc/paper/registry/PaperRegistry.java b/src/main/java/io/papermc/paper/registry/PaperRegistry.java
new file mode 100644
index 0000000000000000000000000000000000000000..8d1f3c4891870b4239df678dd1e52e9f4ef74b2c
--- /dev/null
+++ b/src/main/java/io/papermc/paper/registry/PaperRegistry.java
@@ -0,0 +1,147 @@
+package io.papermc.paper.registry;
+
+import com.google.common.base.Preconditions;
+import com.google.common.base.Suppliers;
+import net.minecraft.core.Holder;
+import net.minecraft.core.Registry;
+import net.minecraft.core.RegistryAccess;
+import net.minecraft.resources.ResourceKey;
+import net.minecraft.resources.ResourceLocation;
+import net.minecraft.server.MinecraftServer;
+import org.bukkit.Keyed;
+import org.bukkit.NamespacedKey;
+import org.bukkit.craftbukkit.util.CraftNamespacedKey;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.framework.qual.DefaultQualifier;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Supplier;
+
+@DefaultQualifier(NonNull.class)
+public abstract class PaperRegistry<API extends Keyed, MINECRAFT> implements org.bukkit.Registry<API> {
+
+    @SuppressWarnings("FieldMayBeFinal") // non-final for testing
+    private static Supplier<RegistryAccess> REGISTRY_ACCESS = Suppliers.memoize(() -> MinecraftServer.getServer().registryAccess());
+    private static final Map<RegistryKey<?, ?>, PaperRegistry<?, ?>> INTERNAL_REGISTRIES = new HashMap<>();
+    public static final Map<RegistryKey<?, ?>, PaperRegistry<?, ?>> REGISTRIES = Collections.unmodifiableMap(INTERNAL_REGISTRIES);
+    private static final Map<Class<?>, PaperRegistry<?, ?>> REGISTRY_BY_API_CLASS = new HashMap<>();
+    private static final Map<ResourceKey<? extends Registry<?>>, PaperRegistry<?, ?>> REGISTRY_BY_RES_KEY = new HashMap<>();
+
+    private boolean registered;
+    private final RegistryKey<API, MINECRAFT> registryKey;
+    private final Supplier<Registry<MINECRAFT>> registry;
+    private final Map<NamespacedKey, API> cache = new ConcurrentHashMap<>();
+    private final Map<NamespacedKey, ResourceKey<MINECRAFT>> resourceKeyCache = new ConcurrentHashMap<>();
+
+    public PaperRegistry(RegistryKey<API, MINECRAFT> registryKey) {
+        this.registryKey = registryKey;
+        this.registry = Suppliers.memoize(() -> REGISTRY_ACCESS.get().registryOrThrow(this.registryKey.resourceKey()));
+    }
+
+    @Override
+    public @Nullable API get(NamespacedKey key) {
+        return this.cache.computeIfAbsent(key, k -> {
+            final @Nullable MINECRAFT nms = this.registry.get().get(CraftNamespacedKey.toMinecraft(k));
+            if (nms != null) {
+                return this.convertToApi(k, nms);
+            }
+            return null;
+        });
+    }
+
+    public abstract API convertToApi(NamespacedKey key, MINECRAFT nms);
+
+    public API convertToApi(ResourceLocation resourceLocation, MINECRAFT nms) {
+        return this.convertToApi(CraftNamespacedKey.fromMinecraft(resourceLocation), nms);
+    }
+
+    public API convertToApi(Holder<MINECRAFT> nmsHolder) {
+        final Optional<ResourceKey<MINECRAFT>> key = nmsHolder.unwrapKey();
+        if (nmsHolder.isBound() && key.isPresent()) {
+            return this.convertToApi(key.get().location(), nmsHolder.value());
+        } else if (!nmsHolder.isBound() && key.isPresent()) {
+            return this.convertToApi(key.get().location(), this.registry.get().getOrThrow(key.get()));
+        } else if (nmsHolder.isBound() && key.isEmpty()) {
+            final @Nullable ResourceLocation loc = this.registry.get().getKey(nmsHolder.value());
+            if (loc != null) {
+                return this.convertToApi(loc, nmsHolder.value());
+            }
+        }
+        throw new IllegalStateException("Cannot convert " + nmsHolder + " to an API type in: " + this.registryKey);
+    }
+
+    public MINECRAFT getMinecraftValue(API apiValue) {
+        return this.registry.get().getOptional(CraftNamespacedKey.toMinecraft(apiValue.getKey())).orElseThrow();
+    }
+
+    public Holder<MINECRAFT> getMinecraftHolder(API apiValue) {
+        return this.registry.get().getHolderOrThrow(this.resourceKeyCache.computeIfAbsent(apiValue.getKey(), key -> ResourceKey.create(this.registryKey.resourceKey(), CraftNamespacedKey.toMinecraft(key))));
+    }
+
+    @Override
+    public Iterator<API> iterator() {
+        return this.registry.get().keySet().stream().map(key -> this.get(CraftNamespacedKey.fromMinecraft(key))).iterator();
+    }
+
+    public void clearCache() {
+        this.cache.clear();
+    }
+
+    public void register() {
+        if (this.registered) {
+            throw new IllegalStateException("Already registered: " + this.registryKey.apiClass());
+        }
+        INTERNAL_REGISTRIES.put(this.registryKey, this);
+        REGISTRY_BY_API_CLASS.put(this.registryKey.apiClass(), this);
+        REGISTRY_BY_RES_KEY.put(this.registryKey.resourceKey(), this);
+        this.registered = true;
+    }
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+        if (this == o) return true;
+        if (o == null || !PaperRegistry.class.isAssignableFrom(o.getClass())) return false;
+        PaperRegistry<?, ?> that = (PaperRegistry<?, ?>) o;
+        return this.registryKey.equals(that.registryKey);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(this.registryKey);
+    }
+
+    protected static <T> Supplier<Registry<T>> registryFor(ResourceKey<? extends Registry<T>> registryKey) {
+        return Suppliers.memoize(() -> REGISTRY_ACCESS.get().registryOrThrow(registryKey));
+    }
+
+    public static void clearCaches() {
+        for (PaperRegistry<?, ?> registry : INTERNAL_REGISTRIES.values()) {
+            registry.clearCache();
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    public static <T extends Keyed> PaperRegistry<T, ?> getRegistry(Class<T> classOfT) {
+        Preconditions.checkArgument(REGISTRY_BY_API_CLASS.containsKey(classOfT), "No registry for that type");
+        return (PaperRegistry<T, ?>) REGISTRY_BY_API_CLASS.get(classOfT);
+    }
+
+    @SuppressWarnings("unchecked")
+    public static <T> PaperRegistry<?, T> getRegistry(ResourceKey<? extends Registry<T>> resourceKey) {
+        Preconditions.checkArgument(REGISTRY_BY_RES_KEY.containsKey(resourceKey));
+        return (PaperRegistry<?, T>) REGISTRY_BY_RES_KEY.get(resourceKey);
+    }
+
+    @SuppressWarnings("unchecked")
+    public static <A extends Keyed, M> PaperRegistry<A, M> getRegistry(RegistryKey<A, M> registryKey) {
+        Preconditions.checkArgument(INTERNAL_REGISTRIES.containsKey(registryKey));
+        return (PaperRegistry<A, M>) INTERNAL_REGISTRIES.get(registryKey);
+    }
+}
diff --git a/src/main/java/io/papermc/paper/registry/RegistryKey.java b/src/main/java/io/papermc/paper/registry/RegistryKey.java
new file mode 100644
index 0000000000000000000000000000000000000000..6f39e343147803e15e7681c993b8797a629702e7
--- /dev/null
+++ b/src/main/java/io/papermc/paper/registry/RegistryKey.java
@@ -0,0 +1,8 @@
+package io.papermc.paper.registry;
+
+import net.minecraft.core.Registry;
+import net.minecraft.resources.ResourceKey;
+import org.bukkit.Keyed;
+
+public record RegistryKey<API extends Keyed, MINECRAFT>(Class<API> apiClass, ResourceKey<? extends Registry<MINECRAFT>> resourceKey) {
+}
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
index 3eda7f5469803af5fae138b81976ecae2b7c6c2e..9941456a019a34384f892c44df3c73f9416bd560 100644
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
@@ -2032,6 +2032,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
             this.packRepository.setSelected(dataPacks);
             this.worldData.setDataPackConfig(MinecraftServer.getSelectedPacks(this.packRepository));
             this.resources.managers.updateRegistryTags(this.registryAccess());
+            io.papermc.paper.PaperRegistry.clearCaches(); // Paper
             new io.papermc.paper.event.server.ServerResourcesReloadedEvent(cause).callEvent(); // Paper
             if (Thread.currentThread() != this.serverThread) return; // Paper
             //this.getPlayerList().saveAll(); // Paper - we don't need to do this
diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
index 5540c5a881a9d34a8c50d6b6f0694303c76aa610..9af0bd83c03a7e9fba04f7b9f0c66029a7f4b65a 100644
--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
+++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
@@ -512,6 +512,11 @@ public final class CraftMagicNumbers implements UnsafeValues {
     public int nextEntityId() {
         return net.minecraft.world.entity.Entity.nextEntityId();
     }
+
+    @Override
+    public <T extends org.bukkit.Keyed> Registry<T> registryFor(Class<T> classOfT) {
+        return io.papermc.paper.registry.PaperRegistry.getRegistry(classOfT);
+    }
     // Paper end
 
     /**
diff --git a/src/test/java/org/bukkit/support/AbstractTestingBase.java b/src/test/java/org/bukkit/support/AbstractTestingBase.java
index d6b6b7a3d2949126520e8256563afeb2b43bf12c..37934f0da922d696373e7a3a8cf976fcb9015271 100644
--- a/src/test/java/org/bukkit/support/AbstractTestingBase.java
+++ b/src/test/java/org/bukkit/support/AbstractTestingBase.java
@@ -38,6 +38,15 @@ public abstract class AbstractTestingBase {
         MultiPackResourceManager resourceManager = new MultiPackResourceManager(PackType.SERVER_DATA, Collections.singletonList(new VanillaPackResources(ServerPacksSource.BUILT_IN_METADATA, "minecraft")));
         // add tags and loot tables for unit tests
         RegistryAccess.Frozen registry = RegistryAccess.builtinCopy().freeze();
+        // Paper start
+        try {
+            java.lang.reflect.Field field = io.papermc.paper.registry.PaperRegistry.class.getDeclaredField("REGISTRY_ACCESS");
+            field.trySetAccessible();
+            field.set(null, com.google.common.base.Suppliers.ofInstance(registry));
+        } catch (ReflectiveOperationException ex) {
+            throw new IllegalStateException("Could not reflectively set RegistryAccess in PaperRegistry", ex);
+        }
+        // Paper end
         // Register vanilla pack
         DATA_PACK = ReloadableServerResources.loadResources(resourceManager, registry, Commands.CommandSelection.DEDICATED, 0, MoreExecutors.directExecutor(), MoreExecutors.directExecutor()).join();
         // Bind tags