<file_sep>package yellowsquid.yetanotheritem.recipe;

import net.minecraft.block.Block;
import net.minecraft.client.Minecraft;
import net.minecraft.init.Blocks;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.oredict.OreDictionary;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import yellowsquid.yetanotheritem.YAIException;
import yellowsquid.yetanotheritem.YetAnotherItem;
import yellowsquid.yetanotheritem.util.ItemStackSet;

import javax.annotation.Nonnull;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.File;
import java.io.IOException;
import java.util.*;

public class RecipeHelper {
    private static final File CONFIG_DIR  = new File(Minecraft.getMinecraft().mcDataDir, "config/yetanotheritem/");
    private static final File RECIPES_XML = new File(CONFIG_DIR, "recipes.xml");

    /**
     * Registers the recipes in the recipe file via their respective handlers.
     *
     * @throws YAIException
     *   if an error occurs during parsing the file.
     */
    public static void registerVanillaRecipes() throws YAIException {
        YetAnotherItem.logInfo("Setting up DOM builder.");
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        factory.setValidating(true);
        factory.setIgnoringComments(true);
        factory.setIgnoringElementContentWhitespace(true);

        YetAnotherItem.logInfo("Creating DOM builder.");
        DocumentBuilder builder;
        try {
            builder = factory.newDocumentBuilder();
        } catch (ParserConfigurationException e) {
            YetAnotherItem.logError("Couldn't create XML parser.");
            throw new YAIException(e);
        }

        YetAnotherItem.logInfo("Building DOM.");
        builder.setErrorHandler(new YAIDOMErrorHandler());
        Document doc;
        try {
            doc = builder.parse(RECIPES_XML);
        } catch (SAXException | IOException e) {
            YetAnotherItem.logError("Couldn't parse recipe file.");
            throw new YAIException(e);
        }

        YetAnotherItem.logInfo("Parsing methods.");
        NodeList nodes = doc.getElementsByTagName("method");
        for (int i = 0; i < nodes.getLength(); i++) {
            parseMethod(nodes.item(i));
        }
        YetAnotherItem.logInfo("Created all recipes from file.");
    }

    /**
     * Parses a particular crafting method.
     *
     * @param method
     *   the DOM node representing the method.
     *
     * @throws YAIException
     *   if an error occurs during loading the classes.
     */
    @SuppressWarnings("unchecked")
    private static void parseMethod(@Nonnull Node method) throws YAIException {
        NamedNodeMap attrs   = method.getAttributes();
        String       name    = attrs.getNamedItem("name").getNodeValue();
        String       handler = attrs.getNamedItem("handler").getNodeValue();

        YetAnotherItem.logInfo("Finding handler class for " + name + ".");
        Class<? extends YAIRecipeHandler> handlerClazz;
        try {
            handlerClazz = (Class<? extends YAIRecipeHandler>) RecipeHelper.class.getClassLoader().loadClass(handler);
        } catch (ClassNotFoundException e) {
            YetAnotherItem.logError("Cannot find handler class.");
            throw new YAIException(e);
        }

        YetAnotherItem.logInfo("Creating handler instance.");
        YAIRecipeHandler recipeHandler;
        try {
            recipeHandler = handlerClazz.newInstance();
        } catch (InstantiationException | IllegalAccessException e) {
            YetAnotherItem.logError("Cannot instantiate handler class.");
            throw new YAIException(e);
        }

        YetAnotherItem.logInfo("Parsing recipes");
        recipeHandler.setMethod(name);
        NodeList recipes = method.getChildNodes();

        for (int i = 0; i < recipes.getLength(); i++) {
            if (Objects.equals(recipes.item(i).getNodeName(), "recipe")) {
                parseRecipe(recipeHandler, recipes.item(i));
            }
        }
    }

    /**
     * Parses a recipe and hands it over to the handler.
     *
     * @param handler
     *   the crafting method handler.
     * @param recipe
     *   the DOM node representing the recipe.
     *
     * @throws YAIException
     *   if one of the stacks does not exist.
     */
    private static void parseRecipe(@Nonnull YAIRecipeHandler handler, @Nonnull Node recipe) throws YAIException {
        String                         inputShape = recipe.getAttributes().getNamedItem("input").getNodeValue();
        Map<Character, Set<ItemStack>> inputs     = new HashMap<>();
        ItemStack                      output     = null;
        NodeList                       children   = recipe.getChildNodes();

        for (int i = 0; i < children.getLength(); i++) {
            Node           node   = children.item(i);
            Set<ItemStack> stacks = parseItemStack(node);

            if (Objects.equals(node.getNodeName(), "input")) {
                String key = node.getAttributes().getNamedItem("key").getNodeValue();

                if (key.length() > 1) {
                    YetAnotherItem.logWarn("Key has length greater than one. Continuing regardless.");
                } else if (key.length() == 0) {
                    YetAnotherItem.logError("Key is invalid.");
                    throw new YAIException();
                }

                inputs.put(key.charAt(0), stacks);
            } else if (Objects.equals(node.getNodeName(), "output")) {
                output = stacks.iterator().next();
            }
        }

        handler.addRecipes(inputShape, inputs, output);
    }

