freecol/src/net/sf/freecol/common/model/Operand.java

401 lines
12 KiB
Java

/**
* Copyright (C) 2002-2022 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.util.Collection;
import java.util.logging.Logger;
import javax.xml.stream.XMLStreamException;
import net.sf.freecol.common.io.FreeColXMLReader;
import net.sf.freecol.common.io.FreeColXMLWriter;
import net.sf.freecol.common.option.GameOptions;
import static net.sf.freecol.common.model.Constants.*;
import static net.sf.freecol.common.util.CollectionUtils.*;
import net.sf.freecol.common.util.Utils;
/**
* The {@code Operand} 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 enum OperandType {
UNITS, BUILDINGS, SETTLEMENTS, FOUNDING_FATHERS, YEAR, OPTION, NONE
}
public enum ScopeLevel {
SETTLEMENT, PLAYER, GAME, NONE
}
/** The type of object the operand really represents. */
private OperandType operandType = OperandType.NONE;
/** How broadly to apply the operand. */
private ScopeLevel scopeLevel = ScopeLevel.NONE;
/** The operand amount. */
private Integer value = null;
/**
* Deliberately empty constructor.
*/
public Operand() {}
/**
* Creates a new {@code Operand} instance.
*
* @param value The initial operand value.
*/
public Operand(int value) {
this.value = value;
}
/**
* Creates a new {@code Operand} instance.
*
* @param operandType The {@code OperandType} to use.
* @param scopeLevel The {@code ScopeLevel} to use.
*/
public Operand(OperandType operandType, ScopeLevel scopeLevel) {
this.operandType = operandType;
this.scopeLevel = scopeLevel;
}
/**
* Create a new operand by reading a stream.
*
* @param xr The {@code FreeColXMLReader} to read.
* @exception XMLStreamException if there is a problem reading the stream.
*/
protected Operand(FreeColXMLReader xr) throws XMLStreamException {
readFromXML(xr);
}
/**
* Gets the operand type.
*
* @return The {@code OperandType}.
*/
public final OperandType getOperandType() {
return this.operandType;
}
/**
* Set the operand type.
*
* @param newOperandType The new {@code OperandType}.
*/
public final void setOperandType(final OperandType newOperandType) {
this.operandType = newOperandType;
}
/**
* Gets the scope level.
*
* @return The scope level.
*/
public final ScopeLevel getScopeLevel() {
return this.scopeLevel;
}
/**
* Sets the scope level.
*
* @param newScopeLevel The new {@code ScopeLevel}.
*/
public final void setScopeLevel(final ScopeLevel newScopeLevel) {
this.scopeLevel = newScopeLevel;
}
/**
* Gets the operand value.
*
* @return The operand value.
*/
public final Integer getValue() {
return this.value;
}
/**
* Sets the operand value.
*
* @param newValue The new value.
*/
public final void setValue(final Integer newValue) {
this.value = newValue;
}
/**
* Count the number of objects in a list that this operand is
* applicable to.
*
* @param objects The list of objects to check.
* @return The number of applicable objects.
*/
private Integer ourCount(Collection<? extends FreeColObject> objects) {
return count(objects, o -> this.appliesTo(o));
}
/**
* Gets the operand value if it is applicable to the given Game.
*
* @param game The {@code Game} to check.
* @return The operand value or null if inapplicable.
*/
public Integer getValue(Game game) {
if (this.value != null) return this.value;
if (this.scopeLevel != ScopeLevel.GAME) return null;
final String methodName = getMethodName();
int result = 0;
switch (this.operandType) {
case NONE:
result = game.invokeMethod(methodName, Integer.class, 0);
break;
case YEAR:
result = game.getTurn().getYear();
break;
case OPTION:
result = game.getSpecification().getInteger(getType());
break;
default:
for (Player player : game.getLivePlayerList()) {
switch (this.operandType) {
case UNITS:
result += ourCount(player.getUnitSet());
break;
case BUILDINGS:
result += sum(player.getColonies(),
c -> ourCount(c.getBuildings()));
break;
case SETTLEMENTS:
result += ourCount(player.getSettlementList());
break;
case FOUNDING_FATHERS:
result += ourCount(player.getFoundingFathers());
break;
default:
return null;
}
}
}
return result;
}
/**
* Gets the operand value if it is applicable to the given Player.
*
* @param player The {@code Player} to check.
* @return The operand value, or null if inapplicable.
*/
public Integer getValue(Player player) {
if (this.value != null) return this.value;
switch (this.scopeLevel) {
case GAME:
return getValue(player.getGame());
case PLAYER: // Real case, handled below
break;
default: // Inapplicable
return null;
}
final Specification spec = player.getSpecification();
final String methodName = getMethodName();
switch (this.operandType) {
case UNITS:
return ourCount(player.getUnitSet());
case BUILDINGS:
return sum(player.getColonies(), c -> ourCount(c.getBuildings()));
case SETTLEMENTS:
if (methodName == null) {
return ourCount(player.getSettlementList())
+ spec.getInteger(GameOptions.SETTLEMENT_LIMIT_MODIFIER);
}
final String methodValue = getMethodValue();
return count(player.getSettlementList(),
s -> String.valueOf(s.invokeMethod(methodName,
Boolean.class, Boolean.FALSE)).equals(methodValue));
case FOUNDING_FATHERS:
return ourCount(player.getFoundingFathers());
default:
break;
}
return player.invokeMethod(methodName, Integer.class, (Integer)null);
}
/**
* Gets the operand value if it is applicable to the given Settlement.
*
* @param settlement The {@code Settlement} to check.
* @return The operand value, or null if inapplicable.
*/
public Integer getValue(Settlement settlement) {
if (this.value != null) return this.value;
// In future, we might expand this to handle native settlements
if (this.scopeLevel != ScopeLevel.SETTLEMENT
|| !(settlement instanceof Colony)) return null;
final Colony colony = (Colony)settlement;
switch (this.operandType) {
case UNITS:
return ourCount(colony.getUnitList());
case BUILDINGS:
return ourCount(colony.getBuildings());
default:
break;
}
return colony.invokeMethod(getMethodName(), Integer.class, (Integer)null);
}
// Override FreeColObject
/**
* {@inheritDoc}
*/
@Override
public <T extends FreeColObject> boolean copyIn(T other) {
Operand o = copyInCast(other, Operand.class);
if (o == null || !super.copyIn(o)) return false;
this.operandType = o.getOperandType();
this.scopeLevel = o.getScopeLevel();
this.value = o.getValue();
return true;
}
// Serialization
private static final String OPERAND_TYPE_TAG = "operand-type";
private static final String SCOPE_LEVEL_TAG = "scope-level";
// @compat 0.11.3
private static final String OLD_OPERAND_TYPE_TAG = "operandType";
private static final String OLD_SCOPE_LEVEL_TAG = "scopeLevel";
// end @compat 0.11.3
/**
* {@inheritDoc}
*/
@Override
protected void writeAttributes(FreeColXMLWriter xw) throws XMLStreamException {
super.writeAttributes(xw);
xw.writeAttribute(OPERAND_TYPE_TAG, this.operandType);
xw.writeAttribute(SCOPE_LEVEL_TAG, this.scopeLevel);
if (this.value != null) {
xw.writeAttribute(VALUE_TAG, this.value);
}
}
/**
* {@inheritDoc}
*/
@Override
protected void readAttributes(FreeColXMLReader xr) throws XMLStreamException {
super.readAttributes(xr);
// @compat 0.11.3
if (xr.hasAttribute(OLD_OPERAND_TYPE_TAG)) {
this.operandType = xr.getAttribute(OLD_OPERAND_TYPE_TAG,
OperandType.class, OperandType.NONE);
} else
// end @compat 0.11.3
this.operandType = xr.getAttribute(OPERAND_TYPE_TAG,
OperandType.class, OperandType.NONE);
// @compat 0.11.3
if (xr.hasAttribute(OLD_SCOPE_LEVEL_TAG)) {
this.scopeLevel = xr.getAttribute(OLD_SCOPE_LEVEL_TAG,
ScopeLevel.class, ScopeLevel.NONE);
} else
// end @compat 0.11.3
this.scopeLevel = xr.getAttribute(SCOPE_LEVEL_TAG,
ScopeLevel.class, ScopeLevel.NONE);
int val = xr.getAttribute(VALUE_TAG, INFINITY);
if (val != INFINITY) this.value = val;
}
// getTagName apparently not needed, uses parents
// Override Object
/**
* {@inheritDoc}
*/
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o instanceof Operand) {
Operand other = (Operand)o;
return this.operandType == other.operandType
&& this.scopeLevel == other.scopeLevel
&& Utils.equals(this.value, other.value)
&& super.equals(other);
}
return false;
}
/**
* {@inheritDoc}
*/
@Override
public int hashCode() {
int hash = super.hashCode();
hash = 31 * hash + this.operandType.ordinal();
hash = 31 * hash + this.scopeLevel.ordinal();
hash = 31 * hash + this.value;
return hash;
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
if (this.value != null) return Integer.toString(value);
StringBuilder sb = new StringBuilder();
sb.append("[Operand type=").append(this.operandType)
.append(" scopeLevel=").append(this.scopeLevel);
return super.toString().replaceFirst("^[^ ]*", sb.toString());
}
}