Spigot の NamespacedKey と PersistentDataContainer を解剖する

# NamespacedKey とは何か
NamespacedKey は以後 NK 、PersistentDataContainer は以後 PDC と表記する。
Spigot の JavaDoc から引用
> Represents a String based key which consists of two components - a namespace and a key. Namespaces may only contain lowercase alphanumeric characters, periods, underscores, and hyphens.
> Keys may only contain lowercase alphanumeric characters, periods, underscores, hyphens, and forward slashes.
> 意訳：文字列をキーとして紐づけられた情報を名前空間(namespace)に保存します。
> 名前空間には小文字のアルファベット、ピリオド、アンダースコア、とハイフンを使用することが出来ます。
> 
> 補足：名前空間に使用することが出来る文字列の長さは 256 未満でないといけません。

後述する PDC を利用する際に必要となる鍵です。
Linux Container の根幹技術である Namespace と大体同じイメージです。

コンストラクタにプラグインのインスタンスと重複しない文字列を渡すと、様々なエンティティ、ブロック、ワールドなどが持つ PDC 内の情報にアクセスするための「鍵」を取得することが出来ます。

鍵生成の例
```java
NamespacedKey key = new NamespacedKey(instance, "test-value");
```
(instance はプラグインそのもののインスタンスであるとします。)

上記の例では、プラグイン名がコンテナの名前を、test-value がそのコンテナ内の詳細な住所を指し、それらが格納された NK を PDC に対して鍵として使用することで、格納されている情報にアクセスすることが出来ます。

Java の Map で例えると、プラグイン名が変数名、test-value が key 名に対応します。


# PersistentDataContainer とは何か
PDC は、Minecraft 内のほぼすべてのモノ(アイテム、ブロック、エンティティ、ワールドなど)が持つ、値を保持するコンテナです。

上の NK の解説で Java の Map に例えましたが、NK が変数名と key 名に対応するのであれば、 PDC は Map のインスタンスそのものに対応します。

ただし Map とは異なりデフォルトでは value に取ることが出来る型に制限があります。

デフォルトでは、と前置きをしたのはPersistentDataType を継承したクラスを作成すれば、デフォルトでは受け入れることが出来ない型の値を代入することも出来るためです。

デフォルトで収納することが出来る値の型を以下に示します。

- STRING
- BOOLEAN (真偽値)
- BYTE
- BYTE_ARRAY (バイト配列)
- DOUBLE
- FLOAT
- INTEGER
- INTEGER_ARRAY (整数配列)
- LONG
- LONG_ARRAY (整数配列)
- SHORT
- TAG_CONTAINER
- TAG_CONTAINER_ARRAY (タグ配列) 1.20.4 未満
- LIST (1.20.4 以降)

TAG_CONTAINER / TAG_CONTIANER_ARRAY は PDC を示すので、PDC の中に PDC を格納することが出来る。

---

(2024/01/13 追記)
1.20.4 では TAG_CONTAINER_ARRAY が非推奨になり LIST が追加されました。
ただ、この LIST は PersistentDataType として利用することは出来ず、 ListPersistentDataTypeProvider であるということを示すだけのようです。
実際に使う際は
```java
Namespacedkey key = ~~~;
ItemStack item = ~~~;
PersistentDataContainer pdc = item.getItemMeta().getPersistentDataContainer();

List<String> list = pdc.get(key, ListPersistentDataProvider.strings());
```
のように、`PersistentDataType.LIST` ではなく `ListPersistentDataTypeProvider` から指定することになりそうです。

## ListPersistentDataTypeProvider
ListPersistentDataTypeProvider は PersistentDataType に示されるプリミティブ型の値を永続的に保管するためのリストのタイプ (ListPersistentType) を提供するクラスです。
デフォルトでは boolean, byte, double, float, integer, long, short, string, persistent_data_container のリスト、integer, long, byte の配列のリストを示すタイプを提供します。

また、 `listTypeFrom(PersistentDataType<P, C> elementType)` を利用することで PersistentDataType を実装した独自クラスのリストを示すタイプを提供することもできます。

## ListPersistentDataType
公式によって作成された、 PersistentDataType を実装した独自クラス。(たぶん)
メソッドはデフォルトの PersistentDataType と同じように `fromPrimitive`, `getComplexType`, `getPrimitiveType`, `toPrimitive` が使えるほか、 `elementType()` が実装されている。

---

# 使用方法
インターフェース：PersistentDataHolder を実装したクラスで getPersistentDataContainer メソッドを使用することで PersistentDataContainer を取得することが出来ます。

よく使うことになるであろうItemStack は PersistentDataHolder の実装クラスではないため、一度 ItemMeta を取得してから getPersistentDataContainer をする必要があります。

以下に鍵生成と使用の例を示します。
```java
NamespacedKey nk = new NamespacedKey(instance, "test-value");
ItemStack item = new ItemStack(Material.STONE);
ItemMeta meta = item.getItemMeta();
meta.getPersistentDataContainer().set(nk,PersistentDataType.STRING,"This is a stone.");
item.setItemMeta(meta);
```

上記の例では石の ItemStack を作成し、作成したインスタンスの PDC の 「プラグイン名:test-value」に "This is a stone." という文字列を格納しました。
この ItemStack の test-value から文字列を取り出すには以下のコードを使用します。

```java
PersistentDataContainer container = item.getItemMeta().getPersistentDataContainer();
if (container.has(key, PersistentDataType.STRING) {
  String value = container.get(key, PersistentDataType.STRING);
}
```
PDC から値を取り出す際は、コンテナ内の NK が示す名前空間に値が存在するか確認してからにしましょう。

# 何に使うのか
カスタムアイテムと通常アイテムを見分けたいときや、NBT の代わりとして使う感じになると思います。


```java Setting.java
ItemStack legendarySword = new Itemstack(Material.IRON_SWORD);
ItemStack normalSword = new ItemStack(Material.IRON_SWORD);

NamespacedKey key = new NamespacedKey(instance, "tag");
ItemMeta meta = legendarySword.getItemMeta();
PersistentDataContainer container = meta.getPersistentDataContainer();
container.set(key, PersistentDataType.STRING, "LEGENDARY");
legendarySword.setItemMeta(meta);

List<ItemStack> items = Arrays.asList(legendarySword, normalSword);
for (ItemStack item : items) {
  if (item.has(key, PersistentDataType.STRING) System.out.println("This is a legendarySword!");
  else System.out.println("This is a normalSword.");
}

// 実行結果
//
// This is a legendarySword!
// This is a normalSword.
```

# 参考

> PAPER MC / Persistent Data Conatiner (PDC)
 
https://docs.papermc.io/paper/dev/pdc

> YouTube / Spigot Plugin Development - 58 - Persistent Data Storage (KodySimpson)

https://www.youtube.com/watch?v=wz8oKcPUzHY

> Spigot JavaDoc / Interface PersistentDataContainer

https://hub.spigotmc.org/javadocs/bukkit/org/bukkit/persistence/PersistentDataContainer.html

> Spigot JavaDoc / Interface PersistentDataHolder

https://hub.spigotmc.org/javadocs/bukkit/org/bukkit/persistence/PersistentDataHolder.html

> Spigot JavaDoc / Interface PersistentDataType

https://hub.spigotmc.org/javadocs/bukkit/org/bukkit/persistence/PersistentDataType.html