    /**
     * Parses an item node and returns the list of valid stacks.
     *
     * @param stack
     *   the DOM node for the item.
     *
     * @return the list of stacks matching the node.
     *
     * @throws YAIException
     *   if no such stack exists.
     */
    @Nonnull
    private static Set<ItemStack> parseItemStack(@Nonnull Node stack) throws YAIException {
        Set<ItemStack> stacks = new ItemStackSet();

        NamedNodeMap attrs         = stack.getAttributes();
        String       itemString    = attrs.getNamedItem("item").getNodeValue();
        String       blockString   = attrs.getNamedItem("block").getNodeValue();
        String       metaString    = attrs.getNamedItem("meta").getNodeValue();
        String       oredictString = "";
        if (attrs.getNamedItem("oredict") != null) {
            oredictString = attrs.getNamedItem("oredict").getNodeValue();
        }
        int count = 1;
        if (attrs.getNamedItem("count") != null) {
            count = Integer.parseInt(attrs.getNamedItem("count").getNodeValue());
        }

        int meta = 0;
        if (metaString.length() > 0) {
            meta = Integer.parseInt(metaString);
        }

        ItemStack itemStack = null;
        if (itemString.length() > 0) {
            Item item = Item.REGISTRY.getObject(new ResourceLocation(itemString));
            if (item != null) {
                itemStack = new ItemStack(item, count, meta);
            }
        } else if (blockString.length() > 0) {
            Block block = Block.REGISTRY.getObject(new ResourceLocation(blockString));
            if (block != Blocks.AIR) {
                itemStack = new ItemStack(block, count, meta);
            }
        }

        if (itemStack != null) {
            stacks.add(itemStack);
        } else if (oredictString.length() > 0) {
            List<ItemStack> stackList = OreDictionary.getOres(oredictString);
            stackList.stream().forEach(stacks::add);
        }

        if (stacks.size() == 0) {
            YetAnotherItem.logError(
              "Stack does not exist. item=" + itemString + ", block=" + blockString + ", oredict=" + oredictString);
            throw new YAIException();
        }

        return stacks;
    }
}
<file_sep>package yellowsquid.yetanotheritem;

import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.inventory.Container;
import net.minecraft.inventory.IInventory;
import net.minecraft.inventory.Slot;

import javax.annotation.ParametersAreNonnullByDefault;

public class ItemListContainer extends Container {
    public ItemListContainer(int rows, int columns, IInventory inventory) {
        super();
        int y = 24;
        for (int i = 0; i < rows; i++) {
            int x = 2;
            for (int j = 0; j < columns; j++) {
                addSlotToContainer(new Slot(inventory, i * columns + j, x, y));
                x += 18;
            }
            y += 18;
        }
    }

    @Override
    @ParametersAreNonnullByDefault
    public boolean canInteractWith(EntityPlayer playerIn) {
        return true;
    }
}
<file_sep>package yellowsquid.yetanotheritem.gui;

import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.gui.GuiTextField;
import net.minecraft.client.gui.inventory.GuiContainer;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.inventory.ClickType;
import net.minecraft.inventory.InventoryBasic;
import net.minecraft.inventory.Slot;
import net.minecraft.item.ItemStack;
import net.minecraft.util.ResourceLocation;
import org.lwjgl.input.Keyboard;
import yellowsquid.yetanotheritem.ItemListContainer;
import yellowsquid.yetanotheritem.YetAnotherItem;
import yellowsquid.yetanotheritem.api.YAICraftMethod;
import yellowsquid.yetanotheritem.event.ItemListEvent;

import javax.annotation.Nullable;
import javax.annotation.ParametersAreNonnullByDefault;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * The GUI that displays the list of all craftable items.
 *
 * @author <NAME>
 */
