/**
* 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.server.ai;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.logging.Logger;
import net.sf.freecol.common.model.Colony;
import net.sf.freecol.common.model.FreeColGameObject;
import net.sf.freecol.common.model.FreeColGameObjectListener;
import net.sf.freecol.common.model.FreeColObject;
import net.sf.freecol.common.model.Game;
import net.sf.freecol.common.model.Player;
import net.sf.freecol.common.model.Unit;
import net.sf.freecol.common.util.Utils;
import net.sf.freecol.server.FreeColServer;
import net.sf.freecol.server.model.ServerPlayer;
import org.freecolandroid.xml.stream.XMLStreamConstants;
import org.freecolandroid.xml.stream.XMLStreamException;
import org.freecolandroid.xml.stream.XMLStreamReader;
import org.freecolandroid.xml.stream.XMLStreamWriter;
import org.w3c.dom.Element;
/**
* The main AI-class. Keeps references to all other AI-classes.
*/
public class AIMain extends FreeColObject
implements FreeColGameObjectListener {
private static final Logger logger = Logger.getLogger(AIMain.class.getName());
private FreeColServer freeColServer;
private int nextID = 1;
/**
* Contains mappings between <code>FreeColGameObject</code>s
* and <code>AIObject</code>s.
*/
private HashMap<String, AIObject> aiObjects = new HashMap<String, AIObject>();
/**
* Creates a new <code>AIMain</code> and searches the current
* game for <code>FreeColGameObject</code>s.
*
* @param freeColServer The main controller object for the
* server.
* @see #findNewObjects()
*/
public AIMain(FreeColServer freeColServer) {
this.freeColServer = freeColServer;
findNewObjects();
}
/**
* Creates a new <code>AIMain</code> and reads the given element.
*
* @param freeColServer The main controller object for the
* server.
* @param element The <code>Element</code> (in a DOM-parsed XML-tree)
* that describes this object.
* @see #readFromXMLElement
*/
public AIMain(FreeColServer freeColServer, Element element) {
this(freeColServer);
readFromXMLElement(element);
}
/**
* Creates a new <code>AIMain</code> and reads the given element.
*
* @param freeColServer The main controller object for the
* server.
* @param in The input stream containing the XML.
* @throws XMLStreamException if a problem was encountered
* during parsing.
* @see #readFromXML
*/
public AIMain(FreeColServer freeColServer, XMLStreamReader in) throws XMLStreamException {
this(freeColServer);
readFromXML(in);
}
/**
* Gets the main controller object for the server.
* @return The <code>FreeColServer</code>-object.
*/
public FreeColServer getFreeColServer() {
return freeColServer;
}
/**
* Gets a unique ID for identifying an <code>AIObject</code>.
* @return A unique ID.
*/
public String getNextID() {
String id = "am" + Integer.toString(nextID);
nextID++;
return id;
}
/**
* Gets a random value from the server to use for individual AI player
* PRNG seeds.
*
* @param logMe A logging string.
* @return A random seed.
*/
public int getRandomSeed(String logMe) {
return Utils.randomInt(logger, logMe, freeColServer.getServerRandom(),
Integer.MAX_VALUE);
}
/**
* Checks the integrity of this <code>AIMain</code>
* by checking if there are any
* {@link AIObject#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() {
// Workaround for BR#3456180.
// Remove when GoodsWishes are working properly again.
for (AIObject ao : new ArrayList<AIObject>(aiObjects.values())) {
if (ao instanceof GoodsWish) {
GoodsWish gw = (GoodsWish)ao;
Transportable tr = gw.getTransportable();
if (tr != null
&& tr instanceof AIGoods
&& ((AIGoods)tr).isUninitialized()) {
gw.setTransportable(null);
((AIGoods)tr).dispose();
logger.warning("Dropping bad GoodsWish: " + gw);
gw.dispose();
}
}
}
boolean ok = true;
for (AIObject ao : aiObjects.values()) {
if (ao.isUninitialized()) {
logger.warning("Uninitialized object: " + ao.getId()
+ " (" + ao.getClass() + ")");
ok = false;
}
}
Iterator<FreeColGameObject> fit = getGame().getFreeColGameObjectIterator();
while (fit.hasNext()) {
FreeColGameObject f = fit.next();
if ((f instanceof Unit
|| f instanceof Colony
|| (f instanceof Player && !((Player)f).isUnknownEnemy()))
&& !aiObjects.containsKey(f.getId())) {
logger.warning("Missing AIObject for: " + f.getId());
ok = false;
}
}
if (ok) {
logger.info("AIMain integrity ok.");
} else {
logger.warning("AIMain integrity test failed.");
}
return ok;
}
/**
* Returns the game.
* @return The <code>Game</code>.
*/
public Game getGame() {
return freeColServer.getGame();
}
/**
* Searches for new {@link FreeColGameObject FreeColGameObjects}. An AI-object is
* created for each object.
*
* <br><br>
*
* Note: Any existing <code>AIObject</code>s will be overwritten.
* @see #findNewObjects(boolean)
*/
private void findNewObjects() {
findNewObjects(true);
}
/**
* Searches for new {@link FreeColGameObject FreeColGameObjects}. An AI-object is
* created for each new object.
* @param overwrite Determines wether any old <code>AIObject</code>
* should be overwritten or not.
*/
public void findNewObjects(boolean overwrite) {
Iterator<FreeColGameObject> i = freeColServer.getGame().getFreeColGameObjectIterator();
while (i.hasNext()) {
FreeColGameObject fcgo = i.next();
if (overwrite || getAIObject(fcgo) == null) {
setFreeColGameObject(fcgo.getId(), fcgo);
}
}
}
/**
* Gets the <code>AIObject</code> for the given
* <code>FreeColGameObject</code>.
*
* @param fcgo The <code>FreeColGameObject</code> to find
* the <code>AIObject</code> for.
* @see #getAIObject(String)
* @return The <code>AIObject</code>.
*/
public AIObject getAIObject(FreeColGameObject fcgo) {
return getAIObject(fcgo.getId());
}
/**
* Gets the <code>AIObject</code> identified by the given ID.
*
* @param id The ID of the <code>AIObject</code>.
* @see #getAIObject(FreeColGameObject)
* @return The <code>AIObject</code>.
*/
public AIObject getAIObject(String id) {
return aiObjects.get(id);
}
/**
* Gets the AI colony corresponding to a given colony.
*
* @param colony The <code>Colony</code> to look up.
* @return The corresponding AI colony, or null if not found.
*/
public AIColony getAIColony(Colony colony) {
AIObject aio = getAIObject(colony.getId());
return (aio instanceof AIColony) ? (AIColony) aio : null;
}
/**
* Gets the AI player corresponding to a given player.
*
* @param player The <code>Player</code> to look up.
* @return The corresponding AI player, or null if not found.
*/
public AIPlayer getAIPlayer(Player player) {
AIObject aio = getAIObject(player.getId());
return (aio instanceof AIPlayer) ? (AIPlayer) aio : null;
}
/**
* Gets the AI unit corresponding to a given unit.
*
* @param unit The <code>Unit</code> to look up.
* @return The corresponding AI unit, or null if not found.
*/
public AIUnit getAIUnit(Unit unit) {
AIObject aio = getAIObject(unit.getId());
return (aio instanceof AIUnit) ? (AIUnit) aio : null;
}
/**
* Adds a reference to the given <code>AIObject</code>.
*
* @param id The ID of the <code>AIObject</code>.
* @param aiObject The <code>AIObject</code> to store a reference
* for.
* @exception IllegalStateException if an <code>AIObject</code> with
* the same <code>id</code> has already been created.
*/
public void addAIObject(String id, AIObject aiObject) {
if (aiObjects.containsKey(id)) {
throw new IllegalStateException("AIObject already created: " + id);
}
if (aiObject == null) {
throw new NullPointerException("aiObject == null");
}
aiObjects.put(id, aiObject);
}
/**
* Removes a reference to the given <code>AIObject</code>.
*
* @param id The ID of the <code>AIObject</code>.
*/
public void removeAIObject(String id) {
aiObjects.remove(id);
}
/**
* Gets the <code>FreeColGameObject</code> with the given ID.
* This is just a convenience method for:
* {@link Game#getFreeColGameObject}
*
* @param id The ID of the <code>FreeColGameObject</code> to find.
* @return The <code>FreeColGameObject</code>.
*/
public FreeColGameObject getFreeColGameObject(String id) {
return freeColServer.getGame().getFreeColGameObject(id);
}
public void ownerChanged(FreeColGameObject source, Player oldOwner, Player newOwner) {
AIObject ao = getAIObject(source);
if (ao != null) {
ao.dispose();
setFreeColGameObject(source.getId(), source);
}
}
/**
* Creates a new <code>AIObject</code> for a given
* <code>FreeColGameObject</code>. This method gets called
* whenever a new object gets added to the {@link Game}.
*
* @param id The ID of the <code>FreeColGameObject</code> to add.
* @param freeColGameObject The <code>FreeColGameObject</code> to add.
* @see AIObject
* @see FreeColGameObject
* @see FreeColGameObject#getId
*/
public void setFreeColGameObject(String id, FreeColGameObject freeColGameObject) {
if (aiObjects.containsKey(id)) {
return;
}
if (!id.equals(freeColGameObject.getId())) {
throw new IllegalArgumentException("!id.equals(freeColGameObject.getId())");
}
if (freeColGameObject instanceof Unit) {
new AIUnit(this, (Unit) freeColGameObject);
} else if (freeColGameObject instanceof ServerPlayer) {
ServerPlayer p = (ServerPlayer) freeColGameObject;
if (p.isIndian()) {
new NativeAIPlayer(this, p);
} else if (p.isREF()) {
new REFAIPlayer(this, p);
} else if (p.isEuropean()) {
new EuropeanAIPlayer(this, p);
}
} else if (freeColGameObject instanceof Colony) {
new AIColony(this, (Colony) freeColGameObject);
}
}
/**
* Removes the <code>AIObject</code> for the given <code>FreeColGameObject</code>.
* @param id The ID of the <code>FreeColGameObject</code>.
*/
public void removeFreeColGameObject(String id) {
AIObject o = getAIObject(id);
if (o != null) {
o.dispose();
}
removeAIObject(id);
}
/**
* Computes how many objects of each class have been created, to
* track memory leaks over time
*/
public HashMap<String, String> getAIStatistics() {
HashMap<String, String> stats = new HashMap<String, String>();
HashMap<String, Long> objStats = new HashMap<String, Long>();
Iterator<AIObject> iter = aiObjects.values().iterator();
while (iter.hasNext()) {
AIObject 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);
}
}
for (String k : objStats.keySet()) {
stats.put(k, Long.toString(objStats.get(k)));
}
return stats;
}
/**
* Writes all of the <code>AIObject</code>s and other AI-related
* information to an XML-stream.
*
* @param out The target stream.
* @throws XMLStreamException if there are any problems writing
* to the stream.
*/
protected void toXMLImpl(XMLStreamWriter out) throws XMLStreamException {
super.toXML(out, getXMLElementTagName());
}
/**
* Write the attributes of this object to a stream.
*
* @param out The target stream.
* @throws XMLStreamException if there are any problems writing
* to the stream.
*/
@Override
protected void writeAttributes(XMLStreamWriter out)
throws XMLStreamException {
super.writeAttributes(out);
out.writeAttribute("nextID", Integer.toString(nextID));
}
/**
* Write the children of this object to a stream.
*
* @param out The target stream.
* @throws XMLStreamException if there are any problems writing
* to the stream.
*/
@Override
protected void writeChildren(XMLStreamWriter out)
throws XMLStreamException {
super.writeChildren(out);
for (AIObject aio : new ArrayList<AIObject>(aiObjects.values())) {
if ((aio instanceof Wish) && !((Wish) aio).shouldBeStored()) {
continue;
}
try {
if (aio.getId() != null) {
aio.toXML(out);
} else {
logger.warning("aio.getId() == null, for: "
+ aio.getClass().getName());
}
} catch (Exception e) {
StringWriter sw = new StringWriter();
e.printStackTrace(new PrintWriter(sw));
logger.warning(sw.toString());
}
}
}
/**
* Reads all the <code>AIObject</code>s and other AI-related information
* from XML data.
*
* @param in The input stream with the XML.
* @throws XMLStreamException if an error occured during parsing.
*/
protected void readFromXMLImpl(XMLStreamReader in)
throws XMLStreamException {
aiObjects.clear();
if (!in.getLocalName().equals(getXMLElementTagName())) {
logger.warning("Expected element name, got: " + in.getLocalName());
}
final String nextIDStr = in.getAttributeValue(null, "nextID");
if (nextIDStr != null) {
nextID = Integer.parseInt(nextIDStr);
}
String lastTag = "";
while (in.nextTag() != XMLStreamConstants.END_ELEMENT) {
final String tagName = in.getLocalName();
final String oid = in.getAttributeValue(null, ID_ATTRIBUTE);
try {
if (oid != null && aiObjects.containsKey(oid)) {
getAIObject(oid).readFromXML(in);
} else if (tagName.equals(AIUnit.getXMLElementTagName())) {
new AIUnit(this, in);
} else if (tagName.equals(AIPlayer.getXMLElementTagName())) {
Player p = (Player) getGame().getFreeColGameObject(oid);
if (p != null) {
if (p.isIndian()) {
new NativeAIPlayer(this, in);
} else if (p.isREF()) {
new REFAIPlayer(this, in);
} else if (p.isEuropean()) {
new EuropeanAIPlayer(this, in);
} else {
logger.warning("Bogus AIPlayer: " + p);
in.nextTag();
}
}
// @compat 0.10.1
} else if (tagName.equals("colonialAIPlayer")) {
new EuropeanAIPlayer(this, in);
// end compatibility code
} else if (tagName.equals(AIColony.getXMLElementTagName())) {
new AIColony(this, in);
} else if (tagName.equals(AIGoods.getXMLElementTagName())) {
new AIGoods(this, in);
} else if (tagName.equals(WorkerWish.getXMLElementTagName())) {
new WorkerWish(this, in);
} else if (tagName.equals(GoodsWish.getXMLElementTagName())
// @compat 0.10.3
|| tagName.equals("GoodsWish")
// end compatibility code
) {
new GoodsWish(this, in);
} else if (tagName.equals(TileImprovementPlan.getXMLElementTagName())
// @compat 0.10.3
|| tagName.equals("tileimprovementplan")
// end compatibility code
) {
new TileImprovementPlan(this, in);
} else {
logger.warning("Unknown AI-object read: " + tagName + "(" + lastTag + ")");
}
lastTag = in.getLocalName();
} catch (Exception e) {
StringWriter sw = new StringWriter();
e.printStackTrace(new PrintWriter(sw));
logger.warning("Exception while reading an AIObject(" + tagName
+ ", " + oid + "): " + sw.toString());
while (!in.getLocalName().equals(tagName) && !in.getLocalName().equals(getXMLElementTagName())) {
in.nextTag();
}
if (!in.getLocalName().equals(getXMLElementTagName())) {
in.nextTag();
}
}
}
if (!in.getLocalName().equals(getXMLElementTagName())) {
logger.warning("Expected element name (2), got: " + in.getLocalName());
}
// This should not be necessary - but just in case:
findNewObjects(false);
}
/**
* Returns the tag name of the root element representing this object.
*
* @return "aiMain"
*/
public static String getXMLElementTagName() {
return "aiMain";
}
}