/** * 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.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.logging.Level; import java.util.logging.Logger; import javax.xml.stream.XMLStreamConstants; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamReader; import javax.xml.stream.XMLStreamWriter; 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.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()); /** The server that this AI is operating within. */ private FreeColServer freeColServer; /** The next AI identifier index. */ private int nextId = 1; /** * Contains mappings between <code>FreeColGameObject</code>s * and <code>AIObject</code>s. */ private final 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; } /** * Convenience accessor for the Game. * * @return The <code>Game</code> this AI is operating in. */ public Game getGame() { return freeColServer.getGame(); } /** * Gets a unique identifier for an <code>AIObject</code>. * * @return A unique identifier. */ 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 invalid objects. * * Detected problems are logged. * * @return <code>true</code> if the <code>Game</code> has * been loaded properly. */ public boolean checkIntegrity() { boolean ok = true; for (AIObject ao : aiObjects.values()) { if (!ao.checkIntegrity()) { logger.warning("Invalid AIObject: " + 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; } } return ok; } /** * Fixes some integrity problems of this <code>AIMain</code>. * * @return True if the integrity problems are fixed. */ public boolean fixIntegrity() { for (AIObject ao : new ArrayList<AIObject>(aiObjects.values())) { if (!ao.checkIntegrity()) { logger.warning("Dropping invalid AIObject: " + ao.getId() + " (" + ao.getClass() + ")"); ao.fixIntegrity(); } } 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("Added missing AIObject for: " + f.getId()); setFreeColGameObject(f.getId(), f); } } return checkIntegrity(); } /** * Searches for new {@link FreeColGameObject * FreeColGameObjects}. An AI-object is created for each object. * * 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); } /** * Replaces the AI object when ownership changes. * * @param source The <code>FreeColGameObject</code> that has changed. * @param oldOwner The old owning <code>Player</code>. * @param newOwner The new owning <code>Player</code>. */ public void ownerChanged(FreeColGameObject source, Player oldOwner, Player newOwner) { AIObject ao = getAIObject(source); logger.finest("Owner changed for " + source.getId() + " with AI object: " + ao); 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 fcgo The <code>FreeColGameObject</code> to add. * @see AIObject * @see FreeColGameObject * @see FreeColGameObject#getId */ public void setFreeColGameObject(String id, FreeColGameObject fcgo) { if (aiObjects.containsKey(id)) return; if (!id.equals(fcgo.getId())) { throw new IllegalArgumentException("!id.equals(fcgo.getId())"); } if (fcgo instanceof Colony) { new AIColony(this, (Colony)fcgo); } else if (fcgo instanceof ServerPlayer) { ServerPlayer p = (ServerPlayer)fcgo; 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 (fcgo instanceof Unit) { new AIUnit(this, (Unit)fcgo); } } /** * Removes the <code>AIObject</code> for a given AI id. * Needed for interface FreeColGameObjectListener. * * @param id The AI object identifier. */ 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; } // Serialization /** * 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 { 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); // Using a copy of the objects defensively against races. for (AIObject aio : new ArrayList<AIObject>(aiObjects.values())) { if (!aio.checkIntegrity()) { logger.warning("Integrity failure: " + aio); continue; } if (aio instanceof Wish) { Wish wish = (Wish)aio; if (!wish.shouldBeStored()) continue; } try { if (aio.getId() == null) { logger.warning("Null AI identifier for: " + aio.getClass().getName()); } else { aio.toXML(out); } } catch (Exception e) { logger.log(Level.WARNING, "Failed to write AI object: " + aio, e); } } } /** * 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 readAttributes(XMLStreamReader in) throws XMLStreamException { aiObjects.clear(); if (!in.getLocalName().equals(getXMLElementTagName())) { logger.warning("Expected element name, got: " + in.getLocalName()); } nextId = getAttribute(in, "nextID", 0); } protected void readChildren(XMLStreamReader in) throws XMLStreamException { String lastTag = ""; Wish wish; while (in.nextTag() != XMLStreamConstants.END_ELEMENT) { final String tagName = in.getLocalName(); final String oid = in.getAttributeValue(null, ID_ATTRIBUTE); wish = null; 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 = getGame().getFreeColGameObject(oid, Player.class); 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())) { wish = new WorkerWish(this, in); } else if (tagName.equals(GoodsWish.getXMLElementTagName()) // @compat 0.10.3 || tagName.equals("GoodsWish") // end compatibility code ) { wish = 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 { throw new IllegalStateException("Unknown AI-object read: " + tagName + "(" + lastTag + ")"); } if (wish != null) { AIColony ac = wish.getDestinationAIColony(); if (ac != null) ac.addWish(wish); } lastTag = in.getLocalName(); } catch (Exception e) { logger.log(Level.WARNING, "Exception reading AIObject(" + tagName + ", " + oid + ")", e); 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 closing element name, got: " + in.getLocalName()); } // TODO: This should not be necessary. Try dropping it. findNewObjects(false); } /** * Returns the tag name of the root element representing this object. * * @return "aiMain" */ public static String getXMLElementTagName() { return "aiMain"; } }