ItemAdapter.java

package it.fulminazzo.yagl;

import it.fulminazzo.fulmicollection.objects.Refl;
import it.fulminazzo.fulmicollection.structures.tuples.Tuple;
import it.fulminazzo.yagl.items.BukkitItem;
import it.fulminazzo.yagl.items.Item;
import it.fulminazzo.yagl.items.fields.ItemFlag;
import it.fulminazzo.yagl.items.recipes.FurnaceRecipe;
import it.fulminazzo.yagl.items.recipes.Recipe;
import it.fulminazzo.yagl.items.recipes.ShapedRecipe;
import it.fulminazzo.yagl.items.recipes.ShapelessRecipe;
import it.fulminazzo.yagl.utils.EnumUtils;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * A collection of utilities for {@link Item}.
 */
@SuppressWarnings("deprecation")
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class ItemAdapter {
    private static final String ID_KEY = "yagl";

    /**
     * Converts the given {@link ItemStack} to an {@link Item}.
     *
     * @param itemStack the item stack
     * @return the item
     */
    public static @Nullable Item itemStackToItem(final @Nullable ItemStack itemStack) {
        if (itemStack == null) return null;
        Item item = BukkitItem.newItem().setMaterial(itemStack.getType().name()).setAmount(itemStack.getAmount());

        ItemMeta meta = itemStack.getItemMeta();
        invokeNoSuchMethod(() -> {
            if (meta instanceof org.bukkit.inventory.meta.Damageable)
                item.setDurability(((org.bukkit.inventory.meta.Damageable) meta).getDamage());
        }, () -> item.setDurability(itemStack.getDurability()));

        if (meta != null) {
            String displayName = meta.getDisplayName();
            if (displayName != null && !displayName.isEmpty()) item.setDisplayName(displayName);
            List<String> lore = meta.getLore();
            if (lore != null) item.setLore(lore);
            meta.getEnchants().forEach((e, l) -> item.addEnchantments(WrappersAdapter.enchantToWEnchant(e, l)));
            meta.getItemFlags().forEach(f -> item.addItemFlags(EnumUtils.valueOf(ItemFlag.class, f.name())));
            invokeNoSuchMethod(() -> item.setUnbreakable(meta.isUnbreakable()), () ->
                    item.setUnbreakable(meta.spigot().isUnbreakable()));
            invokeNoSuchMethod(() -> {
                if (meta.hasCustomModelData()) item.setCustomModelData(meta.getCustomModelData());
            }, null);
        }
        return item;
    }

    /**
     * Converts the given {@link Item} to an {@link ItemStack}.
     *
     * @param item the item
     * @return the item stack
     */
    public static @Nullable ItemStack itemToItemStack(final @Nullable Item item) {
        if (item == null) return null;
        String material = item.getMaterial();
        if (material == null) throw new IllegalArgumentException("Material cannot be null!");
        ItemStack itemStack = new ItemStack(EnumUtils.valueOf(Material.class, item.getMaterial()), item.getAmount());

        final ItemMeta meta = getItemMeta(itemStack);
        invokeNoSuchMethod(() -> {
            if (meta instanceof org.bukkit.inventory.meta.Damageable)
                ((org.bukkit.inventory.meta.Damageable) meta).setDamage(item.getDurability());
        }, () -> itemStack.setDurability((short) item.getDurability()));

        if (meta != null) {
            String displayName = item.getDisplayName();
            if (!displayName.isEmpty()) meta.setDisplayName(displayName);
            meta.setLore(item.getLore());
            item.getEnchantments().forEach(e -> {
                @NotNull Tuple<Enchantment, Integer> tuple = WrappersAdapter.wEnchantToEnchant(e);
                meta.addEnchant(tuple.getKey(), tuple.getValue(), true);
            });
            item.getItemFlags().forEach(f -> meta.addItemFlags(EnumUtils.valueOf(org.bukkit.inventory.ItemFlag.class, f.name())));
            invokeNoSuchMethod(() -> meta.setUnbreakable(item.isUnbreakable()), () ->
                    meta.spigot().setUnbreakable(item.isUnbreakable()));
            invokeNoSuchMethod(() -> {
                int modelData = item.getCustomModelData();
                if (modelData > 0) meta.setCustomModelData(modelData);
            }, null);
            itemStack.setItemMeta(meta);
        }
        return itemStack;
    }

    private static ItemMeta getItemMeta(final @NotNull ItemStack itemStack) {
        ItemMeta meta = itemStack.getItemMeta();
        if (meta == null) return Bukkit.getItemFactory().getItemMeta(itemStack.getType());
        else return meta;
    }

    /**
     * Tries to execute the first given {@link Runnable}.
     * If it fails for a {@link NoSuchMethodError}, the second one is called.
     *
     * @param get the first function
     * @param orElse the second function
     */
    private static void invokeNoSuchMethod(final @NotNull Runnable get, final @Nullable Runnable orElse) {
        try {
            get.run();
        } catch (NoSuchMethodError | NoClassDefFoundError e) {
            if (orElse != null) orElse.run();
        }
    }

    /**
     * Converts the {@link Recipe} to a Minecraft {@link org.bukkit.inventory.Recipe}.
     *
     * @param recipe the recipe
     * @return the recipe
     */
    public static org.bukkit.inventory.Recipe recipeToMinecraft(final @Nullable Recipe recipe) {
        if (recipe == null) return null;
        org.bukkit.inventory.Recipe result;
        if (recipe instanceof ShapedRecipe)
            result = recipeToMinecraft((ShapedRecipe) recipe);
        else if (recipe instanceof ShapelessRecipe)
            result = recipeToMinecraft((ShapelessRecipe) recipe);
        else if (recipe instanceof FurnaceRecipe)
            result = recipeToMinecraft((FurnaceRecipe) recipe);
        else throw new IllegalArgumentException("Unrecognized recipe type: " + recipe.getClass());
        return result;
    }

    @SuppressWarnings("DataFlowIssue")
    private static org.bukkit.inventory.Recipe recipeToMinecraft(final @Nullable ShapedRecipe recipe) {
        if (recipe == null) return null;

        final ItemStack output = recipe.getOutput().copy(BukkitItem.class).create();

        Refl<org.bukkit.inventory.ShapedRecipe> r;
        try {
            final Object namespacedKey = WrappersAdapter.getNamespacedKey(ID_KEY, recipe.getId());
            r = new Refl<>(org.bukkit.inventory.ShapedRecipe.class, namespacedKey, output);
        } catch (RuntimeException e) {
            r = new Refl<>(org.bukkit.inventory.ShapedRecipe.class, output);
        }

        StringBuilder charShape = new StringBuilder();
        ShapedRecipe.Shape shape = recipe.getShape();
        char c = 'A';
        for (int i = 0; i < shape.getRows(); i++) {
            for (int j = 0; j < shape.getColumns(); j++) charShape.append(c++);
            charShape.append(",");
        }
        r.getObject().shape(charShape.toString().split(","));

        List<Item> ingredients = recipe.getIngredients();
        Map<Character, Object> finalIngredients = r.getFieldObject("ingredients");

        for (char ch = 'A'; ch < c; ch++)
            finalIngredients.put(ch, getItemOrRecipeChoice(ingredients.get(ch - 'A')));

        return r.getObject();
    }

    private static org.bukkit.inventory.Recipe recipeToMinecraft(final @Nullable ShapelessRecipe recipe) {
        if (recipe == null) return null;

        final List<Object> ingredients = recipe.getIngredients().stream()
                .map(ItemAdapter::getItemOrRecipeChoice)
                .collect(Collectors.toList());
        final ItemStack output = recipe.getOutput().copy(BukkitItem.class).create();

        Refl<org.bukkit.inventory.ShapelessRecipe> r;
        try {
            final Object namespacedKey = WrappersAdapter.getNamespacedKey(ID_KEY, recipe.getId());
            r = new Refl<>(org.bukkit.inventory.ShapelessRecipe.class, namespacedKey, output);
        } catch (RuntimeException e) {
            r = new Refl<>(org.bukkit.inventory.ShapelessRecipe.class, output);
        }

        r.setFieldObject("ingredients", ingredients);

        return r.getObject();
    }

    private static org.bukkit.inventory.Recipe recipeToMinecraft(final @Nullable FurnaceRecipe recipe) {
        if (recipe == null) return null;

        final Object ingredient = getItemOrRecipeChoice(recipe.getIngredients().get(0));
        final ItemStack output = recipe.getOutput().copy(BukkitItem.class).create();
        final int cookingTime = (int) (recipe.getCookingTime() * Constants.TICKS_IN_SECOND);
        final float experience = recipe.getExperience();

        Refl<org.bukkit.inventory.FurnaceRecipe> r;
        try {
            final Object namespacedKey = WrappersAdapter.getNamespacedKey(ID_KEY, recipe.getId());
            r = new Refl<>(org.bukkit.inventory.FurnaceRecipe.class, namespacedKey, output, Material.STONE, experience, cookingTime);
        } catch (RuntimeException e) {
            r = new Refl<>(org.bukkit.inventory.FurnaceRecipe.class, output, Material.STONE);
        }

        r.setFieldObject("ingredient", ingredient);

        return r.getObject();
    }

    private static Object getItemOrRecipeChoice(final @Nullable Item item) {
        if (item == null) return null;
        ItemStack itemStack = item.copy(BukkitItem.class).create();
        try {
            return new Refl<>("org.bukkit.inventory.RecipeChoice.ExactChoice", itemStack).getObject();
        } catch (Exception e) {
            return itemStack;
        }
    }
}