mirror of https://github.com/FreeCol/freecol.git
401 lines
12 KiB
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());
|
|
}
|
|
}
|