Extracted the Bang stats system into reusable land. Too late for Yohoho, but
Whirled will benefit. git-svn-id: svn+ssh://src.earth.threerings.net/vilya/trunk@251 c613c5cb-e716-0410-b11b-feb51c14d237
This commit is contained in:
@@ -204,6 +204,10 @@
|
||||
<fileset dir="${classes.dir}" includes="com/threerings/stage/**"/>
|
||||
<fileset dir="${classes.dir}" includes="rsrc/**/stage/**"/>
|
||||
</jar>
|
||||
<jar destfile="${deploy.dir}/${app.name}-stats.jar">
|
||||
<fileset dir="${classes.dir}" includes="com/threerings/stats/**"/>
|
||||
<fileset dir="${classes.dir}" includes="rsrc/**/stats/**"/>
|
||||
</jar>
|
||||
<jar destfile="${deploy.dir}/${app.name}-whirled.jar">
|
||||
<fileset dir="${classes.dir}" includes="com/threerings/whirled/**"/>
|
||||
<fileset dir="${classes.dir}" includes="rsrc/**/whirled/**"/>
|
||||
@@ -238,6 +242,7 @@
|
||||
<weave inputjar="${inpre}-parlor.jar" outputjar="${outpre}-parlor.jar"/>
|
||||
<weave inputjar="${inpre}-puzzle.jar" outputjar="${outpre}-puzzle.jar"/>
|
||||
<weave inputjar="${inpre}-stage.jar" outputjar="${outpre}-stage.jar"/>
|
||||
<weave inputjar="${inpre}-stats.jar" outputjar="${outpre}-stats.jar"/>
|
||||
<weave inputjar="${inpre}-whirled.jar" outputjar="${outpre}-whirled.jar"/>
|
||||
|
||||
<!-- now weave again with the verifier to check for unweavable 1.5isms -->
|
||||
@@ -245,6 +250,7 @@
|
||||
<antcall target="vweave"><param name="which" value="parlor"/></antcall>
|
||||
<antcall target="vweave"><param name="which" value="puzzle"/></antcall>
|
||||
<antcall target="vweave"><param name="which" value="stage"/></antcall>
|
||||
<antcall target="vweave"><param name="which" value="stats"/></antcall>
|
||||
<antcall target="vweave"><param name="which" value="whirled"/></antcall>
|
||||
</target>
|
||||
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
//
|
||||
// $Id$
|
||||
|
||||
package com.threerings.stats;
|
||||
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* A placeholder class that contains a reference to the log object used by this project.
|
||||
*/
|
||||
public class Log
|
||||
{
|
||||
/** We dispatch our log messages through this logger. */
|
||||
public static Logger log = Logger.getLogger("com.threerings.stats");
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
//
|
||||
// $Id$
|
||||
|
||||
package com.threerings.stats.data;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import com.threerings.io.ObjectInputStream;
|
||||
import com.threerings.io.ObjectOutputStream;
|
||||
|
||||
/**
|
||||
* Maps up to 127 string values to integers in the range 0 - 127.
|
||||
*/
|
||||
public class ByteByteStringMapStat extends StringMapStat
|
||||
{
|
||||
@Override // documentation inherited
|
||||
public void persistTo (ObjectOutputStream out, AuxDataSource aux)
|
||||
throws IOException
|
||||
{
|
||||
out.writeByte(_keys.length);
|
||||
for (int ii = 0; ii < _keys.length; ii++) {
|
||||
out.writeByte((byte)aux.getStringCode(_type, _keys[ii]));
|
||||
out.writeByte((byte)_values[ii]);
|
||||
}
|
||||
}
|
||||
|
||||
@Override // documentation inherited
|
||||
public void unpersistFrom (ObjectInputStream in, AuxDataSource aux)
|
||||
throws IOException, ClassNotFoundException
|
||||
{
|
||||
_keys = new String[in.readByte()];
|
||||
_values = new int[_keys.length];
|
||||
for (int ii = 0; ii < _keys.length; ii++) {
|
||||
_keys[ii] = aux.getCodeString(_type, in.readByte());
|
||||
_values[ii] = in.readByte();
|
||||
}
|
||||
}
|
||||
|
||||
@Override // from StringMapStat
|
||||
protected int getMaxValue ()
|
||||
{
|
||||
return Byte.MAX_VALUE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
//
|
||||
// $Id$
|
||||
|
||||
package com.threerings.stats.data;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import com.threerings.io.ObjectInputStream;
|
||||
import com.threerings.io.ObjectOutputStream;
|
||||
|
||||
/**
|
||||
* A string set that maps its values to bytes.
|
||||
*/
|
||||
public class ByteStringSetStat extends StringSetStat
|
||||
{
|
||||
@Override // documentation inherited
|
||||
public void persistTo (ObjectOutputStream out, AuxDataSource aux)
|
||||
throws IOException
|
||||
{
|
||||
out.writeByte(_values.length);
|
||||
for (int ii = 0; ii < _values.length; ii++) {
|
||||
out.writeByte((byte)aux.getStringCode(_type, _values[ii]));
|
||||
}
|
||||
}
|
||||
|
||||
@Override // documentation inherited
|
||||
public void unpersistFrom (ObjectInputStream in, AuxDataSource aux)
|
||||
throws IOException, ClassNotFoundException
|
||||
{
|
||||
_values = new String[in.readByte()];
|
||||
for (int ii = 0; ii < _values.length; ii++) {
|
||||
_values[ii] = aux.getCodeString(_type, in.readByte());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
//
|
||||
// $Id$
|
||||
|
||||
package com.threerings.stats.data;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import com.samskivert.util.ArrayUtil;
|
||||
import com.samskivert.util.StringUtil;
|
||||
|
||||
import com.threerings.io.ObjectInputStream;
|
||||
import com.threerings.io.ObjectOutputStream;
|
||||
|
||||
/**
|
||||
* Used to track an integer array statistic.
|
||||
*/
|
||||
public class IntArrayStat extends Stat
|
||||
{
|
||||
/**
|
||||
* Returns the value of this statistic.
|
||||
*/
|
||||
public int[] getValue ()
|
||||
{
|
||||
return _value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets this statistic's value to the specified value.
|
||||
*
|
||||
* @return true if the stat was modified, false if not.
|
||||
*/
|
||||
public boolean setValue (int[] value)
|
||||
{
|
||||
if (!Arrays.equals(_value, value)) {
|
||||
_value = value;
|
||||
_modified = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends a value to this statistic.
|
||||
*/
|
||||
public void appendValue (int value)
|
||||
{
|
||||
_value = ArrayUtil.append(_value, value);
|
||||
_modified = true;
|
||||
}
|
||||
|
||||
@Override // documentation inherited
|
||||
public String valueToString ()
|
||||
{
|
||||
return StringUtil.toString(_value);
|
||||
}
|
||||
|
||||
@Override // documentation inherited
|
||||
public void persistTo (ObjectOutputStream out, AuxDataSource aux)
|
||||
throws IOException
|
||||
{
|
||||
out.writeObject(_value);
|
||||
}
|
||||
|
||||
@Override // documentation inherited
|
||||
public void unpersistFrom (ObjectInputStream in, AuxDataSource aux)
|
||||
throws IOException, ClassNotFoundException
|
||||
{
|
||||
_value = (int[])in.readObject();
|
||||
}
|
||||
|
||||
/** Contains the integer list value of this statistic. */
|
||||
protected int[] _value = new int[0];
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
//
|
||||
// $Id$
|
||||
|
||||
package com.threerings.stats.data;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import com.threerings.io.ObjectInputStream;
|
||||
import com.threerings.io.ObjectOutputStream;
|
||||
|
||||
/**
|
||||
* Used to track a single integer statistic.
|
||||
*/
|
||||
public class IntStat extends Stat
|
||||
{
|
||||
/**
|
||||
* Returns the value of this integer statistic.
|
||||
*/
|
||||
public int getValue ()
|
||||
{
|
||||
return _value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets this statistic's value to the specified value.
|
||||
*
|
||||
* @return true if the stat was modified, false if not.
|
||||
*/
|
||||
public boolean setValue (int value)
|
||||
{
|
||||
if (value != _value) {
|
||||
_value = value;
|
||||
_modified = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increments this statistic by the specified delta value.
|
||||
*
|
||||
* @return true if the stat was modified, false if not.
|
||||
*/
|
||||
public boolean increment (int delta)
|
||||
{
|
||||
return setValue(_value + delta);
|
||||
}
|
||||
|
||||
@Override // documentation inherited
|
||||
public String valueToString ()
|
||||
{
|
||||
return String.valueOf(_value);
|
||||
}
|
||||
|
||||
@Override // documentation inherited
|
||||
public void persistTo (ObjectOutputStream out, AuxDataSource aux)
|
||||
throws IOException
|
||||
{
|
||||
out.writeInt(_value);
|
||||
}
|
||||
|
||||
@Override // documentation inherited
|
||||
public void unpersistFrom (ObjectInputStream in, AuxDataSource aux)
|
||||
throws IOException, ClassNotFoundException
|
||||
{
|
||||
_value = in.readInt();
|
||||
}
|
||||
|
||||
/** Contains the integer value of this statistic. */
|
||||
protected int _value;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
//
|
||||
// $Id$
|
||||
|
||||
package com.threerings.stats.data;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import com.threerings.io.ObjectInputStream;
|
||||
import com.threerings.io.ObjectOutputStream;
|
||||
|
||||
/**
|
||||
* A string set that maps its values to integers.
|
||||
*/
|
||||
public class IntStringSetStat extends StringSetStat
|
||||
{
|
||||
@Override // documentation inherited
|
||||
public void persistTo (ObjectOutputStream out, AuxDataSource aux)
|
||||
throws IOException
|
||||
{
|
||||
out.writeInt(_values.length);
|
||||
for (int ii = 0; ii < _values.length; ii++) {
|
||||
out.writeInt(aux.getStringCode(_type, _values[ii]));
|
||||
}
|
||||
}
|
||||
|
||||
@Override // documentation inherited
|
||||
public void unpersistFrom (ObjectInputStream in, AuxDataSource aux)
|
||||
throws IOException, ClassNotFoundException
|
||||
{
|
||||
_values = new String[in.readInt()];
|
||||
for (int ii = 0; ii < _values.length; ii++) {
|
||||
_values[ii] = aux.getCodeString(_type, in.readInt());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
//
|
||||
// $Id$
|
||||
|
||||
package com.threerings.stats.data;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.EOFException;
|
||||
|
||||
import com.threerings.io.ObjectInputStream;
|
||||
import com.threerings.io.ObjectOutputStream;
|
||||
|
||||
/**
|
||||
* Extends the {@link IntStat} by maintaining a maximum value which is updated every time a value
|
||||
* is accumulated to the stat. Thus we track an accumulating value as well as the largest amount by
|
||||
* which it has ever accumulated.
|
||||
*/
|
||||
public class MaxIntStat extends IntStat
|
||||
{
|
||||
/**
|
||||
* Returns the maximum value every accumulated to this integer statistic.
|
||||
*/
|
||||
public int getMaxValue ()
|
||||
{
|
||||
return _maxValue;
|
||||
}
|
||||
|
||||
@Override // from IntStat
|
||||
public boolean increment (int delta)
|
||||
{
|
||||
if (super.increment(delta)) {
|
||||
_maxValue = Math.max(delta, _maxValue);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override // from IntStat
|
||||
public String valueToString ()
|
||||
{
|
||||
return super.valueToString() + " " + String.valueOf(_maxValue);
|
||||
}
|
||||
|
||||
@Override // from IntStat
|
||||
public void persistTo (ObjectOutputStream out, AuxDataSource aux)
|
||||
throws IOException
|
||||
{
|
||||
super.persistTo(out, aux);
|
||||
out.writeInt(_maxValue);
|
||||
}
|
||||
|
||||
@Override // from IntStat
|
||||
public void unpersistFrom (ObjectInputStream in, AuxDataSource aux)
|
||||
throws IOException, ClassNotFoundException
|
||||
{
|
||||
super.unpersistFrom(in, aux);
|
||||
_maxValue = in.readInt();
|
||||
}
|
||||
|
||||
/** The largest value ever accumulated to this stat. */
|
||||
protected int _maxValue;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
//
|
||||
// $Id$
|
||||
|
||||
package com.threerings.stats.data;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import com.threerings.io.ObjectInputStream;
|
||||
import com.threerings.io.ObjectOutputStream;
|
||||
|
||||
/**
|
||||
* A string set that maps its values to shorts.
|
||||
*/
|
||||
public class ShortStringSetStat extends StringSetStat
|
||||
{
|
||||
@Override // documentation inherited
|
||||
public void persistTo (ObjectOutputStream out, AuxDataSource aux)
|
||||
throws IOException
|
||||
{
|
||||
out.writeShort(_values.length);
|
||||
for (int ii = 0; ii < _values.length; ii++) {
|
||||
out.writeShort((short)aux.getStringCode(_type, _values[ii]));
|
||||
}
|
||||
}
|
||||
|
||||
@Override // documentation inherited
|
||||
public void unpersistFrom (ObjectInputStream in, AuxDataSource aux)
|
||||
throws IOException, ClassNotFoundException
|
||||
{
|
||||
_values = new String[in.readShort()];
|
||||
for (int ii = 0; ii < _values.length; ii++) {
|
||||
_values[ii] = aux.getCodeString(_type, in.readShort());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
//
|
||||
// $Id$
|
||||
|
||||
package com.threerings.stats.data;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.zip.CRC32;
|
||||
|
||||
import com.samskivert.util.HashIntMap;
|
||||
import com.samskivert.util.StringUtil;
|
||||
import com.threerings.util.MessageBundle;
|
||||
|
||||
import com.threerings.io.ObjectInputStream;
|
||||
import com.threerings.io.ObjectOutputStream;
|
||||
|
||||
import com.threerings.presents.dobj.DSet;
|
||||
|
||||
import static com.threerings.stats.Log.log;
|
||||
|
||||
/**
|
||||
* A base class for persistent statistics tracked on a per-player basis (some for a single game,
|
||||
* others for all time).
|
||||
*/
|
||||
public abstract class Stat
|
||||
implements DSet.Entry, Cloneable
|
||||
{
|
||||
/**
|
||||
* Defines the various per-player tracked statistics.
|
||||
*/
|
||||
public interface Type
|
||||
{
|
||||
/** Returns a new blank stat instance of the specified type. */
|
||||
public Stat newStat ();
|
||||
|
||||
/** Returns the enum name of stat. */
|
||||
public String name ();
|
||||
|
||||
/** Returns the unique code for this stat which is a function of its name. */
|
||||
public int code ();
|
||||
|
||||
/** Returns true if this stat is persisted between sessions. */
|
||||
public boolean isPersistent ();
|
||||
};
|
||||
|
||||
/** Provides auxilliary information to statistics during the persisting process. */
|
||||
public static interface AuxDataSource
|
||||
{
|
||||
/** Maps the specified string to a unique integer value. */
|
||||
public int getStringCode (Type type, String value);
|
||||
|
||||
/** Maps the specified unique code back to its string value. */
|
||||
public String getCodeString (Type type, int code);
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps a {@link Type}'s code code back to a {@link Type} instance.
|
||||
*/
|
||||
public static Type getType (int code)
|
||||
{
|
||||
return _codeToType.get(code);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by the {@link Type} implementation to map itself to an integer code.
|
||||
*
|
||||
* @return the code to which the supplied type was mapped.
|
||||
*/
|
||||
public static int initType (Type type, Stat prototype)
|
||||
{
|
||||
// compute a code for this stat using the CRC32 hash of its name
|
||||
_crc.reset();
|
||||
_crc.update(type.name().getBytes());
|
||||
int code = (int)_crc.getValue();
|
||||
|
||||
// make sure the code does not collide
|
||||
if (_codeToType.containsKey(code)) {
|
||||
log.warning("Stat type collision! " + type.name() + " and " +
|
||||
_codeToType.get(code).name() + " both map to '" + code + "'.");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// initialize the prototype
|
||||
prototype._type = type;
|
||||
|
||||
// map the stat by its code
|
||||
_codeToType.put(code, type);
|
||||
return code;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type of this statistic.
|
||||
*/
|
||||
public Type getType ()
|
||||
{
|
||||
return _type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the integer code to which this statistic's name maps.
|
||||
*/
|
||||
public int getCode ()
|
||||
{
|
||||
return _type.code();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the supplied statistic has been modified since it
|
||||
* was loaded from the repository.
|
||||
*/
|
||||
public boolean isModified ()
|
||||
{
|
||||
return _modified;
|
||||
}
|
||||
|
||||
/**
|
||||
* Forces this stat to consider itself modified. Generally this is not
|
||||
* called but rather the derived class will update its modified state when
|
||||
* it is actually modified.
|
||||
*/
|
||||
public void setModified (boolean modified)
|
||||
{
|
||||
_modified = modified;
|
||||
}
|
||||
|
||||
/** Writes our custom streamable fields. */
|
||||
public void writeObject (ObjectOutputStream out)
|
||||
throws IOException
|
||||
{
|
||||
out.writeInt(_type.code());
|
||||
out.defaultWriteObject();
|
||||
}
|
||||
|
||||
/** Reads our custom streamable fields. */
|
||||
public void readObject (ObjectInputStream in)
|
||||
throws IOException, ClassNotFoundException
|
||||
{
|
||||
_type = getType(in.readInt());
|
||||
in.defaultReadObject();
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes this instance for storage in the item database. Derived classes must override
|
||||
* this method to implement persistence.
|
||||
*/
|
||||
public abstract void persistTo (ObjectOutputStream out, AuxDataSource aux)
|
||||
throws IOException;
|
||||
|
||||
/**
|
||||
* Unserializes this item from data obtained from the item database. Derived classes must
|
||||
* override this method to implement persistence.
|
||||
*/
|
||||
public abstract void unpersistFrom (ObjectInputStream in, AuxDataSource aux)
|
||||
throws IOException, ClassNotFoundException;
|
||||
|
||||
/**
|
||||
* Generates a string representation of this instance.
|
||||
*/
|
||||
public String toString ()
|
||||
{
|
||||
StringBuffer buf = new StringBuffer(StringUtil.toUSLowerCase(_type.name()));
|
||||
buf.append("=");
|
||||
buf.append(valueToString());
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Derived statistics must override this method and render their value to a string. Used by
|
||||
* {@link #toString} and to display the value in game.
|
||||
*/
|
||||
public abstract String valueToString ();
|
||||
|
||||
// documentation inherited from DSet.Entry
|
||||
public String getKey ()
|
||||
{
|
||||
return _type.name();
|
||||
}
|
||||
|
||||
// documentation inherited from Cloneable
|
||||
public Object clone ()
|
||||
{
|
||||
try {
|
||||
return super.clone();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Clone failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
/** The type of the statistic in question. */
|
||||
protected transient Type _type;
|
||||
|
||||
/** Indicates whether or not this statistic has been modified since it
|
||||
* was loaded from the database. */
|
||||
protected transient boolean _modified;
|
||||
|
||||
/** Used for computing our stat codes. */
|
||||
protected static CRC32 _crc = new CRC32();
|
||||
|
||||
/** The table mapping stat codes to enumerated types. */
|
||||
protected static HashIntMap<Type> _codeToType = new HashIntMap<Type>();
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
//
|
||||
// $Id$
|
||||
|
||||
package com.threerings.stats.data;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
import com.threerings.presents.dobj.DSet;
|
||||
|
||||
/**
|
||||
* A distributed set containing {@link Stat} objects.
|
||||
*/
|
||||
public final class StatSet extends DSet<Stat>
|
||||
{
|
||||
/** An interface to be implemented by an entity that wishes to be notified when the contents of
|
||||
* a stat set change. */
|
||||
public interface Container
|
||||
{
|
||||
public void addToStats (Stat stat);
|
||||
public void updateStats (Stat stat);
|
||||
}
|
||||
|
||||
/** Creates a stat set with the specified contents. */
|
||||
public StatSet (Iterator<Stat> contents)
|
||||
{
|
||||
super(contents);
|
||||
}
|
||||
|
||||
/** Creates a blank stat set. */
|
||||
public StatSet ()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Wires this stat set up to a containing user object. All subsequent modifications will be
|
||||
* published to the container.
|
||||
*/
|
||||
public void setContainer (Container container)
|
||||
{
|
||||
_container = container;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an integer statistic in this set.
|
||||
*
|
||||
* @exception ClassCastException thrown if the registered type of the specified stat is not an
|
||||
* {@link IntStat}.
|
||||
*/
|
||||
public void setStat (Stat.Type type, int value)
|
||||
{
|
||||
IntStat stat = (IntStat)get(type.name());
|
||||
if (stat == null) {
|
||||
stat = (IntStat)type.newStat();
|
||||
stat.setValue(value);
|
||||
addStat(stat);
|
||||
} else if (stat.setValue(value)) {
|
||||
updateStat(stat);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an integer stat to the specified value, if it exceeds our existing recorded value.
|
||||
*
|
||||
* @exception ClassCastException thrown if the registered type of the specified stat is not an
|
||||
* {@link IntStat}.
|
||||
*/
|
||||
public void maxStat (Stat.Type type, int value)
|
||||
{
|
||||
int ovalue = getIntStat(type);
|
||||
if (value > ovalue) {
|
||||
setStat(type, value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Increments an integer statistic in this set.
|
||||
*
|
||||
* @exception ClassCastException thrown if the registered type of the specified stat is not an
|
||||
* {@link IntStat}.
|
||||
*/
|
||||
public void incrementStat (Stat.Type type, int delta)
|
||||
{
|
||||
IntStat stat = (IntStat)get(type.name());
|
||||
if (stat == null) {
|
||||
stat = (IntStat)type.newStat();
|
||||
stat.increment(delta);
|
||||
addStat(stat);
|
||||
} else if (stat.increment(delta)) {
|
||||
updateStat(stat);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends an integer value to an {@link IntArrayStat}.
|
||||
*
|
||||
* @exception ClassCastException thrown if the registered type of the specified stat is not an
|
||||
* {@link IntArrayStat}.
|
||||
*/
|
||||
public void appendStat (Stat.Type type, int value)
|
||||
{
|
||||
IntArrayStat stat = (IntArrayStat)get(type.name());
|
||||
if (stat == null) {
|
||||
stat = (IntArrayStat)type.newStat();
|
||||
stat.appendValue(value);
|
||||
addStat(stat);
|
||||
} else {
|
||||
stat.appendValue(value);
|
||||
updateStat(stat);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a string value to a {@link StringSetStat}.
|
||||
*
|
||||
* @exception ClassCastException thrown if the registered type of the specified stat is not an
|
||||
* {@link StringSetStat}.
|
||||
*/
|
||||
public void addToSetStat (Stat.Type type, String value)
|
||||
{
|
||||
StringSetStat stat = (StringSetStat)get(type.name());
|
||||
if (stat == null) {
|
||||
stat = (StringSetStat)type.newStat();
|
||||
stat.add(value);
|
||||
addStat(stat);
|
||||
} else if (stat.add(value)) {
|
||||
updateStat(stat);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Increments a string value in a {@link StringMapStat}.
|
||||
*
|
||||
* @exception ClassCastException thrown if the registered type of the specified stat is not an
|
||||
* {@link StringMapStat}.
|
||||
*/
|
||||
public void incrementMapStat (Stat.Type type, String value, int amount)
|
||||
{
|
||||
StringMapStat stat = (StringMapStat)get(type.name());
|
||||
if (stat == null) {
|
||||
stat = (StringMapStat)type.newStat();
|
||||
stat.increment(value, amount);
|
||||
addStat(stat);
|
||||
} else if (stat.increment(value, amount)) {
|
||||
updateStat(stat);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current value of the specified integer statistic.
|
||||
*/
|
||||
public int getIntStat (Stat.Type type)
|
||||
{
|
||||
IntStat stat = (IntStat)get(type.name());
|
||||
return (stat == null) ? 0 : stat.getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the maximum value by which the specified integer statistic has ever been
|
||||
* incremented.
|
||||
*/
|
||||
public int getMaxIntStat (Stat.Type type)
|
||||
{
|
||||
MaxIntStat stat = (MaxIntStat)get(type.name());
|
||||
return (stat == null) ? 0 : stat.getMaxValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current value of the specified integer array statistic.
|
||||
*/
|
||||
public int[] getIntArrayStat (Stat.Type type)
|
||||
{
|
||||
IntArrayStat stat = (IntArrayStat)get(type.name());
|
||||
return (stat == null) ? new int[0] : stat.getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the specified {@link StringSetStat} contains the specified value, false
|
||||
* otherwise.
|
||||
*/
|
||||
public boolean containsValue (Stat.Type type, String value)
|
||||
{
|
||||
StringSetStat stat = (StringSetStat)get(type.name());
|
||||
return (stat == null) ? false : stat.contains(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value to which the specified string is mapped in a {@link StringMapStat} or zero
|
||||
* if the value has not been mapped.
|
||||
*/
|
||||
public int getMapValue (Stat.Type type, String value)
|
||||
{
|
||||
StringMapStat stat = (StringMapStat)get(type.name());
|
||||
return (stat == null) ? 0 : stat.get(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Don't call this method, it's only needed by some tricky business we do when preventing
|
||||
* distributed object event generation for all but the bounty criteria related stats.
|
||||
*/
|
||||
public void addQuietly (Stat stat)
|
||||
{
|
||||
add(stat);
|
||||
}
|
||||
|
||||
protected void addStat (Stat stat)
|
||||
{
|
||||
if (_container != null) {
|
||||
_container.addToStats(stat);
|
||||
} else {
|
||||
add(stat);
|
||||
}
|
||||
}
|
||||
|
||||
protected void updateStat (Stat stat)
|
||||
{
|
||||
if (_container != null) {
|
||||
_container.updateStats(stat);
|
||||
}
|
||||
}
|
||||
|
||||
protected transient Container _container;
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
//
|
||||
// $Id$
|
||||
|
||||
package com.threerings.stats.data;
|
||||
|
||||
import com.samskivert.util.ArrayUtil;
|
||||
import com.samskivert.util.StringUtil;
|
||||
|
||||
/**
|
||||
* Used to track a statistic comprised of a set of strings that map to numeric counts.
|
||||
*/
|
||||
public abstract class StringMapStat extends Stat
|
||||
{
|
||||
/**
|
||||
* Returns true if the specified key is contained in this map.
|
||||
*/
|
||||
public boolean containsKey (String key)
|
||||
{
|
||||
return (key == null) ? false :
|
||||
ArrayUtil.binarySearch(_keys, 0, _keys.length, key) >= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value to which the specified key maps or zero if it is not contained in this
|
||||
* map.
|
||||
*/
|
||||
public int get (String key)
|
||||
{
|
||||
if (key == null) {
|
||||
return 0;
|
||||
}
|
||||
int iidx = ArrayUtil.binarySearch(_keys, 0, _keys.length, key);
|
||||
return (iidx < 0) ? 0 : _values[iidx];
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the specified key value pair to this set, overwriting any existing mapping.
|
||||
*
|
||||
* @return true if the stat's value was changed, false otherwise.
|
||||
*/
|
||||
public boolean put (String key, int value)
|
||||
{
|
||||
if (key == null) {
|
||||
return false;
|
||||
}
|
||||
int iidx = getOrCreateEntry(key);
|
||||
int ovalue = _values[iidx];
|
||||
_values[iidx] = Math.min(value, getMaxValue());
|
||||
if (_values[iidx] != ovalue) {
|
||||
_modified = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the specified key value pair to this set, overwriting any existing mapping.
|
||||
*
|
||||
* @return true if the stat's value was changed, false otherwise.
|
||||
*/
|
||||
public boolean increment (String key, int amount)
|
||||
{
|
||||
if (key == null) {
|
||||
return false;
|
||||
}
|
||||
int iidx = getOrCreateEntry(key);
|
||||
int ovalue = _values[iidx];
|
||||
_values[iidx] = Math.min(_values[iidx] + amount, getMaxValue());
|
||||
if (_values[iidx] != ovalue) {
|
||||
_modified = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override // documentation inherited
|
||||
public String valueToString ()
|
||||
{
|
||||
StringBuffer buf = new StringBuffer("[");
|
||||
for (int ii = 0; ii < _keys.length; ii++) {
|
||||
if (ii > 0) {
|
||||
buf.append(", ");
|
||||
}
|
||||
buf.append(_keys[ii]).append("->").append(_values[ii]);
|
||||
}
|
||||
return buf.append("]").toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index of the specified key, creating an entry if necessary.
|
||||
*/
|
||||
protected int getOrCreateEntry (String key)
|
||||
{
|
||||
int iidx = ArrayUtil.binarySearch(_keys, 0, _keys.length, key);
|
||||
if (iidx < 0) {
|
||||
iidx = -iidx - 1;
|
||||
String[] keys = new String[_keys.length+1];
|
||||
System.arraycopy(_keys, 0, keys, 0, iidx);
|
||||
System.arraycopy(_keys, iidx, keys, iidx+1, (_keys.length-iidx));
|
||||
keys[iidx] = key;
|
||||
_keys = keys;
|
||||
int[] values = new int[_values.length+1];
|
||||
System.arraycopy(_values, 0, values, 0, iidx);
|
||||
System.arraycopy(
|
||||
_values, iidx, values, iidx+1, (_values.length-iidx));
|
||||
_values = values;
|
||||
}
|
||||
return iidx;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the maximum value to which we'll allow our strings to map (to avoid overflowing our
|
||||
* underlying data types and to avoid accumulating forever when we don't really care).
|
||||
*/
|
||||
protected abstract int getMaxValue ();
|
||||
|
||||
/** Contains the string keys in this set. */
|
||||
protected String[] _keys = {};
|
||||
|
||||
/** Contains the values in this set. */
|
||||
protected int[] _values = {};
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
//
|
||||
// $Id$
|
||||
|
||||
package com.threerings.stats.data;
|
||||
|
||||
import com.samskivert.util.ArrayUtil;
|
||||
import com.samskivert.util.StringUtil;
|
||||
|
||||
/**
|
||||
* Used to track a statistic comprised of a set of strings.
|
||||
*/
|
||||
public abstract class StringSetStat extends Stat
|
||||
{
|
||||
/**
|
||||
* Returns true if the specified string is contained in this set.
|
||||
*/
|
||||
public boolean contains (String key)
|
||||
{
|
||||
if (key == null) {
|
||||
throw new IllegalArgumentException("StringSetStat cannot contain null");
|
||||
}
|
||||
return ArrayUtil.binarySearch(_values, 0, _values.length, key) >= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the specified string to this set.
|
||||
*
|
||||
* @return true if the string was newly added, false if it was already
|
||||
* contained in the set.
|
||||
*/
|
||||
public boolean add (String key)
|
||||
{
|
||||
if (key == null) {
|
||||
throw new IllegalArgumentException("Cannot add null to StringSetStat");
|
||||
}
|
||||
|
||||
int iidx = ArrayUtil.binarySearch(_values, 0, _values.length, key);
|
||||
if (iidx >= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
iidx = -iidx - 1;
|
||||
String[] values = new String[_values.length+1];
|
||||
System.arraycopy(_values, 0, values, 0, iidx);
|
||||
System.arraycopy(_values, iidx, values, iidx+1, (_values.length-iidx));
|
||||
values[iidx] = key;
|
||||
_values = values;
|
||||
_modified = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the values in this set. <em>Do not</em> modify the returned array.
|
||||
*/
|
||||
public String[] values ()
|
||||
{
|
||||
return _values;
|
||||
}
|
||||
|
||||
@Override // documentation inherited
|
||||
public String valueToString ()
|
||||
{
|
||||
return StringUtil.toString(_values);
|
||||
}
|
||||
|
||||
/** Contains the strings in this set. */
|
||||
protected String[] _values = new String[0];
|
||||
}
|
||||
@@ -0,0 +1,460 @@
|
||||
//
|
||||
// $Id$
|
||||
|
||||
package com.threerings.stats.server.persist;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.Date;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.sql.Timestamp;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import com.samskivert.io.ByteArrayOutInputStream;
|
||||
import com.samskivert.io.PersistenceException;
|
||||
import com.samskivert.jdbc.ConnectionProvider;
|
||||
import com.samskivert.jdbc.DatabaseLiaison;
|
||||
import com.samskivert.jdbc.JDBCUtil;
|
||||
import com.samskivert.jdbc.SimpleRepository;
|
||||
import com.samskivert.util.HashIntMap;
|
||||
import com.samskivert.util.StringUtil;
|
||||
|
||||
import com.threerings.io.ObjectInputStream;
|
||||
import com.threerings.io.ObjectOutputStream;
|
||||
|
||||
import com.threerings.stats.data.Stat;
|
||||
|
||||
import static com.threerings.stats.Log.log;
|
||||
|
||||
/**
|
||||
* Responsible for the persistent storage of per-player statistics.
|
||||
*/
|
||||
public class StatRepository extends SimpleRepository
|
||||
implements Stat.AuxDataSource
|
||||
{
|
||||
/** Used by {@link #processStats}. */
|
||||
public interface Processor
|
||||
{
|
||||
/**
|
||||
* Called on every stat matching the criterion supplied to {@link #processStats}.
|
||||
* <em>Note:</em> do not retain a reference to the supplied {@link Stat} instance as its
|
||||
* contents will be overwritten prior to each call to process.
|
||||
*/
|
||||
public void process (int playerId, String accountName, String handle,
|
||||
Date created, int sessionMinutes, Stat stat);
|
||||
}
|
||||
|
||||
/**
|
||||
* The database identifier used when establishing a database connection. This value being
|
||||
* <code>statdb</code>.
|
||||
*/
|
||||
public static final String STAT_DB_IDENT = "statdb";
|
||||
|
||||
/**
|
||||
* Constructs a new statistics repository with the specified connection provider.
|
||||
*
|
||||
* @param conprov the connection provider via which we will obtain our
|
||||
* database connection.
|
||||
*/
|
||||
public StatRepository (ConnectionProvider conprov)
|
||||
throws PersistenceException
|
||||
{
|
||||
super(conprov, STAT_DB_IDENT);
|
||||
|
||||
// load up our string set mappings
|
||||
loadStringCodes(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the stats associated with the specified player.
|
||||
*/
|
||||
public ArrayList<Stat> loadStats (final int playerId)
|
||||
throws PersistenceException
|
||||
{
|
||||
final ArrayList<Stat> stats = new ArrayList<Stat>();
|
||||
final String query = "select STAT_CODE, STAT_DATA from STATS where PLAYER_ID = " + playerId;
|
||||
execute(new Operation<Object>() {
|
||||
public Object invoke (Connection conn, DatabaseLiaison liaison)
|
||||
throws SQLException, PersistenceException
|
||||
{
|
||||
Statement stmt = conn.createStatement();
|
||||
try {
|
||||
ResultSet rs = stmt.executeQuery(query);
|
||||
while (rs.next()) {
|
||||
Stat stat = decodeStat(
|
||||
rs.getInt(1), (byte[])rs.getObject(2));
|
||||
if (stat != null) {
|
||||
stats.add(stat);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
JDBCUtil.close(stmt);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
return stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes out any of the stats in the supplied array that have been modified since they were
|
||||
* first loaded. Exceptions that occur while writing the stats will be caught and logged.
|
||||
*/
|
||||
public void writeModified (int playerId, Stat[] stats)
|
||||
{
|
||||
for (int ii = 0; ii < stats.length; ii++) {
|
||||
try {
|
||||
if (stats[ii].getType().isPersistent() &&
|
||||
stats[ii].isModified()) {
|
||||
updateStat(playerId, stats[ii]);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.log(Level.WARNING, "Error flushing modified stat [stat=" + stats[ii] + "].", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// documentation inherited from interface Stat.AuxDataSource
|
||||
public int getStringCode (Stat.Type type, String value)
|
||||
{
|
||||
HashMap<String,Integer> map = _stringToCode.get(type);
|
||||
if (map == null) {
|
||||
_stringToCode.put(type, map = new HashMap<String,Integer>());
|
||||
}
|
||||
Integer code = map.get(value);
|
||||
if (code == null) {
|
||||
try {
|
||||
code = assignStringCode(type, value);
|
||||
} catch (PersistenceException pe) {
|
||||
log.log(Level.WARNING, "Failed to assign code [type=" + type +
|
||||
", value=" + value + "].", pe);
|
||||
// at this point the database is probably totally hosed, so we can just punt here,
|
||||
// and assume that this value will never be persisted
|
||||
code = -1;
|
||||
}
|
||||
mapStringCode(type, value, code);
|
||||
}
|
||||
return code;
|
||||
}
|
||||
|
||||
// documentation inherited from interface Stat.AuxDataSource
|
||||
public String getCodeString (Stat.Type type, int code)
|
||||
{
|
||||
HashIntMap<String> map = _codeToString.get(type);
|
||||
String value = (map == null) ? null : map.get(code);
|
||||
if (value == null) {
|
||||
// our value may have been mapped on a different server, so refresh
|
||||
// this mapping table from the database; then try again
|
||||
try {
|
||||
loadStringCodes(type);
|
||||
} catch (PersistenceException pe) {
|
||||
log.log(Level.WARNING, "Failed to reload string codes " +
|
||||
"[type=" + type + ", code=" + code + "].", pe);
|
||||
}
|
||||
map = _codeToString.get(type);
|
||||
value = (map == null) ? null : map.get(code);
|
||||
if (value == null) {
|
||||
log.warning("Missing reverse maping [type=" + type +
|
||||
", code=" + code + "].");
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is only used for testing. Do not call this method.
|
||||
*/
|
||||
public void clearMapping (Stat.Type type, String value)
|
||||
{
|
||||
int ocode = _stringToCode.get(type).remove(value);
|
||||
_codeToString.get(type).remove(ocode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the supplied processor on every stat in the database of the specified type.
|
||||
*
|
||||
* <p><em>Note:</em> the stats database will inevitable be extremely large (one row for every
|
||||
* paying player and one for non-payers less than six months old; millions of rows if the game
|
||||
* is at all successful). Don't call this method willy nilly and the summarized results should
|
||||
* be cached for at least 12 hours. (Stats don't change that frequently in the aggregate.)
|
||||
*/
|
||||
public void processStats (final Processor processor, Stat.Type type)
|
||||
throws PersistenceException
|
||||
{
|
||||
final Stat stat = type.newStat();
|
||||
final String query = "select STATS.PLAYER_ID, ACCOUNT_NAME, HANDLE, CREATED, " +
|
||||
"SESSION_MINUTES, STAT_DATA from STATS, PLAYERS " +
|
||||
"where PLAYERS.PLAYER_ID = STATS.PLAYER_ID and STAT_CODE = " + type.code();
|
||||
execute(new Operation<Object>() {
|
||||
public Object invoke (Connection conn, DatabaseLiaison liaison)
|
||||
throws SQLException, PersistenceException
|
||||
{
|
||||
Statement stmt = conn.createStatement();
|
||||
try {
|
||||
ResultSet rs = stmt.executeQuery(query);
|
||||
while (rs.next()) {
|
||||
if (decodeStat(stat, (byte[])rs.getObject(6)) != null) {
|
||||
processor.process(rs.getInt(1), rs.getString(2), rs.getString(3),
|
||||
rs.getDate(4), rs.getInt(5), stat);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
JDBCUtil.close(stmt);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates the appropriate stat class and decodes the stat from the data.
|
||||
*/
|
||||
protected Stat decodeStat (int statCode, byte[] data)
|
||||
{
|
||||
Stat.Type type = Stat.getType(statCode);
|
||||
if (type == null) {
|
||||
log.warning("Unable to decode stat, unknown type [code=" + statCode + "].");
|
||||
return null;
|
||||
}
|
||||
return decodeStat(type.newStat(), data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates the appropriate stat class and decodes the stat from the data.
|
||||
*/
|
||||
protected Stat decodeStat (Stat stat, byte[] data)
|
||||
{
|
||||
String errmsg = null;
|
||||
Exception error = null;
|
||||
|
||||
try {
|
||||
// decode its contents from the serialized data
|
||||
ByteArrayInputStream bin = new ByteArrayInputStream(data);
|
||||
stat.unpersistFrom(new ObjectInputStream(bin), this);
|
||||
return stat;
|
||||
|
||||
} catch (ClassNotFoundException cnfe) {
|
||||
error = cnfe;
|
||||
errmsg = "Unable to instantiate stat";
|
||||
|
||||
} catch (IOException ioe) {
|
||||
error = ioe;
|
||||
errmsg = "Unable to decode stat";
|
||||
}
|
||||
|
||||
log.log(Level.WARNING, errmsg + " [type=" + stat.getType() + "]", error);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the specified stat in the database, inserting it if necessary.
|
||||
*/
|
||||
protected void updateStat (int playerId, final Stat stat)
|
||||
throws PersistenceException
|
||||
{
|
||||
final String uquery = "update STATS set STAT_DATA = ?" +
|
||||
" where PLAYER_ID = " + playerId + " and STAT_CODE = " + stat.getCode();
|
||||
final String iquery = "insert into STATS (PLAYER_ID, STAT_CODE, " +
|
||||
"STAT_DATA) values (" + playerId + ", " + stat.getCode() + ", ?)";
|
||||
final ByteArrayOutInputStream out = new ByteArrayOutInputStream();
|
||||
try {
|
||||
stat.persistTo(new ObjectOutputStream(out), this);
|
||||
} catch (IOException ioe) {
|
||||
String errmsg = "Error serializing stat " + stat;
|
||||
throw new PersistenceException(errmsg, ioe);
|
||||
}
|
||||
|
||||
// now update (or insert) the flattened data into the database
|
||||
executeUpdate(new Operation<Object>() {
|
||||
public Object invoke (Connection conn, DatabaseLiaison liaison)
|
||||
throws SQLException, PersistenceException
|
||||
{
|
||||
PreparedStatement stmt = conn.prepareStatement(uquery);
|
||||
try {
|
||||
stmt.setBinaryStream(1, out.getInputStream(), out.size());
|
||||
if (stmt.executeUpdate() == 0) {
|
||||
JDBCUtil.close(stmt);
|
||||
stmt = conn.prepareStatement(iquery);
|
||||
stmt.setBinaryStream(1, out.getInputStream(), out.size());
|
||||
JDBCUtil.checkedUpdate(stmt, 1);
|
||||
}
|
||||
return null;
|
||||
} finally {
|
||||
JDBCUtil.close(stmt);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Helper function for {@link #getStringCode}. */
|
||||
protected Integer assignStringCode (final Stat.Type type, final String value)
|
||||
throws PersistenceException
|
||||
{
|
||||
return executeUpdate(new Operation<Integer>() {
|
||||
public Integer invoke (Connection conn, DatabaseLiaison liaison)
|
||||
throws SQLException, PersistenceException
|
||||
{
|
||||
for (int ii = 0; ii < 10; ii++) {
|
||||
try {
|
||||
int code = getNextCode(conn, type);
|
||||
// DEBUG: uncomment this to test code collision
|
||||
// if (ii == 0 && code > 0) {
|
||||
// code = code-1;
|
||||
// }
|
||||
insertStringCode(conn, type, value, code);
|
||||
return code;
|
||||
|
||||
} catch (SQLException sqe) {
|
||||
// if this is not a duplicate row exception, something is booched and we
|
||||
// just fail
|
||||
if (!liaison.isDuplicateRowException(sqe)) {
|
||||
throw sqe;
|
||||
}
|
||||
|
||||
// if it is a duplicate row exception, possibly someone inserted our value
|
||||
// before we could, in which case we can just look up the new mapping
|
||||
int code = getCurrentCode(conn, type, value);
|
||||
if (code != -1) {
|
||||
log.info("Value collision assigning string code [type=" + type +
|
||||
", value=" + value + "].");
|
||||
return code;
|
||||
}
|
||||
|
||||
// otherwise someone used the code we were trying to use and we just need
|
||||
// to loop around and get the next highest code
|
||||
log.info("Code collision assigning string code [type=" + type +
|
||||
", value=" + value + "].");
|
||||
}
|
||||
}
|
||||
throw new SQLException("Unable to assign code after 10 attempts " +
|
||||
"[type=" + type + ", value=" + value + "]");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Helper function for {@link #assignStringCode}. */
|
||||
protected int getNextCode (Connection conn, Stat.Type type)
|
||||
throws SQLException
|
||||
{
|
||||
return getCode(conn, "select MAX(CODE)+1 from STRING_CODES where STAT_CODE = " +
|
||||
type.code());
|
||||
}
|
||||
|
||||
/** Helper function for {@link #assignStringCode}. */
|
||||
protected int getCurrentCode (Connection conn, Stat.Type type, String value)
|
||||
throws SQLException
|
||||
{
|
||||
return getCode(conn, "select CODE from STRING_CODES where STAT_CODE = " + type.code() +
|
||||
" and VALUE = " + JDBCUtil.escape(value));
|
||||
}
|
||||
|
||||
/** Helper function for {@link #getNextCode} and {@link #getCurrentCode}. */
|
||||
protected int getCode (Connection conn, String query)
|
||||
throws SQLException
|
||||
{
|
||||
int code = -1;
|
||||
Statement stmt = conn.createStatement();
|
||||
try {
|
||||
ResultSet rs = stmt.executeQuery(query);
|
||||
while (rs.next()) {
|
||||
code = rs.getInt(1);
|
||||
}
|
||||
} finally {
|
||||
JDBCUtil.close(stmt);
|
||||
}
|
||||
return code;
|
||||
}
|
||||
|
||||
/** Helper function for {@link #assignStringCode}. */
|
||||
protected void insertStringCode (Connection conn, Stat.Type type, String value, int code)
|
||||
throws SQLException
|
||||
{
|
||||
String query = "insert into STRING_CODES (STAT_CODE, VALUE, CODE) values(" + type.code() +
|
||||
", " + JDBCUtil.escape(value) + ", " + code + ")";
|
||||
Statement stmt = conn.createStatement();
|
||||
try {
|
||||
int mods = stmt.executeUpdate(query);
|
||||
if (mods != 1) {
|
||||
throw new SQLException("Insertion failed to modify one row [type=" + type +
|
||||
", value=" + value + ", code=" + code +
|
||||
", mods=" + mods + "]");
|
||||
}
|
||||
} finally {
|
||||
JDBCUtil.close(stmt);
|
||||
}
|
||||
}
|
||||
|
||||
/** Helper function used at repository startup. */
|
||||
protected void loadStringCodes (Stat.Type type)
|
||||
throws PersistenceException
|
||||
{
|
||||
final String query = "select STAT_CODE, VALUE, CODE from STRING_CODES " +
|
||||
((type == null) ? "" : (" where STAT_CODE = " + type.code()));
|
||||
execute(new Operation<Object>() {
|
||||
public Object invoke (Connection conn, DatabaseLiaison liaison)
|
||||
throws SQLException, PersistenceException
|
||||
{
|
||||
Statement stmt = conn.createStatement();
|
||||
try {
|
||||
ResultSet rs = stmt.executeQuery(query);
|
||||
while (rs.next()) {
|
||||
mapStringCode(Stat.getType(rs.getInt(1)), rs.getString(2), rs.getInt(3));
|
||||
}
|
||||
} finally {
|
||||
JDBCUtil.close(stmt);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Helper function used at repository startup. */
|
||||
protected void mapStringCode (Stat.Type type, String value, int code)
|
||||
{
|
||||
HashMap<String,Integer> fmap = _stringToCode.get(type);
|
||||
if (fmap == null) {
|
||||
_stringToCode.put(type, fmap = new HashMap<String,Integer>());
|
||||
}
|
||||
fmap.put(value, code);
|
||||
HashIntMap<String> rmap = _codeToString.get(type);
|
||||
if (rmap == null) {
|
||||
_codeToString.put(type, rmap = new HashIntMap<String>());
|
||||
}
|
||||
rmap.put(code, value);
|
||||
}
|
||||
|
||||
@Override // documentation inherited
|
||||
protected void migrateSchema (Connection conn, DatabaseLiaison liaison)
|
||||
throws SQLException, PersistenceException
|
||||
{
|
||||
JDBCUtil.createTableIfMissing(conn, "STATS", new String[] {
|
||||
"PLAYER_ID INTEGER NOT NULL",
|
||||
"STAT_CODE INTEGER NOT NULL",
|
||||
"STAT_DATA BLOB NOT NULL",
|
||||
"KEY (PLAYER_ID)",
|
||||
"KEY (STAT_CODE)",
|
||||
}, "");
|
||||
|
||||
JDBCUtil.createTableIfMissing(conn, "STRING_CODES", new String[] {
|
||||
"STAT_CODE INTEGER NOT NULL",
|
||||
"VALUE VARCHAR(255) NOT NULL",
|
||||
"CODE INTEGER NOT NULL",
|
||||
"UNIQUE (STAT_CODE, VALUE)",
|
||||
"UNIQUE (STAT_CODE, CODE)",
|
||||
}, "");
|
||||
}
|
||||
|
||||
protected HashMap<Stat.Type,HashMap<String,Integer>> _stringToCode =
|
||||
new HashMap<Stat.Type,HashMap<String,Integer>>();
|
||||
protected HashMap<Stat.Type,HashIntMap<String>> _codeToString =
|
||||
new HashMap<Stat.Type,HashIntMap<String>>();
|
||||
}
|
||||
Reference in New Issue
Block a user