package com.positive.charts.data;
import java.io.Serializable;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import com.positive.charts.common.SortOrder;
import com.positive.charts.common.UnknownKeyException;
import com.positive.charts.data.util.KeyedValueComparator;
import com.positive.charts.data.util.KeyedValueComparatorType;
import com.positive.charts.data.util.ObjectUtilities;
/**
* An ordered list of (key, value) items. This class provides a default
* implementation of the {@link KeyedValues} interface.
*/
public class DefaultKeyedValues implements KeyedValues, Cloneable, Serializable {
/** For serialization. */
private static final long serialVersionUID = 8468154364608194797L;
/** Storage for the data. */
private List data;
private final Map key2Index;
/**
* Creates a new collection (initially empty).
*/
public DefaultKeyedValues() {
this.data = new java.util.ArrayList();
this.key2Index = new LinkedHashMap();
}
/**
* Updates an existing value, or adds a new value to the collection.
*
* @param key
* the key (<code>null</code> not permitted).
* @param value
* the value.
*/
public void addValue(final Comparable key, final double value) {
this.addValue(key, new Double(value));
}
/**
* Adds a new value to the collection, or updates an existing value. This
* method passes control directly to the
* {@link #setValue(Comparable, Number)} method.
*
* @param key
* the key (<code>null</code> not permitted).
* @param value
* the value (<code>null</code> permitted).
*/
public void addValue(final Comparable key, final Number value) {
this.setValue(key, value);
}
/**
* Clears all values from the collection.
*
* @since 1.0.2
*/
public void clear() {
this.data.clear();
this.key2Index.clear();
}
/**
* Returns a clone.
*
* @return A clone.
*
* @throws CloneNotSupportedException
* this class will not throw this exception, but subclasses
* might.
*/
public Object clone() throws CloneNotSupportedException {
final DefaultKeyedValues clone = (DefaultKeyedValues) super.clone();
clone.data = (List) ObjectUtilities.deepClone(this.data);
return clone;
}
/**
* Tests if this object is equal to another.
*
* @param obj
* the object (<code>null</code> permitted).
*
* @return A boolean.
*/
public boolean equals(final Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof KeyedValues)) {
return false;
}
final KeyedValues that = (KeyedValues) obj;
final int count = this.getItemCount();
if (count != that.getItemCount()) {
return false;
}
for (int i = 0; i < count; i++) {
final Comparable k1 = this.getKey(i);
final Comparable k2 = that.getKey(i);
if (!k1.equals(k2)) {
return false;
}
final Number v1 = this.getValue(i);
final Number v2 = that.getValue(i);
if (v1 == null) {
if (v2 != null) {
return false;
}
} else {
if (!v1.equals(v2)) {
return false;
}
}
}
return true;
}
/**
* Returns the index for a given key.
*
* @param key
* the key (<code>null</code> not permitted).
*
* @return The index, or <code>-1</code> if the key is not recognised.
*
* @throws IllegalArgumentException
* if <code>key</code> is <code>null</code>.
*/
public int getIndex(final Comparable key) {
if (key == null) {
throw new IllegalArgumentException("Null 'key' argument.");
}
int i = -1;
final Integer nI = (Integer) this.key2Index.get(key);
if (nI != null) {
i = nI.intValue();
}
return i;
// int i = 0;
// Iterator iterator = this.data.iterator();
// while (iterator.hasNext()) {
// KeyedValue kv = (KeyedValue) iterator.next();
// if (kv.getKey().equals(key)) {
// return i;
// }
// i++;
// }
// return -1; // key not found
}
/**
* Returns the number of items (values) in the collection.
*
* @return The item count.
*/
public int getItemCount() {
return this.data.size();
}
/**
* Returns a key.
*
* @param index
* the item index (zero-based).
*
* @return The row key.
*
* @throws IndexOutOfBoundsException
* if <code>item</code> is out of bounds.
*/
public Comparable getKey(final int index) {
Comparable result = null;
final KeyedValue item = (KeyedValue) this.data.get(index);
if (item != null) {
result = item.getKey();
}
return result;
}
/**
* Returns the keys for the values in the collection.
*
* @return The keys (never <code>null</code>).
*/
public List getKeys() {
final List result = new java.util.ArrayList();
for (final Iterator iter = this.data.iterator(); iter.hasNext();) {
final KeyedValue kv = (KeyedValue) iter.next();
result.add(kv.getKey());
}
return result;
}
/**
* Returns the value for a given key.
*
* @param key
* the key.
*
* @return The value (possibly <code>null</code>).
*
* @throws UnknownKeyException
* if the key is not recognised.
*/
public Number getValue(final Comparable key) {
final int index = this.getIndex(key);
if (index < 0) {
throw new UnknownKeyException("Key not found: " + key);
}
return this.getValue(index);
}
/**
* Returns a value.
*
* @param item
* the item of interest (zero-based index).
*
* @return The value.
*
* @throws IndexOutOfBoundsException
* if <code>item</code> is out of bounds.
*/
public Number getValue(final int item) {
Number result = null;
final KeyedValue kval = (KeyedValue) this.data.get(item);
if (kval != null) {
result = kval.getValue();
}
return result;
}
/**
* Returns a hash code.
*
* @return A hash code.
*/
public int hashCode() {
return (this.data != null ? this.data.hashCode() : 0);
}
protected void rebuildKey2Index() {
int i = 0;
final Iterator iterator = this.data.iterator();
while (iterator.hasNext()) {
final KeyedValue kv = (KeyedValue) iterator.next();
this.key2Index.put(kv.getKey(), new Integer(i));
i++;
}
}
/**
* Removes a value from the collection. If there is no item with the
* specified key, this method does nothing.
*
* @param key
* the item key (<code>null</code> not permitted).
*
* @throws IllegalArgumentException
* if <code>key</code> is <code>null</code>.
*/
public void removeValue(final Comparable key) {
final int index = this.getIndex(key);
if (index >= 0) {
this.removeValue(index);
}
}
/**
* Removes a value from the collection.
*
* @param index
* the index of the item to remove (in the range <code>0</code>
* to <code>getItemCount() - 1</code>).
*
* @throws IndexOutOfBoundsException
* if <code>index</code> is not within the specified range.
*/
public void removeValue(final int index) {
final DefaultKeyedValue kv = (DefaultKeyedValue) this.data.get(index);
this.key2Index.remove(kv.getKey());
this.data.remove(index);
}
/**
* Updates an existing value, or adds a new value to the collection.
*
* @param key
* the key (<code>null</code> not permitted).
* @param value
* the value.
*/
public void setValue(final Comparable key, final double value) {
this.setValue(key, new Double(value));
}
/**
* Updates an existing value, or adds a new value to the collection.
*
* @param key
* the key (<code>null</code> not permitted).
* @param value
* the value (<code>null</code> permitted).
*/
public void setValue(final Comparable key, final Number value) {
if (key == null) {
throw new IllegalArgumentException("Null 'key' argument.");
}
final int keyIndex = this.getIndex(key);
if (keyIndex >= 0) {
final DefaultKeyedValue kv = (DefaultKeyedValue) this.data
.get(keyIndex);
kv.setValue(value);
} else {
final KeyedValue kv = new DefaultKeyedValue(key, value);
this.data.add(kv);
final int index = this.data.size() - 1;
this.key2Index.put(key, new Integer(index));
}
}
/**
* Sorts the items in the list by key.
*
* @param order
* the sort order (<code>null</code> not permitted).
*/
public void sortByKeys(final SortOrder order) {
final Comparator comparator = new KeyedValueComparator(
KeyedValueComparatorType.BY_KEY, order);
Collections.sort(this.data, comparator);
this.rebuildKey2Index();
}
/**
* Sorts the items in the list by value. If the list contains
* <code>null</code> values, they will sort to the end of the list,
* irrespective of the sort order.
*
* @param order
* the sort order (<code>null</code> not permitted).
*/
public void sortByValues(final SortOrder order) {
final Comparator comparator = new KeyedValueComparator(
KeyedValueComparatorType.BY_VALUE, order);
Collections.sort(this.data, comparator);
this.rebuildKey2Index();
}
}