package net.obnoxint.util;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;
/**
* <p>
* This class is a simple and abstract organizational unit utilizable for statistical purposes.
* </p>
* <p>
* Its value (balance) is backed by a volatile long field. The balance can be negative.<br>
* An instance of Stat can also be used to store percentual values. In this case Long.MAX_VALUE represents 100% and 0L represents 0%.
* </p>
*/
public abstract class Stat implements Serializable {
/**
* A simple implementation of Stat.
*/
public static class SimpleStat extends Stat {
private static final long serialVersionUID = -3702196029599532933L;
public SimpleStat(final Stat stat) {
super(stat);
}
public SimpleStat(final String id) {
super(id);
}
public SimpleStat(final String id, final float percentage) {
super(id, percentage);
}
public SimpleStat(final String id, final long balance) {
super(id, balance);
}
}
/**
* Classes which implement this interface can be registered with one or more instances of Stat in order to be notified about balance modifications.
*/
public static interface StatListener {
/**
* Called by a Stat if a modifaction of the balance occurs. The new balance can be acquired by calling the {@link Stat#getBalance()} method.
*
* @param stat the calling Stat.
* @param previousBalance the previous balance of the calling Stat.
*/
void onBalanceChanged(Stat stat, long previousBalance);
}
private static final long serialVersionUID = 280113680395112996L;
protected static long getPercentageLong(final float percentage) {
return (long) ((Long.MAX_VALUE / 100f) * ((percentage > 100f) ? 100f : ((percentage < 0f) ? 0f : percentage)));
}
private volatile long balance = 0;
private final String id;
private final long initialBalance;
private final transient Set<StatListener> listeners;
/**
* Creates a clone of the given Stat.
*
* @param stat the Stat.
*/
public Stat(final Stat stat) {
this(stat.id, stat.balance, new HashSet<>(stat.listeners), stat.initialBalance);
}
/**
* Creates a new instance with the initial balance of 0.
*
* @param id the id.
*/
public Stat(final String id) {
this(id, 0L);
}
/**
* Creates a new instance with a non-decimal percentual (long) representation of the given percentage.
*
* @param id the id.
* @param percentage the percentage.
*/
public Stat(final String id, final float percentage) {
this(id, getPercentageLong(percentage));
}
/**
* Creates a new instance with the given balance.
*
* @param id the id.
* @param balance the balance.
*/
public Stat(final String id, final long balance) {
this(id, balance, null, balance);
}
private Stat(final String id, final long balance, final Set<StatListener> listeners, final long initialBalance) {
this.id = id;
this.balance = balance;
this.listeners = (listeners == null) ? new HashSet<StatListener>() : listeners;
this.initialBalance = initialBalance;
}
/**
* @return true if <code>
* ((Stat)obj).getId().equals(getId())
* </code> or if <code>
* ((String)obj).equals(getId())
* </code>.
*/
@Override
public boolean equals(final Object obj) {
if (obj instanceof Stat) {
return ((Stat) obj).id.equals(id);
} else if (obj instanceof String) {
return ((String) obj).equals(id);
}
return false;
}
/**
* @return the balance.
*/
public final long getBalance() {
return balance;
}
/**
* @return the id.
*/
public final String getId() {
return id;
}
/**
* @return the balance used to initialize this instance.
*/
public final long getInitialBalance() {
return initialBalance;
}
/**
* @return a percentual representation of the balance. Always returns 0f if the balance is <= 0L.
*/
public final float getPercentage() {
if (balance <= 0L) {
return 0f;
} else if (balance == Long.MAX_VALUE) {
return 100f;
} else {
return ((balance / Long.MAX_VALUE) * 100f);
}
}
/**
* Modifies the balance, preventing over-/underflow.
*
* @param balance the modification value.
* @return true if an over- or underflow was prevented.
*/
public boolean modifyBalance(final long balance) {
long l = this.balance;
boolean r = false;
if (balance < 0 && (l - balance > l)) { // underflow-check
l = Long.MIN_VALUE;
r = true;
} else if (balance > 0 && (l + balance < l)) { // overflow-check
l = Long.MAX_VALUE;
r = true;
} else {
l += balance;
}
setBalance(l);
return r;
}
/**
* Resets the balance to the balance used to initialize this instance.
*/
public void reset() {
balance = initialBalance;
}
/**
* @param balance the new balance.
*/
public void setBalance(final long balance) {
final long previousBalance = this.balance;
this.balance = balance;
for (final StatListener l : getListeners()) {
l.onBalanceChanged(this, previousBalance);
}
}
/**
* @param percentage the new percentual balance.
*/
public void setPercentage(final float percentage) {
setBalance(getPercentageLong(percentage));
}
/**
* @param listener an instance of {@link StatListener} which will be notfied about balance modifications.
*/
protected void addListener(final StatListener listener) {
if (listener != null) {
listeners.add(listener);
}
}
/**
* @return a Set of all registered {@link StatListener}s.
*/
protected Set<? extends StatListener> getListeners() {
return new HashSet<>(listeners);
}
/**
* Unregisters a {@link StatListener}. It will no longer be notified about balance modifications.
*
* @param listener the {@link StatListener}.
*/
protected void removeListener(final StatListener listener) {
listeners.remove(listener);
}
}