// TODO: remove suppressed warnings
@SuppressWarnings({"unused", "WeakerAccess"})
@ParametersAreNonnullByDefault
public class YAIItemList extends GuiContainer {
    /**
     * The mod instance.
     */
    private static final YetAnotherItem   YAI        = YetAnotherItem.getInstance();
    /**
     * The rows in the list inventory
     */
    private static final int              ROWS       = 8;
    /**
     * The columns in the list inventory.
     */
    private static final int              COLUMNS    = 9;
    /**
     * The path to the list background texture.
     */
    private static final ResourceLocation BACKGROUND = new ResourceLocation("yetanotheritem",
                                                                            "textures/gui/container/item_list.png");
    /**
     * ARGB for the colour white.
     */
    private static final int              WHITE      = 0xffffffff;
    /**
     * The parent container.
     */
    private final GuiContainer         parent;
    /**
     * The search box GUI element.
     */
    private       GuiTextField         searchBox;
    /**
     * The list of stacks.
     */
    private       List<ItemStack>      stacks;
    /**
     * The list of crafting methods from the recipes.
     */
    private       List<YAICraftMethod> methods;
    /**
     * The crafting method filter.
     */
    private       YAICraftMethod       method;
    /**
     * The page number.
     */
    private       int                  page;
    /**
     * Whether or not the inventory slots need to be reassigned.
     */
    private       boolean              dirty;

    /**
     * Creates a new item list GUI from a given container.
     *
     * @param parent
     *   the parent GUI container.
     */
    public YAIItemList(GuiContainer parent) {
        super(new ItemListContainer(ROWS, COLUMNS, new InventoryBasic("", true, ROWS * COLUMNS)));
        this.parent = parent;
        dirty = true;
        xSize = COLUMNS * 18 + 2;
        ySize = ROWS * 18 + 46;
        methods = new ArrayList<>();

        methods.add(null);
        for (YAICraftMethod method : YAI.getMethods()) {
            methods.add(method);
        }
    }

    /**
     * Gets the parent GUI container.
     *
     * @return the parent GUI container.
     */
    public GuiContainer getParent() {
        return parent;
    }

    /**
     * Gets the current list of stacks to show.
     *
     * @return the list of stacks to show.
     */
    public List<ItemStack> getStacks() {
        return stacks;
    }

    /**
     * Sets the current list of stacks to show.
     *
     * @param stacks
     *   the new list of stacks to show.
     */
    public void setStacks(List<ItemStack> stacks) {
        YetAnotherItem.logInfo("Changed stacks to show in item list.");
        dirty = true;
        this.stacks = stacks;
    }

    /**
     * Gets the current crafting method filter.
     *
     * @return the crafting method filter.
     */
    @Nullable
    public YAICraftMethod getMethod() {
        return method;
    }

    /**
     * Sets the current crafting method filter.
     *
     * @param method
     *   the new crafting method filter.
     */
    public void setMethod(@Nullable YAICraftMethod method) {
        dirty = true;
        this.method = method;
        YetAnotherItem.logInfo("Changed crafting method filter.");
        ItemListEvent.MethodChange event = new ItemListEvent.MethodChange(this, method);
        YAI.getItemListeners().forEach(listener -> listener.onMethodChange(event));
    }

    /**
     * Gets the current page number.
     *
     * @return the current page number.
     */
    public int getPage() {
        return page;
    }

    /**
     * Sets the current page number.
     *
     * @param page
     *   the new page number.
     */
    public void setPage(int page) {
        dirty = true;
        this.page = page;
        YetAnotherItem.logInfo("Changed item list page.");
        ItemListEvent.PageChange event = new ItemListEvent.PageChange(this, page);
        YAI.getItemListeners().forEach(listener -> listener.onPageChange(event));
    }

    /**
     * Assigns the various item stacks to the different inventory.
     */
    private void setSlots() {
        List<ItemStack> inventory = inventorySlots.getInventory();
        int             index     = ROWS * COLUMNS * page;
        int             i         = 0;

        // Put stacks in slots.
        while (i < inventory.size() && index < stacks.size()) {
            inventorySlots.putStackInSlot(i, stacks.get(index));
            i++;
            index++;
        }

        // Empty othe slots.
        while (i < inventory.size()) {
            inventorySlots.putStackInSlot(i, null);
            i++;
        }
    }

    @Override
    public void onGuiClosed() {
        super.onGuiClosed();
        Keyboard.enableRepeatEvents(false);
    }

