[Spigot] ItemMeta の enchant 周りの解説

# はじめに
ItemMeta に格納されているエンチャント系のデータの扱いについて解説

[ここ](https://hub.spigotmc.org/stash/projects/SPIGOT/repos/craftbukkit/browse/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java) のコードを基に書いている。

小見出しの括弧内は In が引数、 Return が戻り値の型を表す。

# getter | setter
## getEnchantLevel (Return : int)
 - 引数に与えられたエンチャントのレベル (int) を返す。
 - 対象の ItemMeta に与えられたエンチャントが含まれていない場合は 0 を返す。

```java
    public int getEnchantLevel(Enchantment ench) {
        Integer level = hasEnchants() ? enchantments.get(ench) : null;
        if (level == null) {
            return 0;
        }
        return level;
    }
```

## getEnchants (Return : ImmutableMap)
 - 対象の ItemMeta がエンチャントを保持している場合は変更不可な Map (ImmutableMap) にエンチャントとレベルの情報を詰めて返す。
    - 内部では普通に HashMap を使用してるが、返却する際には `ImmutableMap.copyOf` で ImmutableMap に変換してから返却している。


 - 対象の ItemMeta がエンチャントを保持していない場合は、空っぽで変更不可な Map を返す。
   - 引数無しの `ImmutableMap#of` なので空っぽのMap を返している。

:::note warn
内部では HashMap を用いているが、外部に情報を渡す際は ImmutableMap に変換される点に注意
:::

```java
    public Map<Enchantment, Integer> getEnchants() {
        return hasEnchants() ? ImmutableMap.copyOf(enchantments) : ImmutableMap.<Enchantment, Integer>of();
    }
```

> [ImmutableMap (Google common collect)](https://guava.dev/releases/23.0/api/docs/com/google/common/collect/ImmutableMap.html)



# has | add | remove

## hasEnchant (In : Enchantment | Return boolean)
引数で与えられたエンチャントを ItemMeta が保持しているかどうか。

ItemMeta でエンチャント情報を持つか確認し、エンチャント情報を格納している HashMap より引数のエンチャントのレベルを取得しチェックして真偽を返す。

- hasEnchants が false であれば false を返す。
- enchantments (エンチャント情報を保持する HashMap) が引数のエンチャントを含んでいれば true を返す。
- enchantments が引数のエンチャントを含んでいなければ false を返す。

```java
    public boolean hasEnchant(Enchantment ench) {
        return hasEnchants() ? enchantments.containsKey(ench) : false;
    }
```

## hasEnchants (Return boolean)
エンチャント情報を持つかどうかを返す。

- enchantments が存在しない、もしくは空っぽな場合は false を返す
- enchantments が存在する場合は true を返す

```java
    public boolean hasEnchants() {
        return !(enchantments == null || enchantments.isEmpty());
    }
```

## addEnchant (Return : boolean | In : Enchantment, int, boolean)
追加するエンチャント種別 (ench)、レベル (level)、制限を設けることの可否 (ignoRerestrictions) を引数に取り、エンチャント情報を追加する。

情報の追加に成功した場合は true, 失敗した場合は false を返す。

---

以下、処理の流れ

1. enchantments が存在しない場合は長さ 4 の HashMap を作成する。
:::note
HashMap を初期化すると長さ 16 になるが、エンチャントを 16 個も格納することは無いから、ということで長さを縮めたのだと考えられる。
:::

1. ignoreRestrictions が true の場合、もしくは level が ench の最小レベル以上且つ ench の最大レベル以下である場合は enchantmens に情報を追加 (put) する。

1. 
- enchantments に put した際、「put する前の値を取得し、尚且つその値が level と異なる (情報の更新を示す)」場合は true を返す。 

- put する前に値が存在しない場合は true を返す。
- 引数で与えられたレベルと put する前に取得した値が等しい場合、更新していないことを意味するため false を返す。

::: note
`HashMap#put` は、 key に以前紐づけられていた値を返し、紐づけられた値が存在しなかった場合は Null を返す。
> [Java HashMap](https://docs.oracle.com/javase/jp/8/docs/api/java/util/HashMap.html)
:::

4\. 上記以外の場合はすべて false を返す。

```java
    public boolean addEnchant(Enchantment ench, int level, boolean ignoreRestrictions) {
        if (enchantments == null) {
            enchantments = new HashMap<Enchantment, Integer>(4);
        }

        if (ignoreRestrictions || level >= ench.getStartLevel() && level <= ench.getMaxLevel()) {
            Integer old = enchantments.put(ench, level);
            return old == null || old != level;
        }
        return false;
    }
```

## removeEnchant (Return : boolean | In : Enchantment)
引数で与えられたエンチャントの削除を試みる。
成功した場合は true を返し、失敗した場合は false を返す。

- hasEnchants を呼び出し、true だった場合は enchantments からエンチャント情報を削除し、削除に成功した場合は true を返す。
  - enchantments に引数のエンチャントが存在しない場合は `HashMap#remove` から null を受け取り、 false を返す。

- hasEnchants を呼び出し、 false だった場合はそのまま false を返す。

```java
    public boolean removeEnchant(Enchantment ench) {
        return hasEnchants() ? enchantments.remove(ench) != null : false;
    }
```

# 終わりに
getEnchants はかなりの初見殺しだと思う。普通、 HashMap が返ってくると思うじゃん。
それと、addEnchants の ignoreRestrictions は true にしておいた方が幸せになれる。