/*
* Copyright (c) Thomas Parker, 2014.
*
* 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.ArrayList;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import pcgen.cdom.enumeration.CharID;
import pcgen.cdom.facet.BonusSkillRankChangeFacet.SkillRankChangeEvent;
import pcgen.cdom.facet.SkillRankFacet.SkillRankChangeListener;
import pcgen.cdom.facet.base.AbstractStorageFacet;
import pcgen.cdom.facet.event.AssociationChangeEvent;
import pcgen.cdom.facet.event.AssociationChangeListener;
import pcgen.core.Skill;
/**
* TotalSkillRankFacet stores the total skill rank for Skills (includes user
* taken ranks and BONUS:SKILLRANK)
*
* @author Thomas Parker (thpr [at] yahoo.com)
*/
public class TotalSkillRankFacet extends AbstractStorageFacet<CharID> implements
SkillRankChangeListener,
pcgen.cdom.facet.BonusSkillRankChangeFacet.SkillRankChangeListener
{
private static final Double DOUBLE_ZERO = 0.0d;
private SkillRankFacet skillRankFacet;
private BonusSkillRankChangeFacet bonusSkillRankChangeFacet;
/**
* Set the given association for the given object in this
* AbstractAssociationFacet for the Player Character represented by the
* given CharID
*
* @param id
* The CharID representing the Player Character for which the
* given skill rank should be set
* @param sk
* The skill for which the rank will be set
* @param rank
* The rank for the given skill
*/
public void set(CharID id, Skill sk, Double rank)
{
if (sk == null)
{
throw new IllegalArgumentException("Skill cannot be null");
}
if (rank == null)
{
throw new IllegalArgumentException("Rank cannot be null");
}
Map<Skill, Double> map = getConstructingInfo(id);
Double currentRank = map.get(sk);
boolean isNew = (currentRank == null) || (rank.doubleValue() != currentRank.doubleValue());
if (isNew)
{
map.put(sk, rank);
if (support != null)
{
support.fireAssociationChange(id, sk, currentRank, rank);
}
}
}
/**
* Removes the association for the given source object in this
* AbstractAssociationFacet for the Player Character represented by the
* given CharID.
*
* @param id
* The CharID representing the Player Character from which the
* given item association should be removed
* @param sk
* The skill for which the rank should be removed
*/
public void remove(CharID id, Skill sk)
{
if (sk == null)
{
throw new IllegalArgumentException("Skill cannot be null");
}
Map<Skill, Double> map = getInfo(id);
if (map == null)
{
return;
}
Double currentRank = map.get(sk);
if (currentRank == null)
{
return;
}
if (support != null)
{
support.fireAssociationChange(id, sk, currentRank, DOUBLE_ZERO);
}
}
/**
* Copies the contents of the AbstractScopeFacet from one Player Character
* to another Player Character, based on the given CharIDs representing
* those Player Characters.
*
* This is a method in AbstractScopeFacet in order to avoid exposing the
* mutable Map object to other classes. This should not be inlined, as the
* Map is internal information to AbstractScopeFacet 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 AbstractScopeFacet of one
* Player Character will only impact the Player Character where the
* AbstractScopeFacet 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<Skill, Double> map = getInfo(source);
if (map != null)
{
getConstructingInfo(copy).putAll(map);
}
}
/**
* The AssociationChangeSupport object that manages the listeners that
* receive AssociationChangeEvents from this AssociationChangeFacet.
*/
private AssociationChangeSupport support;
/**
* Gets the association for the Player Character (identified by the given
* CharID) and the given source object.
*
* @param id
* The CharID identifying the Player Character for which the
* association get is being performed.
* @param obj
* The source object for which the association get is being
* performed.
* @return The association for the Player Character (identified by the given
* CharID) and the given source object
*/
public Double get(CharID id, Skill obj)
{
if (obj == null)
{
throw new IllegalArgumentException(
"Object for getting association may not be null");
}
Map<Skill, Double> map = getInfo(id);
if (map != null)
{
return map.get(obj);
}
return null;
}
/**
* AssociationChangeSupport is a support class that provides the actual
* structure for adding and removing listeners to a class that can provide
* updates for changes to Association Bonus values on a Player Character.
*
* @author Thomas Parker (thpr [at] yahoo.com)
*/
public static class AssociationChangeSupport
{
private final Object source;
public AssociationChangeSupport(Object src)
{
source = src;
}
private List<AssociationChangeListener> listeners =
new ArrayList<>();
/**
* Adds a new AssociationChangeListener to receive
* AssociationChangeEventas from the change source. The given
* AssociationChangeListener is subscribed to all Association Bonus
* value changes.
*
* Note that the AssociationChangeListeners are a list, meaning a given
* AssociationChangeListener can be added more than once, and if that
* occurs, it must be removed an equivalent number of times in order to
* no longer receive events from this AssociationChangeSupport.
*
* @param listener
* The AssociationChangeListener to receive
* AssociationChangeEvents from this AssociationChangeSupport
*/
public synchronized void addAssociationChangeListener(
AssociationChangeListener listener)
{
listeners.add(listener);
}
/**
* Removes a AssociationChangeListener so that it will no longer receive
* AssociationChangeEvents from the source DataFacet.
*
* @param listener
* The AssociationChangeListener to be removed
*/
public synchronized void removeAssociationChangeListener(
AssociationChangeListener listener)
{
listeners.remove(listener);
}
public synchronized AssociationChangeListener[] getAssociationChangeListeners()
{
return (listeners.toArray(new AssociationChangeListener[listeners.size()]));
}
/**
* Sends a AssociationChangeEvent to the AssociationChangeListeners that
* are receiving AssociationChangeEvents from the change source.
*
* @param id
* The CharID identifying the Player Character to which the
* AssociationChangeEvent relates.
* @param skill
* The skill for which the Association Bonus value changed
* @param oldValue
* The previous value of the Bonus value
* @param newValue
* The new value of the Bonus value
*/
public void fireAssociationChange(CharID id, Skill skill,
Number oldValue, Number newValue)
{
AssociationChangeEvent bce =
new AssociationChangeEvent(id, skill, oldValue, newValue,
source);
for (AssociationChangeListener target : listeners)
{
target.bonusChange(bce);
}
}
}
/**
* Adds a new AssociationChangeListener to receive AssociationChangeEvents
* from AssociationChangeFacet. The given AssociationChangeListener
* subscribed to all Association Bonus changes.
*
* Note that the AssociationChangeListeners are a list, meaning a given
* AssociationChangeListener can be added more than once, and if that
* occurs, it must be removed an equivalent number of times in order to no
* longer receive events from this AssociationChangeFacet.
*
* @param listener
* The AssociationChangeListener to receive
* AssociationChangeEvents from this AssociationChangeFacet
*/
public void addAssociationChangeListener(AssociationChangeListener listener)
{
if (support == null)
{
support = new AssociationChangeSupport(this);
}
support.addAssociationChangeListener(listener);
}
/**
* Removes a AssociationChangeListener so that it will no longer receive
* AssociationChangeEvents from AssociationChangeFacet.
*
* @param listener
* The AssociationChangeListener to be removed
*/
public void removeAssociationChangeListener(
AssociationChangeListener listener)
{
if (support == null)
{
support = new AssociationChangeSupport(this);
}
support.removeAssociationChangeListener(listener);
}
private Map<Skill, Double> getConstructingInfo(CharID id)
{
Map<Skill, Double> map = getInfo(id);
if (map == null)
{
map = new IdentityHashMap<>();
setCache(id, map);
}
return map;
}
private Map<Skill, Double> getInfo(CharID id)
{
return (Map<Skill, Double>) getCache(id);
}
@Override
public void bonusChange(SkillRankChangeEvent srce)
{
CharID id = srce.getCharID();
Skill skill = srce.getSkill();
double newBonus = srce.getNewVal().doubleValue();
float rank = skillRankFacet.getRank(id, skill);
set(id, skill, rank + newBonus);
}
@Override
public void rankChanged(
pcgen.cdom.facet.SkillRankFacet.SkillRankChangeEvent srce)
{
CharID id = srce.getCharID();
Skill skill = srce.getSkill();
float newRank = srce.getNewRank();
double bonus = bonusSkillRankChangeFacet.getRank(id, skill);
set(id, skill, newRank + bonus);
}
public void init()
{
skillRankFacet.addSkillRankChangeListener(this);
bonusSkillRankChangeFacet.addSkillRankChangeListener(this);
}
public void setSkillRankFacet(SkillRankFacet skillRankFacet)
{
this.skillRankFacet = skillRankFacet;
}
public void setBonusSkillRankChangeFacet(
BonusSkillRankChangeFacet bonusSkillRankChangeFacet)
{
this.bonusSkillRankChangeFacet = bonusSkillRankChangeFacet;
}
}