    @Override
    public void initGui() {
        Keyboard.enableRepeatEvents(true);
        super.initGui();
        buttonList.clear();

        func_189646_b(new GuiButton(ButtonId.BACK.ordinal(), 5, 5, 20, 20, "X"));
        func_189646_b(new GuiButton(ButtonId.LAST_PAGE.ordinal(), guiLeft + 1, guiTop + ySize - 21, 20, 20, "<"));
        func_189646_b(
          new GuiButton(ButtonId.NEXT_PAGE.ordinal(), guiLeft + xSize - 21, guiTop + ySize - 21, 20, 20, ">"));
        func_189646_b(new GuiButton(ButtonId.NEXT_METHOD.ordinal(), guiLeft + xSize - 21, guiTop + 1, 20, 20, ">"));
        func_189646_b(new GuiButton(ButtonId.LAST_METHOD.ordinal(), guiLeft + 1, guiTop + 1, 20, 20, "<"));

        searchBox = new GuiTextField(99, fontRendererObj, width / 2 - 50, height - 25, 100, 20);
    }

    @Override
    public void drawScreen(int mouseX, int mouseY, float partialTicks) {
        // Change the items in the slots if necessary
        if (dirty) {
            setSlots();
        }

        super.drawScreen(mouseX, mouseY, partialTicks);

        // Draw page information
        drawCenteredString(fontRendererObj, (page + 1) + " of " + (pageCount()), width / 2, guiTop + ySize - 15, WHITE);

        // Draw crafting method information
        if (method != null) {
            drawCenteredString(fontRendererObj, method.getName(), width / 2, guiTop + 7, WHITE);
        } else {
            drawCenteredString(fontRendererObj, "All", width / 2, guiTop + 7, WHITE);
        }

        // Draw search box
        searchBox.drawTextBox();
    }

    @Override
    protected void actionPerformed(GuiButton button) throws IOException {
        super.actionPerformed(button);
        YetAnotherItem.logInfo("Button pressed.");

        if (button.id == ButtonId.BACK.ordinal()) {
            // Display parent screen
            mc.displayGuiScreen(parent);
        } else if (button.id == ButtonId.NEXT_PAGE.ordinal() && page < pageCount() - 1) {
            // Move to next page (if it exists)
            setPage(page + 1);
        } else if (button.id == ButtonId.LAST_PAGE.ordinal() && page > 0) {
            // Move to previous page (if it exists)
            setPage(page - 1);
        } else if (button.id == ButtonId.NEXT_METHOD.ordinal()) {
            // Switches to next crafting filter
            int index = methods.indexOf(method) + 1;
            if (index >= methods.size()) {
                index = 0;
            }
            setMethod(methods.get(index));
        } else if (button.id == ButtonId.LAST_METHOD.ordinal()) {
            // Switches to previous crafting filter
            int index = methods.indexOf(method) - 1;
            if (index < 0) {
                index = methods.size() - 1;
            }
            setMethod(methods.get(index));
        }

        ItemListEvent.ButtonPress event = new ItemListEvent.ButtonPress(this, button);
        YAI.getItemListeners().forEach(listener -> listener.onButtonPress(event));
    }

    @Override
    protected void mouseClicked(int mouseX, int mouseY, int mouseButton) throws IOException {
        super.mouseClicked(mouseX, mouseY, mouseButton);
        searchBox.mouseClicked(mouseX, mouseY, mouseButton);
    }

    @Override
    protected void mouseClickMove(int mouseX, int mouseY, int clickedMouseButton, long timeSinceLastClick) {
        // No-Op
    }

    @Override
    protected void drawGuiContainerBackgroundLayer(float partialTicks, int mouseX, int mouseY) {
        GlStateManager.color(1.0f, 1.0f, 1.0f, 1.0f);
        mc.getTextureManager().bindTexture(BACKGROUND);
        drawTexturedModalRect(guiLeft, guiTop, 0, 0, xSize, ySize);
    }

    @Override
    protected void handleMouseClick(@Nullable Slot slotIn, int slotId, int mouseButton, ClickType type) {
        if (slotIn != null && slotIn.getStack() != null && mouseButton == 0) {
            YetAnotherItem.logInfo("Stack selected.");
            ItemListEvent.StackSelect event = new ItemListEvent.StackSelect(this, slotIn.getStack());
            YAI.getItemListeners().forEach(listener -> listener.onStackSelect(event));
        }
    }

    @Override
    protected void keyTyped(char typedChar, int keyCode) throws IOException {
        super.keyTyped(typedChar, keyCode);
        if (searchBox.textboxKeyTyped(typedChar, keyCode)) {
            YetAnotherItem.logInfo("Search bar changed status.");
            ItemListEvent.FilterChange event = new ItemListEvent.FilterChange(this, searchBox.getText());
            YAI.getItemListeners().forEach(listener -> listener.onFilterChange(event));
        }
    }

    /**
     * Gets the maximum number of pages to display,
     *
     * @return the maximum number of pages to display.
     */
    private int pageCount() {
        float ratio = (stacks.size() * 1.0f) / (ROWS * COLUMNS * 1.0f);
        return (int) Math.ceil(ratio);
    }

