/* * 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.analysis; import java.util.HashMap; import java.util.IdentityHashMap; import java.util.Iterator; import java.util.Map; import java.util.Set; import pcgen.base.formula.Formula; import pcgen.base.util.WrappedMapSet; import pcgen.cdom.base.CDOMObject; import pcgen.cdom.enumeration.CharID; import pcgen.cdom.enumeration.VariableKey; import pcgen.cdom.facet.CDOMObjectConsolidationFacet; import pcgen.cdom.facet.FormulaResolvingFacet; import pcgen.cdom.facet.base.AbstractStorageFacet; import pcgen.cdom.facet.event.DataFacetChangeEvent; import pcgen.cdom.facet.event.DataFacetChangeListener; /** * VariableFacet is a Facet that tracks the Variables that are contained in a * Player Character. * * @author Thomas Parker (thpr [at] yahoo.com) */ public class VariableFacet extends AbstractStorageFacet<CharID> implements DataFacetChangeListener<CharID, CDOMObject> { private FormulaResolvingFacet formulaResolvingFacet; private CDOMObjectConsolidationFacet consolidationFacet; /** * Adds variables and their Formulas when a variable is granted by a * CDOMObject which is added to a Player Character. * * Triggered when one of the Facets to which VariableFacet listens fires a * DataFacetChangeEvent to indicate a CDOMObject was added to a Player * Character. * * @param dfce * The DataFacetChangeEvent containing the information about the * change * * @see pcgen.cdom.facet.event.DataFacetChangeListener#dataAdded(pcgen.cdom.facet.event.DataFacetChangeEvent) */ @Override public void dataAdded(DataFacetChangeEvent<CharID, CDOMObject> dfce) { CDOMObject cdo = dfce.getCDOMObject(); Set<VariableKey> keys = cdo.getVariableKeys(); CharID id = dfce.getCharID(); for (VariableKey vk : keys) { add(id, vk, cdo.get(vk), cdo); } } /** * Removes variables and their Formulas when a variable is granted by a * CDOMObject which is removed from a Player Character. * * Triggered when one of the Facets to which VariableFacet listens fires a * DataFacetChangeEvent to indicate a CDOMObject was removed from a Player * Character. * * @param dfce * The DataFacetChangeEvent containing the information about the * change * * @see pcgen.cdom.facet.event.DataFacetChangeListener#dataRemoved(pcgen.cdom.facet.event.DataFacetChangeEvent) */ @Override public void dataRemoved(DataFacetChangeEvent<CharID, CDOMObject> dfce) { removeAll(dfce.getCharID(), dfce.getCDOMObject()); } private void add(CharID id, VariableKey vk, Formula formula, CDOMObject cdo) { Map<VariableKey, Map<Formula, Set<CDOMObject>>> map = getConstructingCachedMap(id); Map<Formula, Set<CDOMObject>> subMap = map.get(vk); if (subMap == null) { subMap = new HashMap<>(); map.put(vk, subMap); } Set<CDOMObject> sources = subMap.get(formula); if (sources == null) { sources = new WrappedMapSet<>(IdentityHashMap.class); subMap.put(formula, sources); } sources.add(cdo); } /** * Returns the type-safe Map for this VariableFacet and the given CharID. * May return null if no information has been set in this VariableFacet for * the given CharID. * * Note that this method SHOULD NOT be public. The Map is owned by * VariableFacet, and since it can be modified, a reference to that object * should not be exposed to any object other than VariableFacet. * * @param id * The CharID for which the Map should be returned * @return The Set for the Player Character represented by the given CharID; * null if no information has been set in this VariableFacet for the * Player Character */ private Map<VariableKey, Map<Formula, Set<CDOMObject>>> getCachedMap( CharID id) { return (Map<VariableKey, Map<Formula, Set<CDOMObject>>>) getCache(id); } /** * Returns a type-safe Map for this VariableFacet and the given CharID. Will * return a new, empty Map if no information has been set in this * VariableFacet for the given CharID. Will not return null. * * Note that this method SHOULD NOT be public. The Map object is owned by * VariableFacet, and since it can be modified, a reference to that object * should not be exposed to any object other than VariableFacet. * * @param id * The CharID for which the Map should be returned * @return The Map for the Player Character represented by the given CharID */ private Map<VariableKey, Map<Formula, Set<CDOMObject>>> getConstructingCachedMap( CharID id) { Map<VariableKey, Map<Formula, Set<CDOMObject>>> componentMap = getCachedMap(id); if (componentMap == null) { componentMap = new HashMap<>(); setCache(id, componentMap); } return componentMap; } /** * Removes all information for the given source from this VariableFacet for * the PlayerCharacter represented by the given CharID. * * @param id * The CharID representing the Player Character for which items * from the given source will be removed * @param source * The source for the variables to be removed from the list of * variables stored for the Player Character identified by the * given CharID */ public void removeAll(CharID id, Object source) { Map<VariableKey, Map<Formula, Set<CDOMObject>>> vkMap = getCachedMap(id); if (vkMap != null) { for (Iterator<Map<Formula, Set<CDOMObject>>> mit = vkMap.values() .iterator(); mit.hasNext();) { Map<Formula, Set<CDOMObject>> fMap = mit.next(); for (Iterator<Set<CDOMObject>> sit = fMap.values().iterator(); sit .hasNext();) { Set<CDOMObject> set = sit.next(); if (set.remove(source) && set.isEmpty()) { sit.remove(); } } if (fMap.isEmpty()) { mit.remove(); } } } } /** * Returns the numeric variable value for the given VariableKey on the * Player Character identified by the given CharID. If a variable has more * than one value, the given isMax argument is used to determine if this * method returns the maximum (true) or minimum (false) of the calculated * values. * * @param id * The CharID identifying the Player Character for which the * numeric variable value is to be returned * @param key * The VariableKey identifying the variable which which the value * is to be returned * @param isMax * Used to determine if this method returns the maximum (true) or * minimum (false) of the calculated values when the Player * Character contains more than one value for the given * VariableKey * @return The numeric variable value for the given VariableKey on the * Player Character identified by the given CharID */ public Double getVariableValue(CharID id, VariableKey key, boolean isMax) { Map<VariableKey, Map<Formula, Set<CDOMObject>>> vkMap = getCachedMap(id); if (vkMap == null) { return null; } Map<Formula, Set<CDOMObject>> fMap = vkMap.get(key); if (fMap == null) { return null; } Double returnValue = null; for (Map.Entry<Formula, Set<CDOMObject>> me : fMap.entrySet()) { Formula f = me.getKey(); Set<CDOMObject> sources = me.getValue(); for (CDOMObject source : sources) { double newVal = formulaResolvingFacet.resolve(id, f, source.getQualifiedKey()).doubleValue(); if (returnValue == null) { returnValue = newVal; } else if ((returnValue > newVal) ^ isMax) { returnValue = newVal; } } } return returnValue; } /** * Returns true if this VariableFacet contains the given VariableKey in the * list of variables for the Player Character represented by the given * CharID. * * @param id * The CharID representing the Player Character used for testing * @param vk * The VariableKey to test if this VariableFacet contains that * VariableKey for the Player Character represented by the given * CharID * @return true if this VariableFacet contains the given VariableKey for the * Player Character represented by the given CharID; false otherwise */ public boolean contains(CharID id, VariableKey vk) { Map<VariableKey, Map<Formula, Set<CDOMObject>>> vkMap = getCachedMap(id); return (vkMap != null) && vkMap.containsKey(vk); } public int getVariableCount(CharID id) { Map<VariableKey, Map<Formula, Set<CDOMObject>>> vkMap = getCachedMap(id); return (vkMap == null) ? 0 : vkMap.size(); } public void setFormulaResolvingFacet(FormulaResolvingFacet formulaResolvingFacet) { this.formulaResolvingFacet = formulaResolvingFacet; } public void setConsolidationFacet(CDOMObjectConsolidationFacet consolidationFacet) { this.consolidationFacet = consolidationFacet; } /** * Initializes the connections for VariableFacet to other facets. * * This method is automatically called by the Spring framework during * initialization of the VariableFacet. */ public void init() { consolidationFacet.addDataFacetChangeListener(this); } /** * Copies the contents of the VariableFacet from one Player Character to * another Player Character, based on the given CharIDs representing those * Player Characters. * * This is a method in VariableFacet in order to avoid exposing the mutable * Map object to other classes. This should not be inlined, as the Map is * internal information to VariableFacet 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 VariableFacet of one Player * Character will only impact the Player Character where the VariableFacet * 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) { Map<VariableKey, Map<Formula, Set<CDOMObject>>> cm = getCachedMap(source); if (cm != null) { for (Map.Entry<VariableKey, Map<Formula, Set<CDOMObject>>> me : cm .entrySet()) { VariableKey vk = me.getKey(); for (Map.Entry<Formula, Set<CDOMObject>> fme : me.getValue() .entrySet()) { Formula f = fme.getKey(); for (CDOMObject cdo : fme.getValue()) { add(copy, vk, f, cdo); } } } } } }