/*
* MegaMek - Copyright (C) 2003 Ben Mazur (bmazur@sev.org)
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 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.
*/
package megamek.common.xml;
import gd.xml.tiny.ParsedXML;
import java.io.IOException;
import java.io.Writer;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;
import megamek.common.Board;
import megamek.common.Building;
import megamek.common.Coords;
import megamek.common.IBoard;
import megamek.common.IGame;
import megamek.common.IHex;
import megamek.common.InfernoTracker;
/**
* Objects of this class can encode a <code>Board</code> object as XML into an
* output writer and decode one from a parsed XML node. It is used when saving
* games into a version- neutral format.
*
* @author James Damour <suvarov454@users.sourceforge.net>
*/
public class BoardEncoder {
/**
* Encode a <code>Board</code> object to an output writer.
*
* @param board - the <code>Board</code> to be encoded. This value must
* not be <code>null</code>.
* @param out - the <code>Writer</code> that will receive the XML. This
* value must not be <code>null</code>.
* @throws <code>IllegalArgumentException</code> if the node is
* <code>null</code>.
* @throws <code>IOException</code> if there's any error on write.
*/
public static void encode(IBoard board, Writer out) throws IOException {
Enumeration<?> iter; // used when marching through a list of sub-elements
Coords coords;
int x;
int y;
int turns;
// First, validate our input.
if (null == board) {
throw new IllegalArgumentException("The board is null.");
}
if (null == out) {
throw new IllegalArgumentException("The writer is null.");
}
// Start the XML stream for this board
out.write("<board version=\"1.0\" >");
// Write the hex array to the stream.
out.write("<boardData width=\"");
out.write(Integer.toString(board.getWidth()));
out.write("\" height=\"");
out.write(Integer.toString(board.getHeight()));
out.write("\" roadsAutoExit=\"");
out.write(board.getRoadsAutoExit() ? "true" : "false");
out.write("\" >");
for (y = 0; y < board.getHeight(); y++) {
for (x = 0; x < board.getWidth(); x++) {
HexEncoder.encode(board.getHex(x, y), out);
}
}
out.write("</boardData>");
// Write out the buildings (if any).
iter = board.getBuildings();
if (iter.hasMoreElements()) {
out.write("<buildings>");
while (iter.hasMoreElements()) {
BuildingEncoder.encode((Building) iter.nextElement(), out);
}
out.write("</buildings>");
}
// Write out the infernos (if any).
iter = board.getInfernoBurningCoords();
if (iter.hasMoreElements()) {
out.write("<infernos>");
while (iter.hasMoreElements()) {
// Encode the infernos as these coordinates.
coords = (Coords) iter.nextElement();
out.write("<inferno>");
CoordsEncoder.encode(coords, out);
turns = board.getInfernoIVBurnTurns(coords);
// This value may be zero.
if (turns > 0) {
out.write("<arrowiv turns=\"");
out.write(Integer.toString(turns));
out.write("\" />");
}
// -(Arrow IV turns - All Turns) = Standard Turns.
turns -= board.getInfernoBurnTurns(coords);
turns = -turns;
if (turns > 0) {
out.write("<standard turns=\"");
out.write(Integer.toString(turns));
out.write("\" />");
}
out.write("</inferno>");
}
out.write("</infernos>");
}
// Finish the XML stream for this board.
out.write("</board>");
}
/**
* Decode a <code>Board</code> object from the passed node.
*
* @param node - the <code>ParsedXML</code> node for this object. This
* value must not be <code>null</code>.
* @param game - the <code>IGame</code> the decoded object belongs to.
* @return the <code>Board</code> object based on the node.
* @throws <code>IllegalArgumentException</code> if the node is
* <code>null</code>.
* @throws <code>IllegalStateException</code> if the node does not contain
* a valid <code>Board</code>.
*/
public static IBoard decode(ParsedXML node, IGame game) {
String attrStr = null;
int attrVal = 0;
Board retVal = null;
Vector<Building> buildings = new Vector<Building>();
Hashtable<Coords, InfernoTracker> infernos = new Hashtable<Coords, InfernoTracker>();
int height = 0;
int width = 0;
IHex[] hexes = null;
Coords coords = null;
Enumeration<?> subnodes = null;
ParsedXML subnode = null;
// Did we get a null node?
if (null == node) {
throw new IllegalArgumentException("The board is null.");
}
// Make sure that the node is for a Board object.
if (!node.getName().equals("board")) {
throw new IllegalStateException("Not passed a board node.");
}
// TODO : perform version checking.
// Walk the board node's children.
Enumeration<?> children = node.elements();
while (children.hasMoreElements()) {
ParsedXML child = (ParsedXML) children.nextElement();
String childName = child.getName();
// Handle null child names.
if (null == childName) {
// No-op.
}
// Did we find the boardData node?
else if (childName.equals("boardData")) {
// There should be only one boardData node.
if (null != hexes) {
throw new IllegalStateException(
"More than one 'boardData' node in a board node.");
}
// Get the number of boardData.
attrStr = child.getAttribute("height");
if (null == attrStr) {
throw new IllegalStateException(
"Couldn't decode the boardData for a board node.");
}
// Try to pull the height from the attribute string
try {
attrVal = Integer.parseInt(attrStr);
} catch (NumberFormatException exp) {
throw new IllegalStateException(
"Couldn't get an integer from " + attrStr);
}
height = attrVal;
// Do we have a valid value?
if (height < 0 || height > Board.BOARD_MAX_HEIGHT) {
throw new IllegalStateException(
"Illegal value for height: " + attrStr);
}
// Get the number of boardData.
attrStr = child.getAttribute("width");
if (null == attrStr) {
throw new IllegalStateException(
"Couldn't decode the boardData for a board node.");
}
// Try to pull the width from the attribute string
try {
attrVal = Integer.parseInt(attrStr);
} catch (NumberFormatException exp) {
throw new IllegalStateException(
"Couldn't get an integer from " + attrStr);
}
width = attrVal;
// Do we have a valid value?
if (width < 0 || width > Board.BOARD_MAX_WIDTH) {
throw new IllegalStateException("Illegal value for width: "
+ attrStr);
}
// Read the "roadsAutoExit" attribute.
// unused, and therefore removed.
// roadsAutoExit = StringUtil.parseBoolean(
// child.getAttribute("roadsAutoExit") );
// Create an array to hold all the boardData.
hexes = new IHex[height * width];
// Walk through the subnodes, parsing out hex nodes.
int numHexes = 0;
subnodes = child.elements();
while (subnodes.hasMoreElements()) {
// Is this a "hex" node?
subnode = (ParsedXML) subnodes.nextElement();
if (subnode.getName().equals("hex")) {
// Are there too many hex nodes?
if (hexes.length == numHexes) {
throw new IllegalStateException(
"Too many hexes in a board node.");
}
// Parse out this hex node.
hexes[numHexes] = HexEncoder.decode(subnode, game);
// Increment the number of boardData.
numHexes++;
} // End found-"hex"-node
} // Look at the next subnode.
// Have we found enough hex nodes?
if (numHexes < hexes.length) {
throw new IllegalStateException(
"Not enough hexes in a board node.");
}
} // End found-"boardData"-node
// Did we find the infernos node?
else if (childName.equals("infernos")) {
subnodes = child.elements();
while (subnodes.hasMoreElements()) {
subnode = (ParsedXML) subnodes.nextElement();
if (subnode.getName().equals("inferno")) {
coords = null;
InfernoTracker tracker = new InfernoTracker();
// Try to find the inferno detail nodes.
Enumeration<?> details = subnode.elements();
while (details.hasMoreElements()) {
ParsedXML detail = (ParsedXML) details
.nextElement();
// Have we found the coords detail?
if (detail.getName().equals("coords")) {
coords = CoordsEncoder.decode(detail, game);
}
// Have we found the Arrow IV inferno detail?
else if (detail.getName().equals("arrowiv")) {
// Get the burn turns attribute.
attrStr = detail.getAttribute("turns");
if (null == attrStr) {
throw new IllegalStateException(
"Couldn't decode the burn turns for an Arrow IV inferno round.");
}
// Try to pull the value from the string
try {
attrVal = Integer.parseInt(attrStr);
} catch (NumberFormatException exp) {
throw new IllegalStateException(
"Couldn't get an integer from "
+ attrStr);
}
// Add the number of Arrow IV burn turns.
tracker.add(InfernoTracker.INFERNO_IV_TURN,
attrVal);
} // End found-arrowiv-detail
// Have we found the standard inferno entry?
else if (detail.getName().equals("standard")) {
// Get the burn turns attribute.
attrStr = detail.getAttribute("turns");
if (null == attrStr) {
throw new IllegalStateException(
"Couldn't decode the burn turns for a standard inferno round.");
}
// Try to pull the value from the string
try {
attrVal = Integer.parseInt(attrStr);
} catch (NumberFormatException exp) {
throw new IllegalStateException(
"Couldn't get an integer from "
+ attrStr);
}
// Add the number of standard burn turns.
tracker.add(InfernoTracker.STANDARD_TURN,
attrVal);
} // End found-standard-detail
} // Handle the next detail node.
// We *did* find the coords, right?
if (null == coords) {
throw new IllegalStateException(
"Couldn't decode the coordinates for an inferno round.");
}
// Add this inferno tracker to the hashtable.
infernos.put(coords, tracker);
} // End found-"inferno"-subnode
} // Check the next subnode
} // End found-"infernos"-child
// Did we find the buildings node?
else if (childName.equals("buildings")) {
subnodes = child.elements();
while (subnodes.hasMoreElements()) {
subnode = (ParsedXML) subnodes.nextElement();
if (subnode.getName().equals("building")) {
Building bldg = BuildingEncoder.decode(subnode, game);
if (null != bldg) {
buildings.addElement(bldg);
}
}
} // Handle the next building
}
} // Look at the next child.
// Did we find all needed child nodes?
if (null == hexes) {
throw new IllegalStateException(
"Couldn't locate the boardData for a board node.");
}
// Construct the board.
retVal = new Board(width, height, hexes, buildings, infernos);
// Return the board for this node.
return retVal;
}
}