    private enum ButtonId {
        BACK,
        NEXT_PAGE,
        LAST_PAGE,
        NEXT_METHOD,
        LAST_METHOD
    }
}
<file_sep>package yellowsquid.yetanotheritem.recipe;

import net.minecraft.client.gui.inventory.GuiContainer;
import net.minecraft.client.gui.inventory.GuiCrafting;
import net.minecraft.client.gui.inventory.GuiInventory;
import net.minecraft.item.ItemStack;
import yellowsquid.yetanotheritem.api.YAICraftMethod;
import yellowsquid.yetanotheritem.api.YAIRecipe;

import javax.annotation.Nonnull;
import java.util.*;

/**
 * Shapeless crafting recipes
 */
public class Shapeless implements YAIRecipe {
    private final ItemStack        output;
    private       Set<ItemStack>[] inputs;

    /**
     * Creates a new shapeless crafting recipe and method.
     *
     * @param inputs
     *   the sets of stacks to craft.
     * @param output
     *   the resulting stack.
     */
    public Shapeless(Set<ItemStack>[] inputs, ItemStack output) {
        this.output = output;
        this.inputs = inputs;
    }

    @Override
    public YAICraftMethod getMethod() {
        return new ShapelessMethod();
    }

    @Nonnull
    @Override
    public ItemStack getOutput() {
        return output;
    }

    @Nonnull
    @Override
    public Set<ItemStack>[] getInputs() {
        return inputs;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (!(o instanceof Shapeless)) {
            return false;
        }
        Shapeless shapeless = (Shapeless) o;
        return ItemStack.areItemStacksEqual(output, shapeless.output) && Arrays.equals(inputs, shapeless.inputs);
    }

    @Override
    public int hashCode() {
        return Objects.hash(output, inputs);
    }

    private class ShapelessMethod extends YAICraftMethod {
        /**
         * Creates a new crafting method.
         */
        private ShapelessMethod() {
            super("crafting.method.shapeless");
        }

        @Override
        public Collection<Class<? extends GuiContainer>> getContainerClasses() {
            Collection<Class<? extends GuiContainer>> classes = new ArrayList<>();
            if (inputs.length <= 4) {
                classes.add(GuiInventory.class);
            }
            classes.add(GuiCrafting.class);
            return classes;
        }
    }
}
<file_sep>package yellowsquid.yetanotheritem.gui;

import net.minecraft.client.gui.GuiScreen;

/**
 * The GUI that displays help for how to craft various items.
 *
 * @author <NAME>
 */
public class YAIHelpGUI extends GuiScreen {
    /**
     * The parent item list.
     */
    private final YAIItemList parent;

    /**
     * Creates a new crafting help GUI screen.
     *
     * @param parent
     *   the parent item list.
     */
    protected YAIHelpGUI(YAIItemList parent) {
        this.parent = parent;
    }

    /**
     * Gets the parent item list.
     *
     * @return the parent item list.
     */
    public YAIItemList getParent() {
        return parent;
    }
}
<file_sep>package yellowsquid.yetanotheritem;

public class YAIException extends Exception {
    public YAIException(Exception e) {
        super(e);
    }

    public YAIException() {
        super();
    }
}
<file_sep>package yellowsquid.yetanotheritem.api;

import net.minecraft.client.gui.inventory.GuiContainer;
import net.minecraft.client.resources.I18n;

import java.util.Collection;
import java.util.Objects;

/**
 * A way of crafting items.
 *
 * @author <NAME>
 */
// TODO: remove suppressed warnings
@SuppressWarnings({"WeakerAccess", "unused"})
public abstract class YAICraftMethod {
    /**
     * The key for the name of the crafting method in the language files.
     */
    protected final String name;

    /**
     * Creates a new crafting method.
     *
     * @param name
     *   the key for the name of the crafting method in the language files.
     */
    protected YAICraftMethod(String name) {
        this.name = name;
    }

    /**
     * Gets the name of the crafting method.
     *
     * @return the default name of the method.
     */
    public String getName() {
        return I18n.format(name);
    }

    /**
     * Gets the classes that the crafting method can be used in.
     *
     * @return the classes that the crafting method can be used in.
     */
    public abstract Collection<Class<? extends GuiContainer>> getContainerClasses();

    /**
     * {@inheritDoc}
     *
     * @param o
     *   {@inheritDoc}
     *
     * @return {@inheritDoc}
     */
    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (!(o instanceof YAICraftMethod)) {
            return false;
        }
        YAICraftMethod method = (YAICraftMethod) o;
        return Objects.equals(name, method.name);
    }

    /**
     * {@inheritDoc}
     *
     * @return {@inheritDoc}
     */
    @Override
    public int hashCode() {
        return Objects.hash(name);
    }
}
<file_sep>package yellowsquid.yetanotheritem.recipe;

