/**
* 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.common.model;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.logging.Logger;
/**
* A container to hold abilities and modifiers for some FreeColObject-subclass.
* FreeColGameObjectType, Europe, Player, Settlement are current implementors.
* Building delegates some functionality to its type.
* Unit fakes it by constructing one on the fly.
* FreeColObject itself implements a null version.
*/
public class FeatureContainer {
private static final Logger logger = Logger.getLogger(FeatureContainer.class.getName());
/** The abilities in the container. */
private final Map<String, Set<Ability>> abilities
= new HashMap<String, Set<Ability>>();
/** The modifiers in the container. */
private final Map<String, Set<Modifier>> modifiers
= new HashMap<String, Set<Modifier>>();
/**
* Is an ability present in this container?
* All applicable abilities must be true, and there must be at least one.
*
* @param id The id of the ability to test.
* @param fcgot An optional <code>FreeColGameObjectType</code> the
* ability applies to.
* @param turn An optional applicable <code>Turn</code>.
* @return True if the ability is `satisfied'.
*/
public static boolean hasAbility(FeatureContainer fc, String id,
FreeColGameObjectType fcgot, Turn turn) {
if (fc == null) return false;
Set<Ability> abilitySet = fc.abilities.get(id);
if (abilitySet == null) return false;
boolean ret = false;
for (Ability ability : abilitySet) {
if (ability.appliesTo(fcgot, turn)) {
if (!ability.getValue()) return false;
ret = true;
}
}
return ret;
}
/**
* Is the given set of abilities non-empty and does not contain
* any false-valued members.
*
* @return True if the ability set is `satisfied'.
*/
public static boolean hasAbility(Set<Ability> abilitySet) {
if (abilitySet.isEmpty()) return false;
for (Ability ability : abilitySet) {
if (!ability.getValue()) return false;
}
return true;
}
/**
* Checks if this container contains a given ability key.
*
* @param fc The <code>FeatureContainer</code> to get abilities from.
* @param key The key to check.
* @return True if the key is present.
*/
public static boolean containsAbilityKey(FeatureContainer fc, String key) {
return (fc == null) ? false : fc.abilities.containsKey(key);
}
/**
* Gets a copy of the abilities of a container.
*
* @param fc The <code>FeatureContainer</code> to get abilities from.
* @return A set of abilities.
*/
public static Set<Ability> getAbilities(FeatureContainer fc) {
Set<Ability> result = new HashSet<Ability>();
if (fc != null) {
for (Set<Ability> abilitySet : fc.abilities.values()) {
result.addAll(abilitySet);
}
}
return result;
}
/**
* Gets the set of abilities with the given Id from a container.
*
* @param fc The <code>FeatureContainer</code> to query.
* @param id The id of the ability to test.
* @param fcgot An optional <code>FreeColGameObjectType</code> the
* ability applies to.
* @param turn An optional applicable <code>Turn</code>.
* @return A set of abilities.
*/
public static Set<Ability> getAbilitySet(FeatureContainer fc, String id,
FreeColGameObjectType fcgot,
Turn turn) {
Set<Ability> result = new HashSet<Ability>();
if (fc != null) {
Set<Ability> abilitySet = fc.abilities.get(id);
if (abilitySet != null) {
for (Ability ability : abilitySet) {
if (ability.appliesTo(fcgot, turn)) result.add(ability);
}
}
}
return result;
}
/**
* Add the given ability to a container.
*
* @param fc The <code>FeatureContainer</code> to add to.
* @param ability An <code>Ability</code> to add.
* @return True if the Ability was added.
*/
public static boolean addAbility(FeatureContainer fc, Ability ability) {
if (fc == null || ability == null) return false;
Set<Ability> abilitySet = fc.abilities.get(ability.getId());
if (abilitySet == null) {
abilitySet = new HashSet<Ability>();
fc.abilities.put(ability.getId(), abilitySet);
}
return abilitySet.add(ability);
}
/**
* Remove the given ability from a container.
*
* @param fc The <code>FeatureContainer</code> to remove from.
* @param ability An <code>Ability</code> to remove.
* @return The ability removed.
*/
public static Ability removeAbility(FeatureContainer fc, Ability ability) {
if (fc == null || ability == null) return null;
Set<Ability> abilitySet = fc.abilities.get(ability.getId());
return (abilitySet == null || !abilitySet.remove(ability)) ? null
: ability;
}
/**
* Remove all abilities with a given Id.
*
* @param fc The <code>FeatureContainer</code> to remove abilities from.
* @param id The id of the abilities to remove.
*/
public static void removeAbilities(FeatureContainer fc, String id) {
if (fc != null) fc.abilities.remove(id);
}
/**
* Checks if this container contains a given modifier key.
*
* @param fc The <code>FeatureContainer</code> to get abilities from.
* @param key The key to check.
* @return True if the key is present.
*/
public static boolean containsModifierKey(FeatureContainer fc, String key) {
return (fc == null) ? false : fc.modifiers.containsKey(key);
}
/**
* Gets a copy of the modifiers in a container.
*
* @param fc The <code>FeatureContainer</code> to get modifiers from.
* @return A set of modifiers.
*/
public static Set<Modifier> getModifiers(FeatureContainer fc) {
Set<Modifier> result = new HashSet<Modifier>();
if (fc != null) {
for (Set<Modifier> modifierSet : fc.modifiers.values()) {
result.addAll(modifierSet);
}
}
return result;
}
/**
* Gets the set of modifiers with the given Id from a container.
*
* @param fc The <code>FeatureContainer</code> to get modifiers from.
* @param id The id of the modifier to test.
* @param fcgot An optional <code>FreeColGameObjectType</code> the
* modifier applies to.
* @param turn An optional applicable <code>Turn</code>.
* @return A set of modifiers.
*/
public static Set<Modifier> getModifierSet(FeatureContainer fc, String id,
FreeColGameObjectType fcgot,
Turn turn) {
return (fc == null) ? new HashSet<Modifier>()
: fc.getModifierSet(id, fcgot, turn);
}
/**
* Gets the set of modifiers with the given Id from this container.
*
* @param id The id of the modifier to test.
* @param fcgot An optional <code>FreeColGameObjectType</code> the
* modifier applies to.
* @param turn An optional applicable <code>Turn</code>.
* @return A set of modifiers.
*/
public Set<Modifier> getModifierSet(String id,
FreeColGameObjectType fcgot,
Turn turn) {
Set<Modifier> result = new HashSet<Modifier>();
Set<Modifier> modifierSet = modifiers.get(id);
if (modifierSet != null) {
if (fcgot == null) {
result.addAll(modifierSet);
} else {
for (Modifier modifier : modifierSet) {
if (modifier.appliesTo(fcgot, turn)) result.add(modifier);
}
}
}
return result;
}
/**
* Applies the modifiers with the given Id to the given number.
*
* @param fc The <code>FeatureContainer</code> to query.
* @param number The number to modify.
* @param id The id of the modifiers to apply.
* @param fcgot An optional <code>FreeColGameObjectType</code> the
* modifier applies to.
* @param turn An optional applicable <code>Turn</code>.
* @return The modified number.
*/
public static float applyModifier(FeatureContainer fc,
float number, String id,
FreeColGameObjectType fcgot, Turn turn) {
return (fc == null) ? number
: fc.applyModifier(number, id, fcgot, turn);
}
/**
* Applies the modifiers with the given Id to the given number.
*
* @param number The number to modify.
* @param id The id of the modifiers to apply.
* @param fcgot An optional <code>FreeColGameObjectType</code> the
* modifier applies to.
* @param turn An optional applicable <code>Turn</code>.
* @return The modified number.
*/
public float applyModifier(float number, String id,
FreeColGameObjectType fcgot, Turn turn) {
return applyModifierSet(number, turn,
getModifierSet(id, fcgot, turn));
}
/**
* Applies a list of modifiers to the given float value.
*
* This routine is required for Col1-compatibility throughout the
* production code, almost all other code should/does call
* applyModifierSet(). The algorithms are quite similar, but different,
* which is unsatisfactory, but appears to be necessary.
*
* @param number The number to modify.
* @param turn An optional applicable <code>Turn</code>.
* @param modifiers The <code>Modifier</code>s to apply.
* @return The modified number.
*/
public static float applyModifiers(float number, Turn turn,
List<Modifier> modifiers) {
if (modifiers == null || modifiers.isEmpty()) return number;
Collections.sort(modifiers);
float result = number;
for (Modifier modifier : modifiers) {
float value = modifier.getValue(turn);
if (value == Modifier.UNKNOWN) {
return value;
} else {
result = modifier.apply(result, value);
}
}
return result;
}
/**
* Applies a set of modifiers to the given float value.
*
* Use this generally. Only use applyModifiers in the production code.
*
* @param number The number to modify.
* @param turn An optional applicable <code>Turn</code>.
* @param modifiers The <code>Modifier</code>s to apply.
* @return The modified number.
*/
public static float applyModifierSet(float number, Turn turn,
Set<Modifier> modifiers) {
float additive = 0, percentage = 0, multiplicative = 1;
for (Modifier modifier : modifiers) {
float value = modifier.getValue(turn);
if (value == Modifier.UNKNOWN) {
return Modifier.UNKNOWN;
}
switch (modifier.getType()) {
case ADDITIVE:
additive += value;
break;
case MULTIPLICATIVE:
multiplicative *= value;
break;
case PERCENTAGE:
percentage += value;
// If we want cumulative percentage modifiers:
// percentage += (percentage * value) / 100 + value;
break;
}
}
float result = number;
result += additive;
result *= multiplicative;
result += (result * percentage) / 100;
return result;
}
/**
* Adds a modifier to a container.
*
* @param fc The <code>FeatureContainer</code> to add to.
* @param modifier The <code>Modifier</code> to add.
* @return True if the modifier was added.
*/
public static boolean addModifier(FeatureContainer fc,
Modifier modifier) {
if (fc == null || modifier == null) return false;
Set<Modifier> modifierSet = fc.modifiers.get(modifier.getId());
if (modifierSet == null) {
modifierSet = new HashSet<Modifier>();
fc.modifiers.put(modifier.getId(), modifierSet);
}
return modifierSet.add(modifier);
}
/**
* Removes a modifier from a container.
*
* @param fc The <code>FeatureContainer</code> to remove from.
* @param modifier The <code>Modifier</code> to remove.
* @return The modifier removed.
*/
public static Modifier removeModifier(FeatureContainer fc,
Modifier modifier) {
if (fc == null || modifier == null) return null;
Set<Modifier> modifierSet = fc.modifiers.get(modifier.getId());
return (modifierSet == null || !modifierSet.remove(modifier)) ? null
: modifier;
}
/**
* Removes all modifiers with a given Id.
*
* @param fc The <code>FeatureContainer</code> to remove from.
* @param id The Id of the modifiers to remove.
*/
public static void removeModifiers(FeatureContainer fc, String id) {
if (fc != null) fc.modifiers.remove(id);
}
/**
* Adds all the features in an object to this object.
*
* @param fc The <code>FreeColObject</code> to add features from.
* @param fco The <code>FreeColObject</code> to add features from.
*/
public static void addFeatures(FeatureContainer fc, FreeColObject fco) {
FeatureContainer c = fco.getFeatureContainer();
if (fc != null && c != null) {
for (Entry<String, Set<Ability>> entry : c.abilities.entrySet()) {
Set<Ability> abilitySet = fc.abilities.get(entry.getKey());
if (abilitySet == null) {
fc.abilities.put(entry.getKey(),
new HashSet<Ability>(entry.getValue()));
} else {
abilitySet.addAll(entry.getValue());
}
}
for (Entry<String, Set<Modifier>> entry : c.modifiers.entrySet()) {
Set<Modifier> modifierSet = fc.modifiers.get(entry.getKey());
if (modifierSet == null) {
fc.modifiers.put(entry.getKey(),
new HashSet<Modifier>(entry.getValue()));
} else {
modifierSet.addAll(entry.getValue());
}
}
}
}
/**
* Removes all the features in an object from a container.
*
* @param fc The <code>FeatureContainer</code> to remove features from.
* @param fco The <code>FreeColObject</code> to find features to remove
* in.
*/
public static void removeFeatures(FeatureContainer fc, FreeColObject fco) {
FeatureContainer c = fco.getFeatureContainer();
if (fc != null && c != null) {
for (Entry<String, Set<Ability>> entry : c.abilities.entrySet()) {
Set<Ability> abilitySet = fc.abilities.get(entry.getKey());
if (abilitySet != null) {
abilitySet.removeAll(entry.getValue());
}
}
for (Entry<String, Set<Modifier>> entry : c.modifiers.entrySet()) {
Set<Modifier> modifierSet = fc.modifiers.get(entry.getKey());
if (modifierSet != null) {
modifierSet.removeAll(entry.getValue());
}
}
}
}
/**
* Replaces the source field. This is necessary because objects
* may inherit Features from other abstract objects.
*
* @param oldSource The old source <code>FreeColGameObjectType</code>.
* @param newSource The new source <code>FreeColGameObjectType</code>.
*/
public void replaceSource(FreeColGameObjectType oldSource,
FreeColGameObjectType newSource) {
for (Ability ability : getAbilities(this)) {
if (oldSource == null || ability.getSource() == oldSource) {
removeAbility(this, ability);
Ability newAbility = new Ability(ability);
newAbility.setSource(newSource);
addAbility(this, newAbility);
}
}
for (Modifier modifier : getModifiers(this)) {
if (oldSource == null || modifier.getSource() == oldSource) {
removeModifier(this, modifier);
Modifier newModifier = new Modifier(modifier);
newModifier.setSource(newSource);
addModifier(this, newModifier);
}
}
}
/**
* Debug helper.
*/
public String toString() {
StringBuilder result = new StringBuilder();
result.append("[FeatureContainer [abilities");
for (Ability ability : getAbilities(this)) {
result.append(" " + ability.toString());
}
result.append("][modifiers");
for (Modifier modifier : getModifiers(this)) {
result.append(" " + modifier.toString());
}
result.append("]]");
return result.toString();
}
}