/*
* 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.Collections;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import pcgen.base.util.WrappedMapSet;
import pcgen.cdom.base.Category;
import pcgen.cdom.enumeration.CharID;
import pcgen.cdom.enumeration.Nature;
import pcgen.cdom.facet.base.AbstractDataFacet;
import pcgen.cdom.facet.event.DataFacetChangeEvent;
import pcgen.core.Ability;
/**
* A CategorizedAbilityFacet is a DataFacet that contains information about
* Ability objects that are contained in a PlayerCharacter
*
* @author Thomas Parker (thpr [at] yahoo.com)
*/
public class CategorizedAbilityFacet extends AbstractDataFacet<CharID, Ability>
{
/**
* Add the given Ability to the list of Abilities defined by the given
* Category and Nature, which is stored in this CategorizedAbilityFacet for
* the Player Character represented by the given CharID
*
* @param id
* The CharID representing the Player Character for which the
* given Ability should be added
* @param cat
* The Ability Category identifying the list to which the given
* Ability should be added
* @param nat
* The Ability Nature identifying the list to which the given
* Ability should be added
* @param obj
* The Ability to be added to the list of Abilities defined by
* the given Category and Nature, which is stored in this
* CategorizedAbilityFacet for the Player Character represented
* by the given CharID
*/
public void add(CharID id, Category<Ability> cat, Nature nat, Ability obj)
{
boolean isNew = ensureCachedSet(id, cat, nat);
if (getCachedSet(id, cat, nat).add(obj) || isNew)
{
fireDataFacetChangeEvent(id, obj, DataFacetChangeEvent.DATA_ADDED);
}
}
/**
* Adds all of the Abilities in the given Collection to the list of
* Abilities defined by the given Category and Nature, which is stored in
* this CategorizedAbilityFacet for the Player Character represented by the
* given CharID
*
* @param id
* The CharID representing the Player Character for which the
* given Abilities should be added
* @param cat
* The Ability Category identifying the list to which the given
* Abilities should be added
* @param nature
* The Ability Nature identifying the list to which the given
* Abilities should be added
* @param abilities
* The Collection of Abilities to be added to the list of
* Abilities defined by the given Category and Nature, which is
* stored in this CategorizedAbilityFacet for the Player
* Character represented by the given CharID
* @throws NullPointerException
* if the given Collection is null
*/
public void addAll(CharID id, Category<Ability> cat, Nature nature,
Collection<Ability> abilities)
{
for (Ability a : abilities)
{
add(id, cat, nature, a);
}
}
/**
* Removes the given Ability from the list of Abilities defined by the given
* Category and Nature, which is stored in this CategorizedAbilityFacet for
* the Player Character represented by the given CharID
*
* @param id
* The CharID representing the Player Character from which the
* given Ability should be removed
* @param cat
* The Ability Category identifying the list from which the given
* Ability should be removed
* @param nat
* The Ability Nature identifying the list from which the given
* Ability should be removed
* @param obj
* The Ability to be removed from the list of Abilities defined
* by the given Category and Nature, which is stored in this
* CategorizedAbilityFacet for the Player Character represented
* by the given CharID
*/
public void remove(CharID id, Category<Ability> cat, Nature nat, Ability obj)
{
Set<Ability> cached = getCachedSet(id, cat, nat);
if (cached != null && cached.remove(obj))
{
fireDataFacetChangeEvent(id, obj, DataFacetChangeEvent.DATA_REMOVED);
}
}
/**
* Returns a non-null copy of the Set of objects in thisSet of Abilities in
* this CategorizedAbilityFacet for the Player Character represented by the
* given CharID. This method returns an empty set if no objects are in this
* CategorizedAbilityFacet for the Player Character identified by the given
* CharID.
*
* This method is value-semantic in that ownership of the returned Set is
* transferred to the class calling this method. Modification of the
* returned Set will not modify this CategorizedAbilityFacet and
* modification of this CategorizedAbilityFacet will not modify the returned
* Set. Modifications to the returned Set will also not modify any future or
* previous objects returned by this (or other) methods on
* CategorizedAbilityFacet. If you wish to modify the information stored in
* this CategorizedAbilityFacet, you must use the add*() and remove*()
* methods of CategorizedAbilityFacet.
*
* @param id
* The CharID representing the Player Character for which the
* items in this CategorizedAbilityFacet should be returned.
* @param cat
* The Ability Category identifying the list of Abilities to be
* returned
* @param nat
* The Ability Nature identifying the list of Abilities to be
* returned
* @return A non-null Set of Abilities in this CategorizedAbilityFacet for
* the Player Character represented by the given CharID
*/
public Set<Ability> get(CharID id, Category<Ability> cat, Nature nat)
{
Set<Ability> set = getCachedSet(id, cat, nat);
if (set == null)
{
return Collections.emptySet();
}
return Collections.unmodifiableSet(set);
}
/**
* Returns true if this CategorizedAbilityFacet contains the given Ability
* in the list of items for the Player Character represented by the given
* CharID.
*
* @param id
* The CharID representing the Player Character used for testing
* @param cat
* The Ability Category identifying the list of Abilities to be
* tested to see if it contains the given Ability
* @param nat
* The Ability Nature identifying the list of Abilities to be
* tested to see if it contains the given Ability
* @param a
* The Ability to test if this CategorizedAbilityFacet contains
* that item for the Player Character represented by the given
* CharID
* @return true if this CategorizedAbilityFacet contains the given Ability
* for the Player Character represented by the given CharID; false
* otherwise
*/
public boolean contains(CharID id, Category<Ability> cat, Nature nat,
Ability a)
{
Set<Ability> set = getCachedSet(id, cat, nat);
if (set == null)
{
return false;
}
if (set.contains(a))
{
return true;
}
/*
* TODO Have to support slow method due to cloning issues :(
*/
for (Ability ab : set)
{
if (ab.equals(a))
{
return true;
}
}
return false;
}
private boolean ensureCachedSet(CharID id, Category<Ability> cat,
Nature nat)
{
boolean isNew = false;
Map<Category<Ability>, Map<Nature, Set<Ability>>> catMap = getCachedMap(id);
if (catMap == null)
{
isNew = true;
catMap = new HashMap<>();
setCache(id, catMap);
}
Map<Nature, Set<Ability>> natureMap = catMap.get(cat);
if (natureMap == null)
{
isNew = true;
natureMap = new HashMap<>();
catMap.put(cat, natureMap);
}
Set<Ability> abilitySet = natureMap.get(nat);
if (abilitySet == null)
{
isNew = true;
// abilitySet = new HashSet<Ability>();
abilitySet = new WrappedMapSet<>(IdentityHashMap.class);
natureMap.put(nat, abilitySet);
}
return isNew;
}
/**
* Returns the type-safe Set for this CategorizedAbilityFacet and the given
* CharID. May return null if no information has been set in this
* CategorizedAbilityFacet for the given CharID.
*
* Note that this method SHOULD NOT be public. The Set is owned by
* CategorizedAbilityFacet, and since it can be modified, a reference to
* that Set should not be exposed to any object other than
* CategorizedAbilityFacet.
*
* @param id
* The CharID for which the Set should be returned
* @param cat
* The Ability Category identifying the list of Abilities to be
* returned
* @param nat
* The Ability Nature identifying the list of Abilities to be
* returned
* @return The Set for the Player Character represented by the given CharID;
* null if no information has been set in this
* CategorizedAbilityFacet for the Player Character.
*/
private Set<Ability> getCachedSet(CharID id, Category<Ability> cat,
Nature nat)
{
Map<Category<Ability>, Map<Nature, Set<Ability>>> catMap = getCachedMap(id);
if (catMap == null)
{
return null;
}
Map<Nature, Set<Ability>> natureMap = catMap.get(cat);
if (natureMap == null)
{
return null;
}
return natureMap.get(nat);
}
/**
* Returns the type-safe Map for this CategorizedAbilityFacet and the given
* CharID. May return null if no information has been set in this
* CategorizedAbilityFacet for the given CharID.
*
* Note that this method SHOULD NOT be public. The Map is owned by
* CategorizedAbilityFacet, and since it can be modified, a reference to
* that Map should not be exposed to any object other than
* CategorizedAbilityFacet.
*
* @param id
* The CharID for which the Map should be returned
* @return The Map for the Player Character represented by the given CharID;
* null if no information has been set in this
* CategorizedAbilityFacet for the Player Character.
*/
private Map<Category<Ability>, Map<Nature, Set<Ability>>> getCachedMap(
CharID id)
{
return (Map<Category<Ability>, Map<Nature, Set<Ability>>>) getCache(id);
}
/**
* Removes all Abilities from the list of Abilities stored in this
* CategorizedAbilityFacet for the Player Character represented by the given
* CharID
*
* @param id
* The CharID representing the Player Character from which all
* Abilities should be removed
*/
public void removeAll(CharID id)
{
Map<Category<Ability>, Map<Nature, Set<Ability>>> catMap =
(Map<Category<Ability>, Map<Nature, Set<Ability>>>) removeCache(
id);
if (catMap != null)
{
for (Map.Entry<Category<Ability>, Map<Nature, Set<Ability>>> catME : catMap
.entrySet())
{
// Category<Ability> cat = catME.getKey();
Map<Nature, Set<Ability>> natMap = catME.getValue();
processRemoveNatureMap(id, natMap);
}
}
}
/**
* Removes all of the Ability objects in the given Category from the lists
* of Abilities stored in this CategorizedAbilityFacet for the Player
* Character represented by the given CharID
*
* @param id
* The CharID representing the Player Character from which the
* given Abilities should be removed
* @param cat
* The Ability Category identifying which Ability objects are to
* be removed from the lists of Abilities stored in this
* CategorizedAbilityFacet for the Player Character represented
* by the given CharID
* @throws NullPointerException
* if the given Collection is null
*/
public void removeAll(CharID id, Category<Ability> cat)
{
Map<Category<Ability>, Map<Nature, Set<Ability>>> catMap = getCachedMap(id);
if (catMap != null)
{
Map<Nature, Set<Ability>> natMap = catMap.remove(cat);
if (natMap != null)
{
processRemoveNatureMap(id, natMap);
}
}
}
/**
* Removes all of the objects of the given Category and Nature from the list
* of Abilities stored in this CategorizedAbilityFacet for the Player
* Character represented by the given CharID
*
* @param id
* The CharID representing the Player Character from which the
* given Abilities should be removed
* @param cat
* The Ability Category identifying which Ability objects are to
* be removed from the lists of Abilities stored in this
* CategorizedAbilityFacet for the Player Character represented
* by the given CharID
* @param nature
* The Ability Nature identifying which Ability objects are to be
* removed from the lists of Abilities stored in this
* CategorizedAbilityFacet for the Player Character represented
* by the given CharID
* @throws NullPointerException
* if the given Collection is null
*/
public void removeAll(CharID id, Category<Ability> cat, Nature nature)
{
Map<Category<Ability>, Map<Nature, Set<Ability>>> catMap = getCachedMap(id);
if (catMap != null)
{
Map<Nature, Set<Ability>> natMap = catMap.remove(cat);
if (natMap != null)
{
Set<Ability> abilitySet = natMap.get(nature);
if (abilitySet != null)
{
processRemoveAbilityMap(id, abilitySet);
}
}
}
}
private void processRemoveNatureMap(CharID id,
Map<Nature, Set<Ability>> natMap)
{
for (Map.Entry<Nature, Set<Ability>> natME : natMap.entrySet())
{
// Nature nature = natME.getKey();
processRemoveAbilityMap(id, natME.getValue());
}
}
/**
* Removes all of the Ability objects in the given Nature from the lists of
* Abilities stored in this CategorizedAbilityFacet for the Player Character
* represented by the given CharID
*
* @param id
* The CharID representing the Player Character from which the
* given Abilities should be removed
* @param nature
* The Ability Nature identifying which Ability objects are to be
* removed from the lists of Abilities stored in this
* CategorizedAbilityFacet for the Player Character represented
* by the given CharID
* @throws NullPointerException
* if the given Collection is null
*/
public void removeAll(CharID id, Nature nature)
{
Map<Category<Ability>, Map<Nature, Set<Ability>>> catMap = getCachedMap(id);
if (catMap != null)
{
for (Map.Entry<Category<Ability>, Map<Nature, Set<Ability>>> catME : catMap
.entrySet())
{
// Category<Ability> cat = catME.getKey();
Set<Ability> abilitySet = catME.getValue().remove(nature);
if (abilitySet != null)
{
processRemoveAbilityMap(id, abilitySet);
}
}
}
}
private void processRemoveAbilityMap(CharID id, Set<Ability> abilitySet)
{
for (Ability a : abilitySet)
{
fireDataFacetChangeEvent(id, a, DataFacetChangeEvent.DATA_REMOVED);
}
}
/**
* Returns a non-null copy of the Set of Ability Category objects in this
* CategorizedAbilityFacet for the Player Character represented by the given
* CharID. This method returns an empty set if no objects are in this
* CategorizedAbilityFacet for the Player Character identified by the given
* CharID.
*
* This method is value-semantic in that ownership of the returned Set is
* transferred to the class calling this method. Modification of the
* returned Set will not modify this CategorizedAbilityFacet and
* modification of this CategorizedAbilityFacet will not modify the returned
* Set. Modifications to the returned Set will also not modify any future or
* previous objects returned by this (or other) methods on
* CategorizedAbilityFacet. If you wish to modify the information stored in
* this CategorizedAbilityFacet, you must use the add*() and remove*()
* methods of CategorizedAbilityFacet.
*
* @param id
* The CharID representing the Player Character for which the
* items in this CategorizedAbilityFacet should be returned.
* @return A non-null Set of Ability Category objects in this
* CategorizedAbilityFacet for the Player Character represented by
* the given CharID
*/
public Set<Category<Ability>> getCategories(CharID id)
{
Map<Category<Ability>, Map<Nature, Set<Ability>>> map = getCachedMap(id);
if (map == null)
{
return Collections.emptySet();
}
return Collections.unmodifiableSet(map.keySet());
}
/**
* Copies the contents of the CategorizedAbilityFacet from one Player
* Character to another Player Character, based on the given CharIDs
* representing those Player Characters.
*
* This is a method in CategorizedAbilityFacet in order to avoid exposing
* the mutable Map object to other classes. This should not be inlined, as
* the Map is internal information to CategorizedAbilityFacet 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 CategorizedAbilityFacet of
* one Player Character will only impact the Player Character where the
* CategorizedAbilityFacet 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<Category<Ability>, Map<Nature, Set<Ability>>> map = getCachedMap(source);
if (map != null)
{
for (Entry<Category<Ability>, Map<Nature, Set<Ability>>> me : map
.entrySet())
{
Category<Ability> cat = me.getKey();
for (Entry<Nature, Set<Ability>> nme : me.getValue().entrySet())
{
Nature nat = nme.getKey();
ensureCachedSet(copy, cat, nat);
getCachedSet(copy, cat, nat).addAll(nme.getValue());
}
}
}
}
}