import net.minecraft.item.ItemStack;
import yellowsquid.yetanotheritem.YAIException;

import java.util.Map;
import java.util.Set;

/**
 * Creates crafting recipes from elements of the recipe file.
 */
public abstract class YAIRecipeHandler {
    public YAIRecipeHandler() {}

    /**
     * Sets the crafting method the recipes use.
     *
     * @param name
     *   the name of the crafting method.
     *
     * @throws YAIException
     *   if the handler cannot process the crafting method.
     */
    public abstract void setMethod(String name) throws YAIException;

    /**
     * Creates and registers a new crafting recipe
     *
     * @param shape
     *   the shape of the input items.
     * @param inputs
     *   a map from the input keys to the possible item stacks.
     */
    public abstract void addRecipes(String shape, Map<Character, Set<ItemStack>> inputs, ItemStack output);
}
<file_sep>package yellowsquid.yetanotheritem.api;

import net.minecraft.item.ItemStack;

import javax.annotation.Nonnull;
import java.util.Set;

/**
 * A crafting recipe for a single item. It has a crafting method, a series of inputs, and a single output.
 *
 * @author <NAME>
 */
// TODO: remove suppressed warnings.
@SuppressWarnings("unused")
public interface YAIRecipe {
    /**
     * Gets the method of crafting the recipe requires.
     *
     * @return the crafting method.
     */
    YAICraftMethod getMethod();

    /**
     * Gets the result of crafting the recipe.
     *
     * @return the result of the recipe.
     */
    @Nonnull
    ItemStack getOutput();

    /**
     * Gets the inputs to the crafting recipe. The returned array must have size equal to the number of input slots to
     * its crafting method interface. The array may contain null elements.
     *
     * @return the inputs to the crafting recipe.
     */
    @Nonnull
    Set<ItemStack>[] getInputs();
}
<file_sep>package yellowsquid.yetanotheritem.event;

import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.gui.inventory.GuiContainer;
import net.minecraft.item.ItemStack;
import yellowsquid.yetanotheritem.api.YAICraftMethod;
import yellowsquid.yetanotheritem.api.YAIRecipe;
import yellowsquid.yetanotheritem.gui.YAIHelpGUI;
import yellowsquid.yetanotheritem.gui.YAIItemList;

import java.util.Objects;

/**
 * An event generated for a crafting help GUI.
 */
// TODO: remove suppressed warnings.
// TODO: inherit docs.
@SuppressWarnings({"WeakerAccess", "unused"})
public class HelpGuiEvent {
    /**
     * The GUI the event occurred to.
     */
    protected final YAIHelpGUI gui;

    /**
     * Creates a new crafting help GUI event.
     *
     * @param gui
     *   the GUI the event occurred on.
     */
    public HelpGuiEvent(YAIHelpGUI gui) {
        this.gui = gui;
    }

    /**
     * Gets the GUI the event occurred to.
     *
     * @return the GUI the event occurred to.
     */
    public YAIHelpGUI getGui() {
        return gui;
    }

