/**
* Project: PFRPG-Toolset
* Created: Oct 21, 2006 by bebopJMM
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.lostkingdomsfrontier.pfrpg;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* AdjustableValue manages a single score, such as an ability or hitPoints. It tracks all
* adjustments, however temporary or permanent, against the base score. It notifies all subscribers
* to changes in the value being managed.
*
* @author bebopjmm
* @since sprint-0.1
*/
public class AdjustableValue implements AdjustmentListener
{
static final Log LOG = LogFactory.getLog(AdjustableValue.class);
public static final int DEFAULT_VALUE = 0;
/**
* The baseValue is the raw score without any modifiers.
*/
private int baseValue;
/**
* The currentValue is the calculated score with all currently applicable modifiers
*/
private int currentValue;
/**
* Optional identifying name for the adjustable value (useful for debugging)
*/
private String name = "";
/**
* The current set of adjustments impacting this adjustable value.
*/
List<Adjustment> adjustments;
/**
* Subscribers to changes in currentValue
*/
List<AdjustableValueListener> subscribers;
/**
* Instantiates a new AdjustableValue, setting both base and current values to the provided
* baseValue.
*
* @param baseValue
* @since sprint-0.1
*/
public AdjustableValue(int baseValue)
{
this.baseValue = baseValue;
this.currentValue = baseValue;
adjustments = new ArrayList<Adjustment>();
subscribers = new ArrayList<AdjustableValueListener>();
}
/**
* Returns the identifying name of this adjustable value.
*
* @return the identifying name
* @since sprint-0.1
*/
public String getName()
{
return name;
}
/**
* Assigns an identifying name to this adjustable value.
*
* @param name the name to set
* @since sprint-0.1
*/
public void setName(String name)
{
this.name = name;
}
/**
* Returns the base score with no adjustments applied.
*
* @return the baseValue
* @since sprint-0.1
*/
public synchronized int getBase()
{
return baseValue;
}
/**
* Assigns a base score to which adjustments will be applied. This will trigger a recalculation
* and notification to subscribers.
*
* @param baseValue the baseValue to set
* @since sprint-0.1
*/
public synchronized void setBase(int baseValue)
{
this.baseValue = baseValue;
recalcValue();
notifyOfChange();
}
/**
* Returns the calculated value (base + adjustments).
*
* @return the current ability value
* @since sprint-0.1
*/
public synchronized int getCurrent()
{
return currentValue;
}
/**
* Adds the adjustment to the value calculation set, updating the currentValue and notifying all
* subscribed listeners. No action is taken if the provided Adjustment is already part of the
* list.
*
* @param adjustment new Adjustment to include in value calculation
* @since sprint-0.1
*/
public synchronized void addAdjustment(Adjustment adjustment)
{
if (adjustments.contains(adjustment)) {
LOG.warn(name + ": Attempt to add duplicate ability adjustment: "
+ adjustment.getPedigree());
return;
}
LOG.debug(name + ": Adding Adjustment: " + adjustment.getPedigree() + " of value: "
+ adjustment.getValue());
adjustments.add(adjustment);
adjustment.subscribe(this);
currentValue += adjustment.getValue();
LOG.debug(name + ": Current value =" + currentValue);
notifyOfChange();
}
/**
* Removes the adjustment from the value calculation set, updating the currentValue and
* notifying all subscribed listeners. No action is taken if the provide Adjustment is not part
* of the list.
*
* @param adjustment Adjustment to be removed from value calculation.
* @since sprint-0.1
*/
public synchronized void removeAdjustment(Adjustment adjustment)
{
if (!adjustments.contains(adjustment)) {
LOG.warn(name + ": Attempt to remove adjustment not associated to ability: "
+ adjustment.getPedigree());
return;
}
LOG.debug(name + ": Removing Adjustment: " + adjustment.getPedigree() + " of value: "
+ adjustment.getValue());
adjustments.remove(adjustment);
adjustment.unsubscribe(this);
currentValue -= adjustment.getValue();
LOG.debug(name + ": Current value =" + currentValue);
notifyOfChange();
}
/**
* Returns the list of Adjustments currently affecting the base score.
*
* @return the list of adjustments
* @since sprint-0.1
*/
public List<Adjustment> getAdjustments()
{
return adjustments;
}
/**
* Add the listener as a subscriber to changes in the AdjustableValue if it hasn't already
* subscribed.
*
* @param listener AdjustableValueListener to add
* @since sprint-0.1
*/
public void subscribe(AdjustableValueListener listener)
{
synchronized (subscribers) {
if (!subscribers.contains(listener)) {
subscribers.add(listener);
}
}
}
/**
* Removes the listener as a subscriber to changes in the AdjustableValue.
*
* @param listener AdjustableValueListener to remove
* @since sprint-0.1
*/
public void unsubscribe(AdjustableValueListener listener)
{
synchronized (subscribers) {
if (subscribers.contains(listener)) {
subscribers.remove(listener);
}
}
}
/*
* (non-Javadoc)
*
* @see
* org.rollinitiative.d20.AdjustmentListener#valueChanged(org.rollinitiative.d20.Adjustment)
*
* @since sprint-0.1
*/
@Override
public synchronized void valueChanged(Adjustment adjustment)
{
// Recalculate the currentValue and notify if different than old value.
int oldVal = currentValue;
recalcValue();
if (oldVal != currentValue) {
notifyOfChange();
}
else {
LOG.debug(name + ": No change in currentValue, not notifying subscribers");
}
}
public static Adjustment findAdjustmentByPedigree(AdjustableValue value, String adjustmentPedigree)
{
synchronized (value) {
for (Adjustment adjustment : value.getAdjustments()) {
if (adjustment.getPedigree().equals(adjustmentPedigree))
return adjustment;
}
}
return null;
}
/**
* Recalculates the current value (baseValue + all adjustments)
*
* @since sprint-0.1
*/
protected void recalcValue()
{
LOG.debug(name + ": recalculating current value: baseValue + sum of adjustments");
currentValue = baseValue;
for (Adjustment adjustment : adjustments) {
currentValue += adjustment.getValue();
}
}
/**
* Notifies all subscribers of a change in the AdjustableValue via their valueChanged() method.
*
* {@link AdjustableValueListener#valueChanged(AdjustableValue)}
*
* @since sprint-0.1
*/
protected void notifyOfChange()
{
synchronized (subscribers) {
LOG.debug(name + ": Notifying subcribers of currentValue change, totalSubscribers = "
+ subscribers.size());
for (AdjustableValueListener subscriber : subscribers) {
subscriber.valueChanged(this);
}
}
}
}