--- /dev/null
+package jalview.util;
+
+import jalview.ext.android.SparseIntArray;
+import jalview.ext.android.SparseShortArray;
+
+/**
+ * A class to count occurrences of characters with minimal memory footprint.
+ * Sparse arrays of short values are used to hold the counts, with automatic
+ * promotion to arrays of int if any count exceeds the maximum value for a
+ * short.
+ *
+ * @author gmcarstairs
+ *
+ */
+public class SparseCount
+{
+ private static final int DEFAULT_PROFILE_SIZE = 2;
+
+ /*
+ * array of keys (chars) and values (counts)
+ * held either as shorts or (if shorts overflow) as ints
+ */
+ private SparseShortArray shortProfile;
+
+ private SparseIntArray intProfile;
+
+ /*
+ * flag is set true after short overflow occurs
+ */
+ private boolean useInts;
+
+ /**
+ * Constructor which initially creates a new sparse array of short values to
+ * hold counts.
+ *
+ * @param profileSize
+ */
+ public SparseCount(int profileSize)
+ {
+ this.shortProfile = new SparseShortArray(profileSize);
+ }
+
+ /**
+ * Constructor which allocates an initial count array for only two distinct
+ * values (the array will grow if needed)
+ */
+ public SparseCount()
+ {
+ this(DEFAULT_PROFILE_SIZE);
+ }
+
+ /**
+ * Adds the given value for the given key (or sets the initial value), and
+ * returns the new value
+ *
+ * @param key
+ * @param value
+ */
+ public int add(int key, int value)
+ {
+ int newValue = 0;
+ if (useInts)
+ {
+ newValue = intProfile.add(key, value);
+ }
+ else
+ {
+ try {
+ newValue = shortProfile.add(key, value);
+ } catch (ArithmeticException e) {
+ handleOverflow();
+ newValue = intProfile.add(key, value);
+ }
+ }
+ return newValue;
+ }
+
+ /**
+ * Switch from counting shorts to counting ints
+ */
+ synchronized void handleOverflow()
+ {
+ int size = shortProfile.size();
+ intProfile = new SparseIntArray(size);
+ for (int i = 0; i < size; i++)
+ {
+ short key = shortProfile.keyAt(i);
+ short value = shortProfile.valueAt(i);
+ intProfile.put(key, value);
+ }
+ shortProfile = null;
+ useInts = true;
+ }
+
+ /**
+ * Returns the size of the profile (number of distinct items counted)
+ *
+ * @return
+ */
+ public int size()
+ {
+ return useInts ? intProfile.size() : shortProfile.size();
+ }
+
+ /**
+ * Returns the value for the key (zero if no such key)
+ *
+ * @param key
+ * @return
+ */
+ public int get(int key)
+ {
+ return useInts ? intProfile.get(key) : shortProfile.get(key);
+ }
+
+ /**
+ * Sets the value for the given key
+ *
+ * @param key
+ * @param value
+ */
+ public void put(int key, int value)
+ {
+ if (useInts)
+ {
+ intProfile.put(key, value);
+ }
+ else
+ {
+ shortProfile.put(key, value);
+ }
+ }
+
+ public int keyAt(int k)
+ {
+ return useInts ? intProfile.keyAt(k) : shortProfile.keyAt(k);
+ }
+
+ public int valueAt(int k)
+ {
+ return useInts ? intProfile.valueAt(k) : shortProfile.valueAt(k);
+ }
+
+ /**
+ * Answers true if this object wraps arrays of int values, false if using
+ * short values
+ *
+ * @return
+ */
+ boolean isUsingInt()
+ {
+ return useInts;
+ }
+}