    /**
     * {@inheritDoc}
     *
     * @param o
     *   {@inheritDoc}
     *
     * @return {@inheritDoc}
     */
    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (!(o instanceof yellowsquid.yetanotheritem.event.HelpGuiEvent)) {
            return false;
        }
        yellowsquid.yetanotheritem.event.HelpGuiEvent event = (yellowsquid.yetanotheritem.event.HelpGuiEvent) o;
        return Objects.equals(gui, event.gui);
    }

    /**
     * {@inheritDoc}
     *
     * @return {@inheritDoc}
     */
    @Override
    public int hashCode() {
        return Objects.hash(gui);
    }

    @Override
    public String toString() {
        return "HelpGuiEvent{" + gui + '}';
    }

    /**
     * An event generated when a crafting help GUI returns to the item list GUI.
     */
    public static class Cancel extends HelpGuiEvent {
        /**
         * The item list the GUI is returning to.
         */
        private final YAIItemList parent;

        /**
         * Creates a new cancel effect.
         *
         * @param gui
         *   the GUI the event occurred to.
         * @param parent
         *   the item list the GUI is returning to.
         */
        public Cancel(YAIHelpGUI gui, YAIItemList parent) {
            super(gui);
            this.parent = parent;
        }

        /**
         * Gets the item list the GUI is returning to.
         *
         * @return the item list the GUI is returning to.
         */
        public YAIItemList getParent() {
            return parent;
        }

        /**
         * {@inheritDoc}
         *
         * @param o
         *   {@inheritDoc}
         *
         * @return {@inheritDoc}
         */
        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (!(o instanceof Cancel)) {
                return false;
            }
            if (!super.equals(o)) {
                return false;
            }
            Cancel cancel = (Cancel) o;
            return Objects.equals(parent, cancel.parent);
        }

        /**
         * {@inheritDoc}
         *
         * @return {@inheritDoc}
         */
        @Override
        public int hashCode() {
            return Objects.hash(super.hashCode(), parent);
        }

        /**
         * {@inheritDoc}
         *
         * @return {@inheritDoc}
         */
        @Override
        public String toString() {
            return "HelpGuiEvent.Cancel{" + gui + ", " + parent + '}';
        }
    }

    /**
     * An event generated when a crafting help GUI is opened.
     */
    public static class Open extends HelpGuiEvent {
        /**
         * The item list the help GUI was loaded from.
         */
        private final YAIItemList parent;

        /**
         * Creates a new crafting help GUI.
         *
         * @param gui
         *   the GUI the event occurred on.
         * @param parent
         *   the item list the help GUI was loaded from.
         */
        public Open(YAIHelpGUI gui, YAIItemList parent) {
            super(gui);
            this.parent = parent;
        }

        /**
         * Gets the item list the help GUI was loaded from.
         *
         * @return the item list the help GUI was loaded from.
         */
        public YAIItemList getParent() {
            return parent;
        }

        /**
         * {@inheritDoc}
         *
         * @param o
         *   {@inheritDoc}
         *
         * @return {@inheritDoc}
         */
        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (!(o instanceof Open)) {
                return false;
            }
            if (!super.equals(o)) {
                return false;
            }
            Open open = (Open) o;
            return Objects.equals(parent, open.parent);
        }

        /**
         * {@inheritDoc}
         *
         * @return {@inheritDoc}
         */
        @Override
        public int hashCode() {
            return Objects.hash(super.hashCode(), parent);
        }

        /**
         * {@inheritDoc}
         *
         * @return {@inheritDoc}
         */

        @Override
        public String toString() {
            return "HelpGuiEvent.Open{" + gui + ", " + parent + '}';
        }
    }

    /**
     * An event generated when closing a crafting help GUI and returning to the container GUI.
     */
    public static class Close extends HelpGuiEvent {
        /**
         * The container to return to.
         */
        private final GuiContainer parent;

        /**
         * Creates a new close event for a crafting help GUI.
         *
         * @param gui
         *   the GUI the event occurred to.
         * @param parent
         *   the container to return to.
         */
        public Close(YAIHelpGUI gui, GuiContainer parent) {
            super(gui);
            this.parent = parent;
        }

        /**
         * Gets the container to return to.
         *
         * @return the container to return to.
         */
        public GuiContainer getParent() {
            return parent;
        }

        /**
         * {@inheritDoc}
         *
         * @param o
         *   {@inheritDoc}
         *
         * @return {@inheritDoc}
         */
        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (!(o instanceof Close)) {
                return false;
            }
            if (!super.equals(o)) {
                return false;
            }
            Close close = (Close) o;
            return Objects.equals(parent, close.parent);
        }

        /**
         * {@inheritDoc}
         *
         * @return {@inheritDoc}
         */
        @Override
        public int hashCode() {
            return Objects.hash(super.hashCode(), parent);
        }

        /**
         * {@inheritDoc}
         *
         * @return {@inheritDoc}
         */
        @Override
        public String toString() {
            return "HelpGuiEvent.Close{" + gui + ", " + parent + '}';
        }
    }

    /**
     * An event generated when the crafting help gui changes crafting method.
     */
    public static class MethodChange extends HelpGuiEvent {
        /**
         * The crafting method the help GUI is switching to.
         */
        private final YAICraftMethod method;

        /**
         * Creates a new crafting method change event.
         *
         * @param gui
         *   the GUI the event occurred to.
         * @param method
         *   the crafting method the GUI is switching to.
         */
        public MethodChange(YAIHelpGUI gui, YAICraftMethod method) {
            super(gui);
            this.method = method;
        }

        /**
         * Gets the crafting method the help GUI is switching to.
         *
         * @return the crafting method the help GUI is switching to.
         */
        public YAICraftMethod getMethod() {
            return method;
        }

        /**
         * {@inheritDoc}
         *
         * @param o
         *   {@inheritDoc}
         *
         * @return {@inheritDoc}
         */
        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (!(o instanceof MethodChange)) {
                return false;
            }
            if (!super.equals(o)) {
                return false;
            }
            MethodChange method1 = (MethodChange) o;
            return Objects.equals(method, method1.method);
        }

        /**
         * {@inheritDoc}
         *
         * @return {@inheritDoc}
         */
        @Override
        public int hashCode() {
            return Objects.hash(super.hashCode(), method);
        }

        /**
         * {@inheritDoc}
         *
         * @return {@inheritDoc}
         */
        @Override
        public String toString() {
            return "HelpGuiEvent.MethodChange{" + gui + ", " + method + '}';
        }
    }

    /**
     * An event generated when the recipe changes in a crafting help GUI.
     */
    public static class RecipeChange extends HelpGuiEvent {
        /**
         * The recipe being switched to.
         */
        private final YAIRecipe recipe;

        /**
         * Creates a new recipe change event.
         *
         * @param gui
         *   the GUI the event occurred on.
         * @param recipe
         *   the recipe being switched to.
         */
        public RecipeChange(YAIHelpGUI gui, YAIRecipe recipe) {
            super(gui);
            this.recipe = recipe;
        }

        /**
         * Gets the recipe being switched to.
         *
         * @return the recipe being switched to.
         */
        public YAIRecipe getRecipe() {
            return recipe;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (!(o instanceof RecipeChange)) {
                return false;
            }
            if (!super.equals(o)) {
                return false;
            }
            RecipeChange recipe1 = (RecipeChange) o;
            return Objects.equals(recipe, recipe1.recipe);
        }

        @Override
        public int hashCode() {
            return Objects.hash(super.hashCode(), recipe);
        }

        @Override
        public String toString() {
            return "HelpGuiEvent.RecipeChange{" + gui + ", " + recipe + '}';
        }
    }

    /**
     * An event generated when a button is pressed in a crafting help GUI.
     */
    public static class ButtonPress extends HelpGuiEvent {
        /**
         * The button that was pressed.
         */
        private final GuiButton button;

        /**
         * Creates a new crafting help GUI event.
         *
         * @param gui
         *   the GUI the event occurred on.
         * @param button
         *   the button that was pressed.
         */
        public ButtonPress(YAIHelpGUI gui, GuiButton button) {
            super(gui);
            this.button = button;
        }

        /**
         * Gets the button that was pressed.
         *
         * @return the button that was pressed.
         */
        public GuiButton getButton() {
            return button;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (!(o instanceof ButtonPress)) {
                return false;
            }
            if (!super.equals(o)) {
                return false;
            }
            ButtonPress press = (ButtonPress) o;
            return Objects.equals(button, press.button);
        }

        @Override
        public int hashCode() {
            return Objects.hash(super.hashCode(), button);
        }

        @Override
        public String toString() {
            return "HelpGuiEvent.ButtonPress{" + gui + ", " + button + '}';
        }
    }

    /**
     * An event generated when a stack is selected (clicked on) in a crafting help GUI.
     */
    public static class StackSelect extends HelpGuiEvent {
        /**
         * The selected slot.
         */
        private final ItemStack slot;

        /**
         * Creates a new stack selection event.
         *
         * @param gui
         *   the GUI the event occurred on.
         * @param slot
         *   the selected slot.
         */
        public StackSelect(YAIHelpGUI gui, ItemStack slot) {
            super(gui);
            this.slot = slot;
        }

        public ItemStack getSlot() {
            return slot;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (!(o instanceof StackSelect)) {
                return false;
            }
            if (!super.equals(o)) {
                return false;
            }
            StackSelect select = (StackSelect) o;
            return ItemStack.areItemStacksEqual(slot, select.slot);
        }

        @Override
        public int hashCode() {
            return Objects.hash(super.hashCode(), slot);
        }

        @Override
        public String toString() {
            return "HelpGuiEvent.StackSelect{" + gui + ", " + slot + '}';
        }
    }
}
<file_sep>package yellowsquid.yetanotheritem.recipe;

import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import yellowsquid.yetanotheritem.YetAnotherItem;

class YAIDOMErrorHandler implements ErrorHandler {
    @Override
    public void warning(SAXParseException exception) throws SAXException {
        YetAnotherItem.logWarn("Issue when parsing file.");
        YetAnotherItem.logWarn(exception.getMessage());
    }

    @Override
    public void error(SAXParseException exception) throws SAXException {
        YetAnotherItem.logError("Issue when parsing file.");
        YetAnotherItem.logError(exception.getMessage());
    }

    @Override
    public void fatalError(SAXParseException exception) throws SAXException {
        YetAnotherItem.logError("Issue when parsing file.");
        throw exception;
    }
}
