/*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option) any
* later version. This program is distributed in the hope that it will be
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details. You should have received a copy of the GNU
* Lesser General Public License along with this program. If not, see
* <http://www.gnu.org/licenses/>
*/
package net.slimevoid.library.util.xml;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraftforge.fml.common.registry.GameRegistry;
import net.slimevoid.library.core.SlimevoidCore;
import net.slimevoid.library.core.lib.CoreLib;
import net.slimevoid.library.util.FileReader;
import net.slimevoid.library.util.FileUtils;
import org.w3c.dom.*;
import org.xml.sax.SAXException;
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.io.InputStream;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class XMLRecipeLoader extends XMLLoader {
/**
* Default XML files. Are copied over when not already exists.
*/
private static Map<String, Map<String, InputStream>> defaultLocations = new HashMap<String, Map<String, InputStream>>();
/**
* Loads default XML Recipe files from a directory.
*
* @param dir Default XML directory.
*/
public static void registerDefaultsFromLocation(Class clazz, String location) {
// Checks that our location list does not contain a reference already
if (!defaultLocations.containsKey(location)) {
try {
// Retrieves the resource listing based on the path and class
// given
String[] resourceList = FileUtils.getResourceListing(clazz,
location);
// If we retrieved results continue
if (resourceList.length > 0) {
// Creates a hashmap of each resource in the list
Map<String, InputStream> defaultStreams = new HashMap<String, InputStream>();
for (String file : resourceList) {
// Returns the file as an InputStream
InputStream instr = clazz.getClassLoader().getResourceAsStream(location
+ file);
// Places that InputStream against its reference name
// for use later
defaultStreams.put(file,
instr);
}
// Adds the InputStream HashMap to our resourceLocations Map
defaultLocations.put(location,
defaultStreams);
SlimevoidCore.console(CoreLib.MOD_ID,
"Resource list loaded from ["
+ clazz.getSimpleName()
+ "][" + location + "]");
} else {
SlimevoidCore.console(CoreLib.MOD_ID,
"Caution: Failed to get resource list from ["
+ clazz.getSimpleName()
+ "][" + location + "]",
1);
}
} catch (URISyntaxException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* Loads XML Recipe files from a directory.
*
* @param dir Source directory.
*/
public static void loadFolder(String locationKey, File dir) {
// Create the directory if it does not already exist.
if (!dir.isDirectory()) dir.mkdir();
if (defaultLocations.containsKey(locationKey)) {
Map<String, InputStream> defaultStreams = defaultLocations.get(locationKey);
// Iterate through the default files.
for (String filename : defaultStreams.keySet()) {
// If it does not exist in the source directory; copy defaults
// over.
if (!FileReader.checkIfExists(filename,
dir)) {
File newFile = new File(dir.getPath() + File.separator
+ filename);
if (FileUtils.copyStream(defaultStreams.get(filename),
newFile)) {
SlimevoidCore.console(CoreLib.MOD_ID,
"Default was file loaded ["
+ newFile.getName() + "]");
} else {
SlimevoidCore.console(CoreLib.MOD_ID,
"Failed to load default file ["
+ newFile.getName() + "]");
}
} else {
SlimevoidCore.console(CoreLib.MOD_ID,
"File ["
+ filename
+ "] already exists! Skipping...");
}
}
// Iterate through XML files in the source directory.
for (File xml : dir.listFiles(filter)) {
// Load the XML file.
loadXML(xml);
}
defaultLocations.remove(locationKey);
} else {
SlimevoidCore.console(CoreLib.MOD_ID,
"Caution: Could not load default settings from ["
+ locationKey + "]");
}
}
/**
* Load a specific XML Recipe file.
*
* @param file Source file.
*/
public static void loadXML(File file) {
try {
// Set up the XML document.
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
Document doc = dBuilder.parse(file);
doc.getDocumentElement().normalize();
// Fetch and iterate through all recipe nodes.
NodeList nodes = doc.getElementsByTagName("recipe");
for (int i = 0; i < nodes.getLength(); i++) {
Node node = nodes.item(i);
// If the node is an element node; assemble the recipe.
if (node.getNodeType() == Node.ELEMENT_NODE) {
Element element = (Element) node;
assemble(element,
file);
}
}
} catch (ParserConfigurationException e) {
// This happens when the parser settings are fooked up.
// Serious business! Very rare.
FileReader.endWithError("Could not parse XML: " + file.getName());
e.printStackTrace();
} catch (SAXException e) {
// This happens when the XML markup is fooked up.
// The syntax must be correct XML. One root node, closed nodes, etc
// etc.
FileReader.endWithError("Could not parse XML markup: "
+ file.getName());
e.printStackTrace();
} catch (IOException e) {
// File I/O error.
// Did not exist? No read/write permissions?
FileReader.endWithError("Could not read XML: " + file.getName());
e.printStackTrace();
}
}
/**
* Assemble a recipe Element node.
*
* @param element Element node.
* @param xmlFile Source XML File.
*/
private static void assemble(Element element, File xmlFile) {
// The layout
String[] recipeLayout = null;
// Output stack size. Defaults to one.
int recipeStackSize = 1;
// Output ID. Must be set or it will skip the recipe.
Item outItem = null;
// Output meta data. Defaults to 0.
int outMeta = 0;
// The input recipe map.
// Maps up itemstacks to layout characters.
Map<String, ItemStack> recipeMap = new HashMap<String, ItemStack>();
/************************************
* \ Fetch stack size, meta and outId * \
************************************/
NamedNodeMap recAttrs = element.getAttributes(); // recipe attributes.
for (int j = 0; j < recAttrs.getLength(); j++) {
// StackSize attribute was found.
if (recAttrs.item(j).getNodeName().equals("stackSize")) {
try {
// Attempt to parse attribute integer.
recipeStackSize = Integer.parseInt(recAttrs.item(j).getNodeValue());
} catch (NumberFormatException e) {
} // Ignore it completely if failed.
}
// Metadata attribute was found.
if (recAttrs.item(j).getNodeName().equals("meta")) {
String outMetaStr = recAttrs.item(j).getNodeValue();
outMeta = xmlValueToInteger(outMetaStr);
}
// Out ID attribute was found.
if (recAttrs.item(j).getNodeName().equals("outId")) {
String outIdStr = recAttrs.item(j).getNodeValue();
outItem = xmlValueToItem(outIdStr);
}
}
// Do not continue without ID.
if (outItem == null) {
FileReader.endWithError("recipe.outID not set! ("
+ xmlFile.getName() + ")");
return;
}
/****************
* \ Fetch layout * \
****************/
// Split it up by newline
recipeLayout = getValue("layout",
element).split("\n");
int nextI = 0;
for (int i = 0; i < recipeLayout.length; i++) {
// Trim the line. Removing all leading and trailing whitespaces.
recipeLayout[i] = recipeLayout[i].trim();
// If its not empty, add it to next index and set current to null.
if (!recipeLayout[i].equals("")) {
recipeLayout[nextI] = recipeLayout[i];
recipeLayout[i] = null;
nextI++;
// If it is empty, set it to null.
} else {
recipeLayout[i] = null;
}
}
// When layout finishes, the array will be n-length, where n is number
// of newlines.
// But the actual layout stuff is moved to the top of the array, where
// rest is null.
/*******************
* \ Fetch mappings * \
*******************/
NodeList blockNodes = element.getElementsByTagName("mapping");
// Iterate through each mapping.
for (int i = 0; i < blockNodes.getLength(); i++) {
Node node = blockNodes.item(i);
// Only care about the node if it is an element node.
if (node.getNodeType() == Node.ELEMENT_NODE) {
NamedNodeMap attrs = node.getAttributes();
// The input object's ID.
Item item = null;
// The input object's metadata.
int meta = 0;
// Iterate through mapping attributes.
for (int j = 0; j < attrs.getLength(); j++) {
// ID attribute was found.
if (attrs.item(j).getNodeName().equals("id")) {
String idStr = attrs.item(j).getNodeValue();
item = xmlValueToItem(idStr);
}
// Metadata attribute was found.
if (attrs.item(j).getNodeName().equals("meta")) {
String metaStr = attrs.item(j).getNodeValue();
meta = xmlValueToInteger(metaStr);
}
}
// Do not continue without ID.
if (item == null) {
FileReader.endWithError("mapping.id not set! ("
+ xmlFile.getName() + ")");
return;
}
// Add the input mapping to the recipe map.
// The node's value is the variable.
recipeMap.put(node.getChildNodes().item(0).getNodeValue(),
new ItemStack(item, 1, meta));
}
}
/*******************
* \ Assemble recipe * \
*******************/
// The recipe input array.
List<Object> recipe = new ArrayList<Object>();
// Iterate through layout.
for (int i = 0; i < recipeLayout.length; i++) {
// Add layout string if it is not null.
if (recipeLayout[i] != null) {
recipe.add(recipeLayout[i]);
}
}
// Iterate through mappings.
for (String key : recipeMap.keySet()) {
// First add the variable...
recipe.add(Character.valueOf(key.toCharArray()[0]));
// ...then the item stack.
recipe.add(recipeMap.get(key));
}
// Register recipe.
// Output itemstack and convert the list to object array.
registerRecipe(new ItemStack(outItem, recipeStackSize, outMeta),
recipe.toArray());
}
private static int xmlValueToInteger(String xmlString) {
int value = 0;
try {
// Try to parse attribute integer
value = Integer.parseInt(xmlString);
} catch (NumberFormatException e) {
// Integer parsin failed, try checking if it is a
// variable string
if (xmlVariables.containsKey(xmlString)) {
// If it was a variable string, use the variable
// mapping instead
value = xmlVariables.get(xmlString);
}
}
return value;
}
private static Item xmlValueToItem(String xmlString) {
int value = 0;
try {
// Try to parse attribute integer
value = Integer.parseInt(xmlString);
} catch (NumberFormatException e) {
// Integer parsin failed, try checking if it is a
// variable string
if (xmlVariables.containsKey(xmlString)) {
// If it was a variable string, use the variable
// mapping instead
value = xmlVariables.get(xmlString);
}
}
return Item.getItemById(value);
}
/**
* Register a recipe.<br>
* Uses Minecraft API (Forge/Modloader) specific method of registration.
*
* @param output Recipe output.
* @param input Recipe input.
*/
private static void registerRecipe(ItemStack output, Object[] input) {
GameRegistry.addRecipe(output,
input);
FileReader.sendMessage("Adding recipe for: "
+ output.getUnlocalizedName());
}
}