/* * Copyright (c) Thomas Parker, 2009. * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free * Software Foundation; either version 2.1 of the License, or (at your option) * any later version. * * This program 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 Lesser General Public License for more * details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ package pcgen.cdom.facet; import java.util.Collection; import java.util.List; import pcgen.base.util.DoubleKeyMap; import pcgen.base.util.DoubleKeyMapToList; import pcgen.cdom.enumeration.CharID; import pcgen.cdom.facet.base.AbstractStorageFacet; /** * BonusChangeFacet tracks changes to Bonus values on a PlayerCharacter and * allows other classes to listen to changes in Bonuses on a Player Character. * * @author Thomas Parker (thpr [at] yahoo.com) */ public class BonusChangeFacet extends AbstractStorageFacet<CharID> { /** * The BonusChangeSupport object that manages the listeners that receive * BonusChangeEvents from this BonusChangeFacet. */ private final BonusChangeSupport support = new BonusChangeSupport(); private BonusCheckingFacet bonusCheckingFacet; /** * Performs a check against the previously known values of the bonuses for * the Player Character identified by the given CharID. If any Bonus values * have changed, then this will throw a BonusChangeEvent to any * BonusChangeListener objects which have subscribed to receive updates for * the Bonus name and Bonus type where the value changed. * * @param id * The CharID identifying the Player Character for which the * check for changes in bonus values should be performed */ public void reset(CharID id) { DoubleKeyMap<String, String, Double> map = getConstructingInfo(id); for (String type : support.getBonusTypes()) { for (String name : support.getBonusNames(type)) { Double newValue = bonusCheckingFacet.getBonus(id, type, name); Double oldValue = map.get(type, name); if (!newValue.equals(oldValue)) { map.put(type, name, newValue); support.fireBonusChange(id, type, name, oldValue, newValue); } } } } /** * Returns a DoubleKeyMap of Bonus values for this BonusChangeFacet and the * PlayerCharacter represented by the given CharID. Will create a new empty * DoubleKeyMap for the PlayerCharacter represented by the given CharID if * no information has been set in this BonusChangeFacet for the given * CharID. Will not return null. * * Note that this method SHOULD NOT be public. The DoubleKeyMap object is * owned by BonusChangeFacet, and since it can be modified, a reference to * that DoubleKeyMap should not be exposed to any object other than * BonusChangeFacet. * * @param id * The CharID for which the DoubleKeyMap of bonus values should * be returned * @return The DoubleKeyMap of Bonus values for the Player Character * represented by the given CharID */ private DoubleKeyMap<String, String, Double> getConstructingInfo(CharID id) { DoubleKeyMap<String, String, Double> map = getInfo(id); if (map == null) { map = new DoubleKeyMap<>(); setCache(id, map); } return map; } /** * Returns a DoubleKeyMap of Bonus values for this BonusChangeFacet and the * PlayerCharacter represented by the given CharID. May return null if no * information has been set in this BonusChangeFacet for the given CharID. * * Note that this method SHOULD NOT be public. The DoubleKeyMap object is * owned by BonusChangeFacet, and since it can be modified, a reference to * that DoubleKeyMap should not be exposed to any object other than * BonusChangeFacet. * * @param id * The CharID for which the DoubleKeyMap of bonus values should * be returned * @return The DoubleKeyMap of Bonus values for the Player Character * represented by the given CharID */ private DoubleKeyMap<String, String, Double> getInfo(CharID id) { return (DoubleKeyMap<String, String, Double>) getCache(id); } /** * BonusChangeListener is the interface that must be implemented by a class * for it to receive BonusChangeEvents from the BonusChangeFacet when a * Bonus value has changed for a Player Character. * * @author Thomas Parker (thpr [at] yahoo.com) */ @FunctionalInterface public interface BonusChangeListener { /** * Method called when a Bonus value has changed on a Player Character. * The BonusChangeEvent contains the relevant details of the Bonus value * change. * * @param bce * The BonusChangeEvent containing the details of the Bonus * value change for a Player Character */ void bonusChange(BonusChangeEvent bce); } /** * BonusChangeEvent is an event sent to a BonusChangeListener when a Bonus * value changes on a Player Character. * * @author Thomas Parker (thpr [at] yahoo.com) */ public static class BonusChangeEvent { /** * The CharID identifying the Player Character on which the Bonus value * change took place. */ private final CharID charID; /** * The Bonus type indicating which Bonus value changed on the Player * Character. */ private final String bonusType; /** * The Bonus name indicating which Bonus value changed on the Player * Character. */ private final String bonusName; /** * The previous value of the Bonus value */ private final Number oldVal; /** * The new value of the Bonus value */ private final Number newVal; /** * Constructs a new BonusChangeEvent indicating a Bonus value change * took place on the Player Character identified by the given CharId. * The Bonus name, type, old value, and new value are provided. * * @param id * The CharID indicating the Player Character on which the * Bonus value change took place * @param type * The Bonus type for the Bonus value that changed * @param name * The Bonus name for the Bonus value that changed * @param oldValue * The previous value of the Bonus value * @param newValue * The new value of the Bonus value */ public BonusChangeEvent(CharID id, String type, String name, Number oldValue, Number newValue) { charID = id; bonusType = type; bonusName = name; oldVal = oldValue; newVal = newValue; } public CharID getCharID() { return charID; } public String getBonusType() { return bonusType; } public String getBonusName() { return bonusName; } public Number getOldVal() { return oldVal; } public Number getNewVal() { return newVal; } } /** * BonusChangeSupport is a support class that provides the actual structure * for adding and removing listeners to a class that can provide updates for * changes to Bonus values on a Player Character. * * @author Thomas Parker (thpr [at] yahoo.com) */ public static class BonusChangeSupport { private DoubleKeyMapToList<String, String, BonusChangeListener> listeners = new DoubleKeyMapToList<>(); /** * Adds a new BonusChangeListener to receive BonusChangeEventas from the * change source. The given BonusChangeListener is subscribed to Bonus * value changes for the given Bonus type and Bonus name. * * Note that the BonusChangeListeners are a list, meaning a given * BonusChangeListener can be added more than once at a given priority, * and if that occurs, it must be removed an equivalent number of times * in order to no longer receive events from this BonusChangeSupport. * * @param listener * The BonusChangeListener to receive BonusChangeEvents from * this BonusChangeSupport * @param type * The Bonus type for the Bonus value changes for which the * given listener will be added to the list of listeners * @param name * The Bonus name for the Bonus value changes for which the * given listener will be added to the list of listeners */ public synchronized void addBonusChangeListener( BonusChangeListener listener, String type, String name) { listeners.addToListFor(type, name, listener); } public Collection<String> getBonusTypes() { return listeners.getKeySet(); } public Collection<String> getBonusNames(String type) { return listeners.getSecondaryKeySet(type); } /** * Removes a BonusChangeListener so that it will no longer receive * BonusChangeEvents from the source DataFacet. This will remove the * data facet change listener from receiving events for the given Bonus * type and Bonus name. * * Note that if the given BonusChangeListener has been registered under * a different Bonus type and Bonus name, it will continue to receive * events for those Bonus value changes. * * @param listener * The BonusChangeListener to be removed * @param type * The Bonus type for the Bonus value changes for which the * given listener will be removed from the list of listeners * @param name * The Bonus name for the Bonus value changes for which the * given listener will be removed from the list of listeners */ public synchronized void removeBonusChangeListener( BonusChangeListener listener, String type, String name) { listeners.removeFromListFor(type, name, listener); } public synchronized BonusChangeListener[] getBonusChangeListeners( String type, String name) { List<BonusChangeListener> listFor = listeners.getListFor(type, name); return (listFor.toArray(new BonusChangeListener[listFor.size()])); } /** * Sends a BonusChangeEvent to the BonusChangeListeners that are * receiving BonusChangeEvents from the change source. * * @param id * The CharID identifying the Player Character to which the * BonusChangeEvent relates. * @param type * The Bonus type for the Bonus value that changed * @param name * The Bonus name for the Bonus value that changed * @param oldValue * The previous value of the Bonus value * @param newValue * The new value of the Bonus value */ public void fireBonusChange(CharID id, String type, String name, Number oldValue, Number newValue) { BonusChangeEvent bce = new BonusChangeEvent(id, type, name, oldValue, newValue); List<BonusChangeListener> localListeners = listeners.getListFor( type, name); if (localListeners != null) { for (BonusChangeListener target : localListeners) { target.bonusChange(bce); } } } } /** * Adds a new BonusChangeListener to receive BonusChangeEvents from * BonusChangeFacet. The given BonusChangeListener subscribed to changes for * the given Bonus type and Bonus name. * * Note that the BonusChangeListeners are a list, meaning a given * BonusChangeListener can be added more than once for a given Bonus type * and Bonus name, and if that occurs, it must be removed an equivalent * number of times in order to no longer receive events from this * BonusChangeFacet. * * @param listener * The BonusChangeListener to receive BonusChangeEvents from this * BonusChangeFacet * @param type * The Bonus type for the Bonus value changes for which the given * listener will be added to the list of listeners * @param name * The Bonus name for the Bonus value changes for which the given * listener will be added to the list of listeners */ public void addBonusChangeListener(BonusChangeListener listener, String type, String name) { support.addBonusChangeListener(listener, type, name); } /** * Removes a BonusChangeListener so that it will no longer receive * BonusChangeEvents from BonusChangeFacet. This will remove the data facet * change listener from the list of listeners only for the given Bonus type * and Bonus name. * * Note that if the given BonusChangeListener has been registered under a * different Bonus type and Bonus name, it will continue to receive events * for those Bonus value changes. * * @param listener * The BonusChangeListener to be removed * @param type * The Bonus type for the Bonus value changes for which the given * listener will be removed from the list of listeners * @param name * The Bonus name for the Bonus value changes for which the given * listener will be removed from the list of listeners */ public void removeBonusChangeListener(BonusChangeListener listener, String type, String name) { support.removeBonusChangeListener(listener, type, name); } public void setBonusCheckingFacet(BonusCheckingFacet bonusCheckingFacet) { this.bonusCheckingFacet = bonusCheckingFacet; } /** * Copies the contents of the BonusChangeFacet from one Player Character to * another Player Character, based on the given CharIDs representing those * Player Characters. * * This is a method in BonusChangeFacet in order to avoid exposing the * mutable DoubleKeyMap object to other classes. This should not be inlined, * as the DoubleKeyMap is internal information to AbstractListFacet and * should not be exposed to other classes. * * Note also the copy is a one-time event and no references are maintained * between the Player Characters represented by the given CharIDs (meaning * once this copy takes place, any change to the BonusChangeFacet of one * Player Character will only impact the Player Character where the * BonusChangeFacet was changed). * * @param source * The CharID representing the Player Character from which the * information should be copied * @param copy * The CharID representing the Player Character to which the * information should be copied */ @Override public void copyContents(CharID source, CharID copy) { DoubleKeyMap<String, String, Double> map = getInfo(source); if (map != null) { getConstructingInfo(copy).putAll(map); } } }