package crazypants.enderio.machine.recipe;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.StringReader;
import java.util.ArrayList;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import net.minecraft.block.Block;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraftforge.fluids.Fluid;
import net.minecraftforge.fluids.FluidContainerRegistry;
import net.minecraftforge.fluids.FluidRegistry;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.oredict.OreDictionary;
import org.apache.commons.io.IOUtils;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
import cpw.mods.fml.common.registry.GameRegistry;
import crazypants.enderio.Log;
import crazypants.enderio.machine.crusher.CrusherRecipeManager;
import crazypants.enderio.machine.recipe.RecipeConfig.RecipeElement;
import crazypants.enderio.machine.recipe.RecipeConfig.RecipeGroup;
public class RecipeConfigParser extends DefaultHandler {
public static final String ELEMENT_RECIPE_GROUP = "recipeGroup";
public static final String ELEMENT_RECIPE = "recipe";
public static final String ELEMENT_INPUT = "input";
public static final String ELEMENT_OUTPUT = "output";
public static final String ELEMENT_ITEM_STACK = "itemStack";
public static final String ELEMENT_FLUID_STACK = "fluidStack";
public static final String ELEMENT_DUMP_REGISTERY = "dumpRegistery";
public static final String AT_NAME = "name";
public static final String AT_ENABLED = "enabled";
public static final String AT_DUMP_ITEMS = "modObjects";
public static final String AT_ORE_DICT = "oreDictionary";
public static final String AT_ENERGY_COST = "energyCost";
public static final String AT_ITEM_META = "itemMeta";
public static final String AT_ITEM_NAME = "itemName";
public static final String AT_MOD_ID = "modID";
public static final String AT_NUMBER = "number";
public static final String AT_AMOUNT = "amount";
public static final String AT_MULTIPLIER = "multiplier";
public static final String AT_SLOT = "slot";
public static final String AT_CHANCE = "chance";
public static final String AT_EXP = "exp";
// Log prefix
private static final String LP = "RecipeParser: ";
public static RecipeConfig parse(String str, CustomTagHandler customHandler) throws Exception {
StringReader reader = new StringReader(str);
InputSource is = new InputSource(reader);
try {
return parse(is, customHandler);
} finally {
reader.close();
}
}
public static RecipeConfig parse(File file, CustomTagHandler customHandler) throws Exception {
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
InputSource is = new InputSource(bis);
try {
return parse(is, customHandler);
} finally {
IOUtils.closeQuietly(bis);
}
}
public static RecipeConfig parse(InputSource is, CustomTagHandler customHandler) throws Exception {
RecipeConfigParser parser = new RecipeConfigParser(customHandler);
SAXParserFactory spf = SAXParserFactory.newInstance();
spf.setNamespaceAware(true);
SAXParser saxParser = spf.newSAXParser();
XMLReader xmlReader = saxParser.getXMLReader();
xmlReader.setContentHandler(parser);
xmlReader.parse(is);
return parser.getResult();
}
private RecipeConfig result = null;
private RecipeConfig root = null;
private RecipeGroup recipeGroup = null;
private RecipeElement recipe = null;
private boolean outputTagOpen = false;
private boolean inputTagOpen = false;
private boolean debug = false;
private boolean inCustomHandler = false;
private CustomTagHandler customHandler = null;
RecipeConfigParser(CustomTagHandler customHandler) {
this.customHandler = customHandler;
}
RecipeConfig getResult() {
return result != null ? result : root;
}
@Override
public void warning(SAXParseException e) throws SAXException {
Log.warn("Warning parsing SAG Mill config file: " + e.getMessage());
}
@Override
public void error(SAXParseException e) throws SAXException {
Log.error("Error parsing SAG Mill config file: " + e.getMessage());
e.printStackTrace();
}
@Override
public void fatalError(SAXParseException e) throws SAXException {
Log.error("Error parsing SAG Mill config file: " + e.getMessage());
e.printStackTrace();
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
if(isElementRoot(localName)) {
result = root;
root = null;
if(debug) {
Log.debug(LP + "Closing root");
}
return;
}
if(ELEMENT_RECIPE_GROUP.equals(localName)) {
if(debug) {
Log.debug(LP + "Closing recipe group");
}
if(recipeGroup != null && root != null) {
root.addRecipeGroup(recipeGroup);
}
recipeGroup = null;
return;
}
if(ELEMENT_RECIPE.equals(localName)) {
if(debug) {
Log.debug(LP + "Closing recipe");
}
if(recipe != null && recipeGroup != null) {
recipeGroup.addRecipe(recipe);
} else {
Log.warn(LP + "Could not add recipe " + recipe + " to group " + recipeGroup);
}
recipe = null;
return;
}
if(ELEMENT_OUTPUT.equals(localName)) {
outputTagOpen = false;
if(debug) {
Log.debug(LP + "Closing output");
}
return;
}
if(ELEMENT_INPUT.equals(localName)) {
inputTagOpen = false;
if(debug) {
Log.debug(LP + "Closing input");
}
return;
}
// Custom tag handling
if(customHandler != null) {
if(customHandler.endElement(uri, localName, qName)) {
inCustomHandler = false;
return;
}
}
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
if(debug) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < attributes.getLength(); i++) {
sb.append("[" + attributes.getQName(i) + "=" + attributes.getValue(i) + "]");
}
Log.debug(LP + "RecipeConfigParser.startElement: localName:" + localName + " attrs:" + sb);
}
if(isElementRoot(localName)) {
if(root != null) {
Log.warn(LP + "Multiple root elements found.");
} else {
root = new RecipeConfig();
}
return;
}
if(root == null) {
Log.warn(LP + " Root element not specified before element " + localName + ".");
root = new RecipeConfig();
}
if(ELEMENT_DUMP_REGISTERY.equals(localName)) {
root.setDumpOreDictionary(getBooleanValue(AT_ORE_DICT, attributes, false));
root.setDumpItemRegistery(getBooleanValue(AT_DUMP_ITEMS, attributes, false));
return;
}
if(ELEMENT_RECIPE_GROUP.equals(localName)) {
if(recipeGroup != null) {
Log.warn(LP + "Recipe group " + recipeGroup.getName() + " not closed before encountering a new recipe group.");
}
recipeGroup = root.createRecipeGroup(attributes.getValue(AT_NAME));
recipeGroup.setEnabled(getBooleanValue(AT_ENABLED, attributes, true));
if(!recipeGroup.isNameValid()) {
Log.warn(LP + "A recipe group was found with an invalid name: " + attributes.getValue(AT_NAME));
recipeGroup = null;
}
return;
}
if(ELEMENT_RECIPE.equals(localName)) {
if(recipeGroup == null) {
Log.warn(LP + "A recipe was found outside of a recipe groups tags.");
return;
}
if(recipe != null) {
Log.warn(LP + "A new recipe was started before the recipe was closed.");
}
String name = getStringValue(AT_NAME, attributes, null);
if(name == null) {
Log.warn(LP + "An unnamed recipe was found.");
return;
}
recipe = recipeGroup.createRecipe(name);
recipe.setEnergyRequired(getIntValue(AT_ENERGY_COST, attributes, CrusherRecipeManager.ORE_ENERGY_COST));
return;
}
// Custom tag handling
if(customHandler != null) {
if(customHandler.startElement(uri, localName, qName, attributes)) {
inCustomHandler = true;
return;
}
}
if(recipe == null) {
if(!inCustomHandler) {
Log.warn(LP + "Found element <" + localName + "> with no recipe decleration.");
}
return;
}
if(ELEMENT_OUTPUT.equals(localName)) {
if(inputTagOpen) {
Log.warn(LP + "<output> encounterd before <input> closed.");
inputTagOpen = false;
}
if(outputTagOpen) {
Log.warn(LP + "<output> encounterd before previous <output> closed.");
}
outputTagOpen = true;
return;
}
if(ELEMENT_INPUT.equals(localName)) {
if(outputTagOpen) {
Log.warn(LP + "<input> encounterd before <output> closed.");
outputTagOpen = false;
}
if(inputTagOpen) {
Log.warn(LP + "<input> encounterd before previous <input> closed.");
}
inputTagOpen = true;
return;
}
boolean isFluidStack = ELEMENT_FLUID_STACK.equals(localName);
if(ELEMENT_ITEM_STACK.equals(localName) || isFluidStack) {
if(!inputTagOpen && !outputTagOpen) {
Log.warn(LP + "Encounterd an item stack outside of either an <input> or <output> tag.");
return;
}
if(inputTagOpen && outputTagOpen) {
Log.warn(LP + "Encounterd an item stack within both an <input> and <output> tag.");
return;
}
if(inputTagOpen) {
if(isFluidStack) {
addInputFluidStack(attributes);
} else {
addInputStack(attributes);
}
} else {
if(isFluidStack) {
addOutputFluidStack(attributes);
} else {
addOutputStack(attributes);
}
}
}
}
//TODO: What a hack!
private boolean isElementRoot(String str) {
return "AlloySmelterRecipes".equals(str) || "SAGMillRecipes".equals(str) || "VatRecipes".equals(str) || "SliceAndSpliceRecipes".equals(str);
}
private void addOutputStack(Attributes attributes) {
RecipeInput stack = getItemStack(attributes);
if(stack == null) {
return;
}
float exp = getFloatValue(AT_EXP, attributes, 0f);
recipe.addOutput(new RecipeOutput(stack.getInput(), getFloatValue(AT_CHANCE, attributes, 1f), exp));
}
private void addInputStack(Attributes attributes) {
RecipeInput stack = getItemStack(attributes);
if(stack == null) {
return;
}
recipe.addInput(stack);
}
private void addOutputFluidStack(Attributes attributes) {
RecipeInput stack = getFluidStack(attributes);
if(stack == null) {
return;
}
recipe.addOutput(new RecipeOutput(stack.getFluidInput()));
}
private void addInputFluidStack(Attributes attributes) {
RecipeInput stack = getFluidStack(attributes);
if(stack == null) {
return;
}
recipe.addInput(stack);
}
private RecipeInput getFluidStack(Attributes attributes) {
int amount = getIntValue(AT_AMOUNT, attributes, FluidContainerRegistry.BUCKET_VOLUME);
String name = getStringValue(AT_NAME, attributes, null);
if(name == null) {
return null;
}
Fluid fluid = FluidRegistry.getFluid(name);
if(fluid == null) {
Log.warn("When parsing recipes could not find fluid with name: " + name);
return null;
}
return new RecipeInput(new FluidStack(fluid, amount), getFloatValue(AT_MULTIPLIER, attributes, 1));
}
public static RecipeInput getItemStack(Attributes attributes) {
int stackSize = getIntValue(AT_NUMBER, attributes, 1);
String oreDict = getStringValue(AT_ORE_DICT, attributes, null);
if(oreDict != null) {
ArrayList<ItemStack> ores = OreDictionary.getOres(oreDict);
if(ores == null || ores.isEmpty() || ores.get(0) == null) {
Log.debug(LP + "Could not find an entry in the ore dictionary for " + oreDict);
return null;
}
ItemStack stack = ores.get(0).copy();
stack.stackSize = stackSize;
return new OreDictionaryRecipeInput(stack, OreDictionary.getOreID(oreDict), getFloatValue(AT_MULTIPLIER, attributes, 1), getIntValue(AT_SLOT, attributes,
-1));
}
boolean useMeta = true;
int itemMeta = 0;
String metaString = getStringValue(AT_ITEM_META, attributes, "0");
if("*".equals(metaString)) {
useMeta = false;
} else {
itemMeta = getIntValue(AT_ITEM_META, attributes, 0);
}
ItemStack res = null;
String modId = getStringValue(AT_MOD_ID, attributes, null);
String name = getStringValue(AT_ITEM_NAME, attributes, null);
if(modId != null && name != null) {
Item i = GameRegistry.findItem(modId, name);
if(i != null) {
res = new ItemStack(i, stackSize, useMeta ? itemMeta : 0);
} else {
Block b = GameRegistry.findBlock(modId, name);
if(b != null) {
res = new ItemStack(b, stackSize, useMeta ? itemMeta : 0);
}
}
}
if(res == null) {
Log.debug("Could not create an item stack from the attributes " + toString(attributes));
return null;
}
return new RecipeInput(res, useMeta, getFloatValue(AT_MULTIPLIER, attributes, 1), getIntValue(AT_SLOT, attributes, -1));
}
public static boolean getBooleanValue(String qName, Attributes attributes, boolean def) {
String val = attributes.getValue(qName);
if(val == null) {
return def;
}
val = val.toLowerCase().trim();
return val.equals("false") ? false : val.equals("true") ? true : def;
}
public static int getIntValue(String qName, Attributes attributes, int def) {
try {
return Integer.parseInt(getStringValue(qName, attributes, def + ""));
} catch (Exception e) {
Log.warn(LP + "Could not parse a valid int for attribute " + qName + " with value " + getStringValue(qName, attributes, null));
return def;
}
}
public static float getFloatValue(String qName, Attributes attributes, float def) {
try {
return Float.parseFloat(getStringValue(qName, attributes, def + ""));
} catch (Exception e) {
Log.warn(LP + "Could not parse a valid float for attribute " + qName + " with value " + getStringValue(qName, attributes, null));
return def;
}
}
public static String getStringValue(String qName, Attributes attributes, String def) {
String val = attributes.getValue(qName);
if(val == null) {
return def;
}
val = val.trim();
if(val.length() <= 0) {
return null;
}
return val;
}
public static boolean hasAttribute(String att, Attributes attributes) {
return attributes.getValue(att) != null;
}
public static String toString(Attributes attributes) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < attributes.getLength(); i++) {
sb.append("[" + attributes.getQName(i) + "=" + attributes.getValue(i) + "]");
}
return sb.toString();
}
}