/**
* Copyright (C) 2002-2012 The FreeCol Team
*
* This file is part of FreeCol.
*
* FreeCol 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.
*
* FreeCol 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 General Public License
* along with FreeCol. If not, see <http://www.gnu.org/licenses/>.
*/
package net.sf.freecol.common.model;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
import java.util.UUID;
import java.util.logging.Logger;
import net.sf.freecol.common.model.NationOptions.Advantages;
import net.sf.freecol.common.model.NationOptions.NationState;
import net.sf.freecol.common.option.BooleanOption;
import net.sf.freecol.common.option.IntegerOption;
import net.sf.freecol.common.option.MapGeneratorOptions;
import net.sf.freecol.common.option.Option;
import net.sf.freecol.common.option.OptionGroup;
import org.freecolandroid.xml.stream.XMLStreamConstants;
import org.freecolandroid.xml.stream.XMLStreamException;
import org.freecolandroid.xml.stream.XMLStreamReader;
import org.freecolandroid.xml.stream.XMLStreamWriter;
/**
* The main component of the game model.
*
* <br>
* <br>
*
* If an object of this class returns a non-null result to {@link #getViewOwner},
* then this object just represents a view of the game from a single player's
* perspective. In that case, some information might be missing from the model.
*/
public class Game extends FreeColGameObject {
public static final String CIBOLA_TAG = "cibola";
private static final Logger logger = Logger.getLogger(Game.class.getName());
/** Game UUID, persistent in savegame files */
private UUID uuid = UUID.randomUUID();
/**
* A virtual player to use with enemy privateers
*/
private Player unknownEnemy;
/** Contains all the players in the game. */
protected List<Player> players = new ArrayList<Player>();
private Map map;
/** The name of the player whose turn it is. */
protected Player currentPlayer = null;
/**
* The owner of this view of the game, or <code>null</code> if this game
* has all the information.
*/
protected Player viewOwner = null;
/** Contains references to all objects created in this game. */
protected HashMap<String, WeakReference<FreeColGameObject>> freeColGameObjects =
new HashMap<String, WeakReference<FreeColGameObject>>(10000);
/**
* The next available ID, that can be given to a new
* <code>FreeColGameObject</code>.
*/
protected int nextId = 1;
private Turn turn = new Turn(1);
/**
* Describe nationOptions here.
*/
private NationOptions nationOptions;
/**
* Whether the War of Spanish Succession has already taken place.
*/
private boolean spanishSuccession = false;
protected FreeColGameObjectListener freeColGameObjectListener;
/**
* The cities of Cibola remaining in this game.
*/
private List<String> citiesOfCibola = null;
/**
* The combat model this game uses. At the moment, the only combat
* model available is the SimpleCombatModel, which strives to
* implement the combat model of the original game. However, it is
* anticipated that other, more complex combat models will be
* implemented in future. As soon as that happens, we will also
* have to make the combat model selectable.
*/
protected CombatModel combatModel;
/**
* The Specification this game uses.
*/
private Specification specification;
/**
* This constructor is used by the Server to create a new Game
* with the given Specification.
*
* @param specification
*/
protected Game(Specification specification) {
super(null);
this.specification = specification;
}
/**
* Minimal constructor,
* Just necessary to call parent constructor
*
* @param game <code>Game</code>
* @param in <code>XMLStreamReader</code>
* @throws XMLStreamException
*/
protected Game(Game game, XMLStreamReader in) throws XMLStreamException {
super(game, in);
}
/*
* Initiate a new <code>Game</code> object from a <code>Element</code>
* in a DOM-parsed XML-tree.
*
* Currently not used, commented.
*
* @param modelController A controller object the model can use to make
* actions not allowed from the model (generate random numbers etc). @param
* viewOwnerUsername The username of the owner of this view of the game.
* @param e An XML-element that will be used to initialize this object.
*
* public Game(ModelController modelController, Element e, String
* viewOwnerUsername){ super(null, e);
*
* this.modelController = modelController; canGiveID = false;
* readFromXMLElement(e); this.viewOwner =
* getPlayerByName(viewOwnerUsername); }
*/
/**
* Initiate a new <code>Game</code> object from an XML-representation.
* <p>
* Note that this is used on the client side; the game is really a partial
* view of the server-side game.
*
* @param in The XML stream to read the data from.
* @param viewOwnerUsername The username of the owner of this view of the
* game.
* @throws XMLStreamException if an error occured during parsing.
* @see net.sf.freecol.client.control.ConnectController#login(String,
* String, int)
*/
public Game(XMLStreamReader in, String viewOwnerUsername)
throws XMLStreamException {
super(null, in);
this.combatModel = new SimpleCombatModel();
readFromXML(in);
this.viewOwner = getPlayerByName(viewOwnerUsername);
// setId() does not add Games to the freeColGameObjects
this.setFreeColGameObject(getId(), this);
}
/** Returns the unique identifier for this game.
* (A game UUID persists in save game files.)
* @return java.util.UUID
*/
public UUID getUUID () {
return uuid;
}
/**
* Returns the "Unknown Enemy" Player, which is used for
* privateers.
*
* @return a <code>Player</code> value
*/
public Player getUnknownEnemy() {
return unknownEnemy;
}
/**
* Sets the "Unknown Enemy" Player, which is used for
* privateers.
*
* @param player a <code>Player</code> value
*/
public void setUnknownEnemy(Player player) {
this.unknownEnemy = player;
}
/**
* Get the <code>VacantNations</code> value.
*
* @return a <code>List<Nation></code> value
*/
public final List<Nation> getVacantNations() {
List<Nation> result = new ArrayList<Nation>();
for (Entry<Nation, NationState> entry : nationOptions.getNations().entrySet()) {
if (entry.getValue() == NationState.AVAILABLE) {
result.add(entry.getKey());
}
}
return result;
}
/**
* Returns the owner of this view of the game, or <code>null</code> if
* this game has all the information. <br>
* <br>
* If this value is <code>null</code>, then it means that this
* <code>Game</code> object has access to all information (ie is the
* server model).
*
* @return The <code>Player</code> using this <code>Game</code>-object
* as a view.
*/
public Player getViewOwner() {
return viewOwner;
}
/**
* Finds a settlement by name.
*
* @param name The name of the <code>Settlement</code>.
* @return The <code>Settlement</code> or <code>null</code> if
* there is no known <code>Settlement</code> with the
* specified name (the settlement might not be visible to
* a client).
*/
public Settlement getSettlement(String name) {
for (Player p : getPlayers()) {
for (Settlement s : p.getSettlements()) {
if (name.equals(s.getName())) return s;
}
}
return null;
}
/**
* Gets the current turn in this game.
*
* @return The current <code>Turn</code>.
*/
public Turn getTurn() {
return turn;
}
/**
* Sets the current turn in this game.
*
* @param newTurn The new <code>Turn</code> to set.
*/
public void setTurn(Turn newTurn) {
turn = newTurn;
}
/**
* Get the <code>CombatModel</code> value.
*
* @return a <code>CombatModel</code> value
*/
public final CombatModel getCombatModel() {
return combatModel;
}
/**
* Set the <code>CombatModel</code> value.
*
* @param newCombatModel The new CombatModel value.
*/
public final void setCombatModel(final CombatModel newCombatModel) {
this.combatModel = newCombatModel;
}
/**
* Get the <code>OptionGroup</code> value.
*
* @return a <code>OptionGroup</code> value
*/
public final OptionGroup getDifficultyLevel() {
return specification.getDifficultyLevel();
}
/**
* Adds the specified player to the game.
*
* @param player The <code>Player</code> that shall be added to this
* <code>Game</code>.
*/
public void addPlayer(Player player) {
if (player.isAI() || canAddNewPlayer()) {
players.add(player);
Nation nation = getSpecification().getNation(player.getNationID());
nationOptions.getNations().put(nation, NationState.NOT_AVAILABLE);
if (currentPlayer == null) {
currentPlayer = player;
}
} else {
logger.warning("Game already full, but tried to add: "
+ player.getName());
}
}
/**
* Removes the specified player from the game.
*
* @param player The <code>Player</code> that shall be removed from this
* <code>Game</code>.
*/
public void removePlayer(Player player) {
boolean updateCurrentPlayer = (currentPlayer == player);
players.remove(players.indexOf(player));
Nation nation = getSpecification().getNation(player.getNationID());
nationOptions.getNations().put(nation, NationState.AVAILABLE);
player.dispose();
if (updateCurrentPlayer) {
currentPlayer = getFirstPlayer();
}
}
/**
* Registers a new <code>FreeColGameObject</code> with the specified ID.
*
* @param id The unique ID of the <code>FreeColGameObject</code>.
* @param freeColGameObject The <code>FreeColGameObject</code> that shall
* be added to this <code>Game</code>.
* @exception IllegalArgumentException If either <code>id</code>
* or <code>freeColGameObject </code> are
* <i>null</i>.
*/
public void setFreeColGameObject(String id, FreeColGameObject freeColGameObject) {
if (id == null || id.equals("")) {
throw new IllegalArgumentException("Parameter 'id' must not be 'null' or empty string.");
} else if (freeColGameObject == null) {
throw new IllegalArgumentException("Parameter 'freeColGameObject' must not be 'null'.");
}
final WeakReference<FreeColGameObject> wr = new WeakReference<FreeColGameObject>(freeColGameObject);
final FreeColGameObject old = getFreeColGameObjectSafely(id);
if (old != null) {
throw new IllegalArgumentException("Replacing FreeColGameObject "
+ id + ": " + old.getClass()
+ " with " + freeColGameObject.getClass());
}
freeColGameObjects.put(id, wr);
if (freeColGameObjectListener != null) {
freeColGameObjectListener.setFreeColGameObject(id, freeColGameObject);
}
}
public void setFreeColGameObjectListener(FreeColGameObjectListener freeColGameObjectListener) {
this.freeColGameObjectListener = freeColGameObjectListener;
}
public FreeColGameObjectListener getFreeColGameObjectListener() {
return freeColGameObjectListener;
}
/**
* Gets the <code>FreeColGameObject</code> with the specified ID.
*
* @param id The identifier of the <code>FreeColGameObject</code>.
* @return The <code>FreeColGameObject</code>.
* @exception IllegalArgumentException If <code>id == null</code>, or <code>id = ""</code>.
*/
public FreeColGameObject getFreeColGameObject(String id) {
if (id == null || id.equals("")) {
throw new IllegalArgumentException("Parameter 'id' must not be null or empty string.");
}
return getFreeColGameObjectSafely(id);
}
/**
* Get the {@link FreeColGameObject} with the given id or null. This method
* does NOT throw if the id is invalid.
*
* @param id The id, may be null or invalid.
* @return game object with id or null.
*/
public FreeColGameObject getFreeColGameObjectSafely(String id) {
if (id == null || id.length() == 0) {
return null;
}
final WeakReference<FreeColGameObject> ro = freeColGameObjects.get(id);
if (ro != null) {
final FreeColGameObject o = ro.get();
if (o != null) {
return o;
} else {
freeColGameObjects.remove(id);
}
}
return null;
}
/**
* Removes the <code>FreeColGameObject</code> with the specified ID.
*
* @param id The identifier of the <code>FreeColGameObject</code> that
* shall be removed from this <code>Game</code>.
* @return The <code>FreeColGameObject</code> that has been removed.
* @exception IllegalArgumentException If <code>id == null</code>, or <code>id = ""</code>.
*/
public FreeColGameObject removeFreeColGameObject(String id) {
if (id == null || id.equals("")) {
throw new IllegalArgumentException("Parameter 'id' must not be null or empty string.");
}
final FreeColGameObject o = getFreeColGameObjectSafely(id);
if (freeColGameObjectListener != null) {
freeColGameObjectListener.removeFreeColGameObject(id);
}
freeColGameObjects.remove(id);
return o;
}
/**
* Gets the <code>Map</code> that is being used in this game.
*
* @return The <code>Map</code> that is being used in this game or
* <i>null</i> if no <code>Map</code> has been created.
*/
public Map getMap() {
return map;
}
/**
* Sets the <code>Map</code> that is going to be used in this game.
*
* @param map The <code>Map</code> that is going to be used in this game.
*/
public void setMap(Map map) {
this.map = map;
for (Player player : getPlayers()) {
if (player.getHighSeas() != null) {
player.getHighSeas().addDestination(map);
}
}
}
/**
* Get the <code>NationOptions</code> value.
*
* @return a <code>NationOptions</code> value
*/
public final NationOptions getNationOptions() {
return nationOptions;
}
/**
* Set the <code>NationOptions</code> value.
*
* @param newNationOptions The new NationOptions value.
*/
public final void setNationOptions(final NationOptions newNationOptions) {
this.nationOptions = newNationOptions;
}
/**
* Returns a vacant nation.
*
* @return A vacant nation.
*/
public Nation getVacantNation() {
//System.out.println("NationOptions: " + nationOptions);
for (Entry<Nation, NationState> entry : nationOptions.getNations().entrySet()) {
if (entry.getValue() == NationState.AVAILABLE) {
return entry.getKey();
}
}
return null;
}
/**
* Return a <code>Player</code> identified by it's nation.
*
* @param nationID The nation.
* @return The <code>Player</code> of the given nation.
*/
public Player getPlayer(String nationID) {
Iterator<Player> playerIterator = getPlayerIterator();
while (playerIterator.hasNext()) {
Player player = playerIterator.next();
if (player.getNationID().equals(nationID)) {
return player;
}
}
return null;
}
/**
* Sets the current player.
*
* @param newCp The new current player.
*/
public void setCurrentPlayer(Player newCp) {
if (newCp != null) {
if (currentPlayer != null) {
currentPlayer.removeModelMessages();
currentPlayer.invalidateCanSeeTiles();
}
} else {
logger.info("Current player set to 'null'.");
}
currentPlayer = newCp;
}
/**
* Gets the current player. This is the <code>Player</code> currently
* playing the <code>Game</code>.
*
* @return The current player.
*/
public Player getCurrentPlayer() {
return currentPlayer;
}
/**
* Gets the next current player.
*
* @return The player that will start its turn as soon as the current player
* is ready.
* @see #getCurrentPlayer
*/
public Player getNextPlayer() {
return getPlayerAfter(currentPlayer);
}
/**
* Gets the player after the given player.
*
* @param beforePlayer The <code>Player</code> before the
* <code>Player</code> to be returned.
* @return The <code>Player</code> after the <code>beforePlayer</code>
* in the list which determines the order each player becomes the
* current player.
* @see #getNextPlayer
*/
public Player getPlayerAfter(Player beforePlayer) {
if (players.size() == 0) {
return null;
}
int index = players.indexOf(beforePlayer) + 1;
if (index >= players.size()) {
index = 0;
}
// Find first non-dead player:
while (true) {
Player player = players.get(index);
if (!player.isDead()) {
return player;
}
index++;
if (index >= players.size()) {
index = 0;
}
}
}
/**
* Gets the first player in this game.
*
* @return the <code>Player</code> that was first added to this
* <code>Game</code>.
*/
public Player getFirstPlayer() {
if (players.isEmpty()) {
return null;
} else {
return players.get(0);
}
}
/**
* Gets an <code>Iterator</code> of every registered
* <code>FreeColGameObject</code>.
*
* This <code>Iterator</code> should be iterated at least once
* in a while since it cleans the <code>FreeColGameObject</code>
* cache.
*
* @return an <code>Iterator</code> containing every registered
* <code>FreeColGameObject</code>.
* @see #setFreeColGameObject
*/
public Iterator<FreeColGameObject> getFreeColGameObjectIterator() {
return new Iterator<FreeColGameObject>() {
final Iterator<Entry<String, WeakReference<FreeColGameObject>>> it = freeColGameObjects.entrySet().iterator();
FreeColGameObject nextValue = null;
public boolean hasNext() {
while (nextValue == null) {
if (!it.hasNext()) {
return false;
}
final Entry<String, WeakReference<FreeColGameObject>> entry = it.next();
final WeakReference<FreeColGameObject> wr = entry.getValue();
final FreeColGameObject o = wr.get();
if (o == null) {
final String id = entry.getKey();
if (freeColGameObjectListener != null) {
freeColGameObjectListener.removeFreeColGameObject(id);
}
it.remove();
} else {
nextValue = o;
}
}
return nextValue != null;
}
public FreeColGameObject next() {
hasNext();
final FreeColGameObject o = nextValue;
nextValue = null;
return o;
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
/**
* Gets a <code>Player</code> specified by a name.
*
* @param name The name identifying the <code>Player</code>.
* @return The <code>Player</code>.
*/
public Player getPlayerByName(String name) {
Iterator<Player> playerIterator = getPlayerIterator();
while (playerIterator.hasNext()) {
Player player = playerIterator.next();
if (player.getName().equals(name)) {
return player;
}
}
return null;
}
/**
* Checks if the specified name is in use.
*
* @param username The name.
* @return <i>true</i> if the name is already in use and <i>false</i>
* otherwise.
*/
public boolean playerNameInUse(String username) {
for (Player player : players) {
if (player.getName().equals(username)) {
return true;
}
}
return false;
}
/**
* Gets an <code>Iterator</code> of every <code>Player</code> in this
* game.
*
* @return The <code>Iterator</code>.
*/
public Iterator<Player> getPlayerIterator() {
return players.iterator();
}
/**
* Gets an <code>Vector</code> containing every <code>Player</code> in
* this game.
*
* @return The <code>Vector</code>.
*/
public List<Player> getPlayers() {
return players;
}
public int getNumberOfPlayers() {
return players.size();
}
/**
* Returns all the live European players known by the player of this game.
*
* @return All the live European players known by the player of this game.
*/
public List<Player> getLiveEuropeanPlayers() {
List<Player> europeans = new ArrayList<Player>();
for (Player player : players) {
if (player.isEuropean() && !player.isDead()) {
europeans.add(player);
}
}
return europeans;
}
/**
* Checks if a new <code>Player</code> can be added.
*
* @return <i>true</i> if a new player can be added and <i>false</i>
* otherwise.
*/
public boolean canAddNewPlayer() {
return (getVacantNation() != null);
}
/**
* Checks if all players are ready to launch.
*
* @return <i>true</i> if all players are ready to launch and <i>false</i>
* otherwise.
*/
public boolean isAllPlayersReadyToLaunch() {
for (Player player : players) {
if (!player.isReady()) {
return false;
}
}
return true;
}
/**
* Get the <code>SpanishSuccession</code> value.
*
* @return a <code>boolean</code> value
*/
public final boolean getSpanishSuccession() {
return spanishSuccession;
}
/**
* Set the <code>SpanishSuccession</code> value.
*
* @param newSpanishSuccession The new SpanishSuccession value.
*/
public final void setSpanishSuccession(final boolean newSpanishSuccession) {
this.spanishSuccession = newSpanishSuccession;
}
/**
* Checks the integrity of this <code>Game</code
* by checking if there are any
* {@link FreeColGameObject#isUninitialized() uninitialized objects}.
*
* Detected problems gets written to the log.
*
* @return <code>true</code> if the <code>Game</code> has
* been loaded properly.
*/
public boolean checkIntegrity() {
List<String> brokenObjects = new ArrayList<String>();
boolean ok = true;
Iterator<FreeColGameObject> iterator = getFreeColGameObjectIterator();
while (iterator.hasNext()) {
FreeColGameObject fgo = iterator.next();
if (fgo.isUninitialized()) {
brokenObjects.add(fgo.getId());
logger.warning("Uninitialized object: " + fgo.getId() + " (" + fgo.getClass() + ")");
ok = false;
}
}
if (ok) {
logger.info("Game integrity ok.");
} else {
logger.warning("Game integrity test failed.");
fixIntegrity(brokenObjects);
}
return ok;
}
/**
* Try to fix integrity problems
*/
private boolean fixIntegrity(List<String> list){
// try to update Units who may have missing info
for(Player player : this.getPlayers()){
for(Unit unit : player.getUnits()){
if(unit.getOwner() == null){
logger.warning("Fixing " + unit.getId() + ": owner missing");
unit.setOwner(player);
}
}
}
return false;
}
/**
* Gets the <code>MapGeneratorOptions</code> that is associated with this
* {@link Game}.
* @return <code>OptionGroup</code>
*/
public OptionGroup getMapGeneratorOptions() {
return specification.getOptionGroup("mapGeneratorOptions");
}
/**
* Initialize the list of cities of Cibola.
* Pull them out of the message file and randomize the order.
*/
private void initializeCitiesOfCibola() {
citiesOfCibola = new ArrayList<String>();
for (int index = 0; index < 7; index++) {
citiesOfCibola.add("lostCityRumour.cityName." + index);
}
Collections.shuffle(citiesOfCibola);
}
/**
* Get the next name for a city of Cibola.
*
* @return The next name for a city of Cibola, or null if none available.
*/
public String getCityOfCibola() {
if (citiesOfCibola == null) initializeCitiesOfCibola();
return (citiesOfCibola.size() == 0) ? null : citiesOfCibola.remove(0);
}
/**
* Helper function to get the source object of a message in this game.
*
* @param message The <code>ModelMessage</code> to find the object in.
* @return The source object.
*/
public FreeColGameObject getMessageSource(ModelMessage message) {
return getFreeColGameObjectSafely(message.getSourceId());
}
/**
* Helper function to get the object to display with a message in
* this game.
*
* @param message The <code>ModelMessage</code> to find the object in.
* @return An object to display.
*/
public FreeColObject getMessageDisplay(ModelMessage message) {
String id = message.getDisplayId();
if (id == null) id = message.getSourceId();
FreeColObject o = getFreeColGameObjectSafely(id);
if (o == null) {
try {
o = getSpecification().getType(id);
} catch (Exception e) {
o = null; // Ignore
}
}
return o;
}
/**
* Return the specification for this Game.
*
* @return a <code>Specification</code> value
*/
@Override
public Specification getSpecification() {
return specification;
}
/**
* Need to overwrite behavior of equals inherited from FreeColGameObject,
* since two games are not the same if they have the same id.
*/
@Override
public boolean equals(Object o) {
return this == o;
}
/**
* Gets the statistics of this game.
*
* @return A <code>Map</code> of the statistics.
*/
public java.util.Map<String, String> getStatistics() {
java.util.Map<String, String> stats = new HashMap<String, String>();
// Memory
System.gc();
long free = Runtime.getRuntime().freeMemory()/(1024*1024);
long total = Runtime.getRuntime().totalMemory()/(1024*1024);
long max = Runtime.getRuntime().maxMemory()/(1024*1024);
stats.put("freeMemory", Long.toString(free));
stats.put("totalMemory", Long.toString(total));
stats.put("maxMemory", Long.toString(max));
// Game objects
java.util.Map<String, Long> objStats = new HashMap<String, Long>();
long disposed = 0;
Iterator<FreeColGameObject> iter = getFreeColGameObjectIterator();
while (iter.hasNext()) {
FreeColGameObject obj = iter.next();
String className = obj.getClass().getSimpleName();
if (objStats.containsKey(className)) {
Long count = objStats.get(className);
count++;
objStats.put(className, count);
} else {
Long count = new Long(1);
objStats.put(className, count);
}
if (obj.isDisposed()) disposed++;
}
stats.put("disposed", Long.toString(disposed));
for (String k : objStats.keySet()) {
stats.put(k, Long.toString(objStats.get(k)));
}
return stats;
}
// comment of Janet: latter is a sick implementation as long as hashCode
// cannot be rooted back to class Object's functionality
/**
* This method writes an XML-representation of this object to the given
* stream.
*
* <br>
* <br>
*
* Only attributes visible to the given <code>Player</code> will be added
* to that representation if <code>showAll</code> is set to
* <code>false</code>.
*
* @param out The target stream.
* @param player The <code>Player</code> this XML-representation should be
* made for, or <code>null</code> if
* <code>showAll == true</code>.
* @param showAll Only attributes visible to <code>player</code> will be
* added to the representation if <code>showAll</code> is set
* to <i>false</i>.
* @param toSavedGame If <code>true</code> then information that is only
* needed when saving a game is added.
* @throws XMLStreamException if there are any problems writing to the
* stream.
*/
protected void toXMLImpl(XMLStreamWriter out, Player player,
boolean showAll, boolean toSavedGame)
throws XMLStreamException {
// Start element:
out.writeStartElement(getXMLElementTagName());
if (toSavedGame && !showAll) {
throw new IllegalArgumentException("showAll must be set to true when toSavedGame is true.");
}
out.writeAttribute(ID_ATTRIBUTE, getId());
out.writeAttribute("UUID", getUUID().toString());
out.writeAttribute("turn", Integer.toString(getTurn().getNumber()));
out.writeAttribute("spanishSuccession", Boolean.toString(spanishSuccession));
writeAttribute(out, "currentPlayer", currentPlayer);
if (toSavedGame) {
out.writeAttribute("nextID", Integer.toString(nextId));
}
specification.toXMLImpl(out);
if (citiesOfCibola == null) initializeCitiesOfCibola();
for (String cityName : citiesOfCibola) {
out.writeStartElement(CIBOLA_TAG);
out.writeAttribute(ID_ATTRIBUTE_TAG, cityName);
out.writeEndElement();
}
nationOptions.toXML(out);
// serialize players
Iterator<Player> playerIterator = getPlayerIterator();
while (playerIterator.hasNext()) {
Player p = playerIterator.next();
p.toXML(out, player, showAll, toSavedGame);
}
Player enemy = getUnknownEnemy();
if (enemy != null) enemy.toXML(out, player, showAll, toSavedGame);
// serialize map
if (map != null) map.toXML(out, player, showAll, toSavedGame);
/* Moved to within player. Last used in 0.9.x.
// serialize messages
playerIterator = getPlayerIterator();
while (playerIterator.hasNext()) {
Player p = playerIterator.next();
if (showAll || p.equals(player)) {
for (ModelMessage message : p.getModelMessages()) {
message.toXML(out);
}
}
}
*/
out.writeEndElement();
}
/**
* Initialize this object from an XML-representation of this object.
*
* @param in The input stream with the XML.
*/
@Override
protected void readFromXMLImpl(XMLStreamReader in)
throws XMLStreamException {
setId(in.getAttributeValue(null, ID_ATTRIBUTE));
String hs = in.getAttributeValue(null, "UUID");
if (hs != null) {
uuid = UUID.fromString(hs);
}
turn = new Turn(getAttribute(in, "turn", 1));
setSpanishSuccession(getAttribute(in, "spanishSuccession", false));
final String nextIDStr = in.getAttributeValue(null, "nextID");
if (nextIDStr != null) {
nextId = Integer.parseInt(nextIDStr);
}
final String currentPlayerStr = in.getAttributeValue(null, "currentPlayer");
if (currentPlayerStr != null) {
currentPlayer = (Player) getFreeColGameObject(currentPlayerStr);
if (currentPlayer == null) {
currentPlayer = new Player(this, currentPlayerStr);
players.add(currentPlayer);
}
} else {
currentPlayer = null;
}
citiesOfCibola = new ArrayList<String>(7);
OptionGroup gameOptions = null;
OptionGroup mapGeneratorOptions = null;
while (in.nextTag() != XMLStreamConstants.END_ELEMENT) {
String tagName = in.getLocalName();
logger.finest("Found tag " + tagName);
if (tagName.equals("gameOptions") || tagName.equals("game-options")) {
// @compat 0.9.x
gameOptions = new OptionGroup(specification);
gameOptions.readFromXML(in);
} else if (tagName.equals(NationOptions.getXMLElementTagName())) {
if (nationOptions == null) {
nationOptions = new NationOptions(specification, Advantages.SELECTABLE);
}
nationOptions.readFromXML(in);
} else if (tagName.equals(Player.getXMLElementTagName())) {
Player player = (Player) getFreeColGameObject(in.getAttributeValue(null, ID_ATTRIBUTE));
if (player == null) {
player = new Player(this, in);
if (player.isUnknownEnemy()) {
setUnknownEnemy(player);
} else {
players.add(player);
}
} else {
player.readFromXML(in);
}
} else if (tagName.equals(Map.getXMLElementTagName())) {
String mapId = in.getAttributeValue(null, ID_ATTRIBUTE);
map = (Map) getFreeColGameObject(mapId);
if (map == null) {
map = new Map(this, mapId);
}
map.readFromXML(in);
} else if (tagName.equals(ModelMessage.getXMLElementTagName())) {
// @compat 0.9.x
ModelMessage m = new ModelMessage();
m.readFromXML(in);
// When this goes, remove getOwnerId().
String owner = m.getOwnerId();
if (owner != null) {
Player player = (Player) getFreeColGameObjectSafely(owner);
player.addModelMessage(m);
}
} else if (tagName.equals("citiesOfCibola")) {
// @compat 0.9.x
citiesOfCibola = readFromListElement("citiesOfCibola", in, String.class);
} else if (tagName.equals(CIBOLA_TAG)) {
citiesOfCibola.add(in.getAttributeValue(null, ID_ATTRIBUTE_TAG));
in.nextTag();
} else if (OptionGroup.getXMLElementTagName().equals(tagName)
|| "difficultyLevel".equals(tagName)) {
// @compat 0.9.x
OptionGroup difficultyLevel = new OptionGroup(specification);
difficultyLevel.readFromXML(in);
} else if (MapGeneratorOptions.getXMLElementTagName().equals(tagName)) {
// @compat 0.9.x
mapGeneratorOptions = new OptionGroup(specification);
mapGeneratorOptions.readFromXML(in);
} else if (Specification.getXMLElementTagName().equals(tagName)) {
Specification spec = new Specification();
spec.readFromXMLImpl(in);
if (specification == null) {
specification = spec;
specification.clean();
}
} else {
logger.warning("Unknown tag: " + tagName + " loading game");
in.nextTag();
}
}
// sanity check: we should be on the closing tag
if (!in.getLocalName().equals(Game.getXMLElementTagName())) {
logger.warning("Error parsing xml: expecting closing tag </" + Game.getXMLElementTagName() + "> "+
"found instead: " + in.getLocalName());
}
// @compat 0.9.x
if (gameOptions != null) {
addOldOptions(gameOptions);
}
if (mapGeneratorOptions != null) {
addOldOptions(mapGeneratorOptions);
}
// end compatibility code
}
// @compat 0.9.x
private void addOldOptions(OptionGroup group) {
Iterator<Option> iterator = group.iterator();
while (iterator.hasNext()) {
Option opt = iterator.next();
if (opt instanceof IntegerOption) {
IntegerOption option = (IntegerOption) opt;
if (specification.hasOption(option.getId())) {
specification.getIntegerOption(option.getId())
.setValue(option.getValue());
} else {
specification.addAbstractOption(option);
}
} else if (opt instanceof BooleanOption) {
BooleanOption option = (BooleanOption) opt;
if (specification.hasOption(option.getId())) {
specification.getBooleanOption(option.getId())
.setValue(option.getValue());
} else {
specification.addAbstractOption(option);
}
}
}
}
// end compatibility code
/**
* Partial writer, so that simple updates can be brief.
*
* @param out The target stream.
* @param fields The fields to write.
* @throws XMLStreamException If there are problems writing the stream.
*/
@Override
protected void toXMLPartialImpl(XMLStreamWriter out, String[] fields)
throws XMLStreamException {
toXMLPartialByClass(out, getClass(), fields);
}
/**
* Partial reader, so that simple updates can be brief.
*
* @param in The input stream with the XML.
* @throws XMLStreamException If there are problems reading the stream.
*/
@Override
protected void readFromXMLPartialImpl(XMLStreamReader in)
throws XMLStreamException {
readFromXMLPartialByClass(in, getClass());
}
/**
* Returns the tag name of the root element representing this object.
*
* @return "game".
*/
public static String getXMLElementTagName() {
return "game";
}
}