/** * 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 * MERCHANTLIMIT 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.reflect.Method; import java.util.LinkedList; import java.util.List; import java.util.logging.Logger; import org.freecolandroid.xml.stream.XMLStreamException; import org.freecolandroid.xml.stream.XMLStreamReader; import org.freecolandroid.xml.stream.XMLStreamWriter; /** * The <code>Operand</code> class implements Operands to be used in * relations, such as the Limit class. The OperandType specifies which * types of objects will be considered, and the ScopeLevel specifies * the level at which these objects are to be selected. If the * ScopeLevel is PLAYER, for example, and the OperandType is UNITS, * then all units owned by a particular player will be considered. * * Since the class inherits from Scope, the choice of objects can be * further refined by specifying type, ability or method. However, the * return value of the method must be an Integer (or int), since this * value will be returned as the value of the Operand itself if the * OperandType is NONE. */ public class Operand extends Scope { private static final Logger logger = Logger.getLogger(Operand.class.getName()); public static enum OperandType { UNITS, BUILDINGS, SETTLEMENTS, FOUNDING_FATHERS, YEAR, OPTION, NONE } public static enum ScopeLevel { SETTLEMENT, PLAYER, GAME, NONE } /** * Describe operandType here. */ private OperandType operandType = OperandType.NONE; /** * Describe scopeLevel here. */ private ScopeLevel scopeLevel = ScopeLevel.NONE; /** * A fixed rather than a dynamic Integer value. */ private Integer value = null; /** * Creates a new <code>Operand</code> instance. * */ public Operand() { // empty constructor } /** * Creates a new <code>Operand</code> instance. * * @param value an <code>int</code> value */ public Operand(int value) { this.value = value; } /** * Creates a new <code>Operand</code> instance. * * @param operandType an <code>OperandType</code> value * @param scopeLevel a <code>ScopeLevel</code> value */ public Operand(OperandType operandType, ScopeLevel scopeLevel) { this.operandType = operandType; this.scopeLevel = scopeLevel; } /** * Get the <code>OperandType</code> value. * * @return an <code>OperandType</code> value */ public final OperandType getOperandType() { return operandType; } /** * Set the <code>OperandType</code> value. * * @param newOperandType The new OperandType value. */ public final void setOperandType(final OperandType newOperandType) { this.operandType = newOperandType; } /** * Get the <code>ScopeLevel</code> value. * * @return a <code>ScopeLevel</code> value */ public final ScopeLevel getScopeLevel() { return scopeLevel; } /** * Set the <code>ScopeLevel</code> value. * * @param newScopeLevel The new ScopeLevel value. */ public final void setScopeLevel(final ScopeLevel newScopeLevel) { this.scopeLevel = newScopeLevel; } /** * Get the <code>Value</code> value. * * @return an <code>Integer</code> value */ public final Integer getValue() { return value; } /** * Set the <code>Value</code> value. * * @param newValue The new Value value. */ public final void setValue(final Integer newValue) { this.value = newValue; } /** * Returns an Integer value if this Operand is applicable to the * given Game, and <code>null</code> otherwise. * * @param game a <code>Game</code> value * @return an <code>Integer</code> value */ public Integer getValue(Game game) { if (value == null) { if (scopeLevel == ScopeLevel.GAME){ return calculateGameValue(game); } else { return null; } } else { return value; } } private Integer calculateGameValue(Game game) { switch(operandType) { case NONE: if (getMethodName() != null) { try { Method method = game.getClass().getMethod(getMethodName()); if (method != null && Integer.class.isAssignableFrom(method.getReturnType())) { return (Integer) method.invoke(game); } } catch(Exception e) { logger.warning(e.toString()); } } return null; case YEAR: return game.getTurn().getYear(); case OPTION: return game.getSpecification().getInteger(getType()); default: List<FreeColObject> list = new LinkedList<FreeColObject>(); for (Player player : game.getPlayers()) { switch(operandType) { case UNITS: list.addAll(player.getUnits()); break; case BUILDINGS: for (Colony colony : player.getColonies()) { list.addAll(colony.getBuildings()); } break; case SETTLEMENTS: list.addAll(player.getSettlements()); break; case FOUNDING_FATHERS: list.addAll(player.getFathers()); break; default: return null; } } return count(list); } } /** * Returns an Integer value if this Operand is applicable to the * given Player, and <code>null</code> otherwise. * * @param player a <code>Player</code> value * @return an <code>Integer</code> value */ public Integer getValue(Player player) { if (value == null) { if (scopeLevel == ScopeLevel.PLAYER) { List<FreeColObject> list = new LinkedList<FreeColObject>(); switch(operandType) { case UNITS: list.addAll(player.getUnits()); break; case BUILDINGS: for (Colony colony : player.getColonies()) { list.addAll(colony.getBuildings()); } break; case SETTLEMENTS: list.addAll(player.getSettlements()); break; case FOUNDING_FATHERS: list.addAll(player.getFathers()); break; default: if (getMethodName() != null) { try { Method method = player.getClass().getMethod(getMethodName()); if (method != null && (int.class.equals(method.getReturnType()) || Integer.class.equals(method.getReturnType()))) { return (Integer) method.invoke(player); } } catch(Exception e) { logger.warning(e.toString()); return null; } } return null; } return count(list); } else if (scopeLevel == ScopeLevel.GAME) { return getValue(player.getGame()); } else { return null; } } else { return value; } } /** * Returns an Integer value if this Operand is applicable to the * given Settlement, and <code>null</code> otherwise. Currently, * this only works for Colonies. * * @param settlement a <code>Settlement</code> value * @return an <code>Integer</code> value */ public Integer getValue(Settlement settlement) { if (value == null) { if (scopeLevel == ScopeLevel.SETTLEMENT && settlement instanceof Colony) { Colony colony = (Colony) settlement; List<FreeColObject> list = new LinkedList<FreeColObject>(); switch(operandType) { case UNITS: list.addAll(colony.getUnitList()); break; case BUILDINGS: list.addAll(colony.getBuildings()); break; default: if (getMethodName() != null) { try { Method method = colony.getClass().getMethod(getMethodName()); if (method != null && Integer.class.isAssignableFrom(method.getReturnType())) { return (Integer) method.invoke(colony); } } catch(Exception e) { logger.warning(e.toString()); return null; } } return null; } return count(list); } else { // in future, we might expand this to handle native // settlements return null; } } else { return value; } } /** * Describe <code>count</code> method here. * * @return an <code>int</code> value */ private int count(List<FreeColObject> objects) { int result = 0; for (FreeColObject object : objects) { if (appliesTo(object)) { result++; } } return result; } /** * 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("operandType", operandType.toString()); out.writeAttribute("scopeLevel", scopeLevel.toString()); if (value != null) { out.writeAttribute(VALUE_TAG, value.toString()); } } /** * Reads the attributes of this object from an XML stream. * * @param in The XML input stream. * @throws XMLStreamException if a problem was encountered * during parsing. */ @Override protected void readAttributes(XMLStreamReader in) throws XMLStreamException { super.readAttributes(in); String attribute = in.getAttributeValue(null, "operandType"); if (attribute != null) { operandType = Enum.valueOf(OperandType.class, attribute); } attribute = in.getAttributeValue(null, "scopeLevel"); if (attribute != null) { scopeLevel = Enum.valueOf(ScopeLevel.class, attribute); } attribute = in.getAttributeValue(null, VALUE_TAG); if (attribute != null) { value = new Integer(attribute); } } @Override public String toString() { if (value == null) { return scopeLevel + "'s number of " + operandType + "s"; } else { return Integer.toString(value); } } }