Issue #2242 Optimize FFMP NavigableMap memory.

Former-commit-id: f0ceddacf1 [formerly 5208c63e9c6eab03bc42736ffe2254fb802d566b]
Former-commit-id: b06171b4f2
This commit is contained in:
Ben Steffensmeier 2013-08-01 12:48:50 -05:00 committed by Greg Armendariz
parent b346666ef1
commit 3eb869c65e
5 changed files with 843 additions and 130 deletions

View file

@ -28,6 +28,8 @@ import java.util.TreeMap;
import javax.persistence.Transient; import javax.persistence.Transient;
import com.raytheon.uf.common.dataplugin.ffmp.collections.ArrayBackedMap;
import com.raytheon.uf.common.dataplugin.ffmp.collections.BasinMapFactory;
import com.raytheon.uf.common.serialization.ISerializableObject; import com.raytheon.uf.common.serialization.ISerializableObject;
import com.raytheon.uf.common.serialization.annotations.DynamicSerialize; import com.raytheon.uf.common.serialization.annotations.DynamicSerialize;
import com.raytheon.uf.common.serialization.annotations.DynamicSerializeElement; import com.raytheon.uf.common.serialization.annotations.DynamicSerializeElement;
@ -48,6 +50,7 @@ import com.raytheon.uf.common.serialization.annotations.DynamicSerializeElement;
* TreeMap creation to the tertiary loader. * TreeMap creation to the tertiary loader.
* Apr 26, 2013 1954 bsteffen Minor code cleanup throughout FFMP. * Apr 26, 2013 1954 bsteffen Minor code cleanup throughout FFMP.
* Jul 15, 2013 2184 dhladky Remove all HUC's for storage except ALL * Jul 15, 2013 2184 dhladky Remove all HUC's for storage except ALL
* Jul 31, 2013 2242 bsteffen Optimize FFMP NavigableMap memory.
* *
* </pre> * </pre>
* *
@ -71,6 +74,13 @@ public class FFMPBasin implements ISerializableObject, Cloneable {
@Transient @Transient
protected NavigableMap<Date, Float> values; protected NavigableMap<Date, Float> values;
/**
* Set to either the values map or the BasinMapFactory that was used to
* create it to enable correct synchronization.
*/
@Transient
protected Object valuesSynchronization;
/** object used for serialization **/ /** object used for serialization **/
@DynamicSerializeElement @DynamicSerializeElement
public float[] serializedValues; public float[] serializedValues;
@ -172,9 +182,9 @@ public class FFMPBasin implements ISerializableObject, Cloneable {
Date prevDate = null; Date prevDate = null;
// map ordered newest first, so grab from newest date to oldest date // map ordered newest first, so grab from newest date to oldest date
if (afterDate.before(beforeDate) && (values.size() > 0)) { if (afterDate.before(beforeDate) && (!values.isEmpty())) {
synchronized (values) { synchronized (valuesSynchronization) {
float factor = 0.0f; float factor = 0.0f;
@ -226,7 +236,7 @@ public class FFMPBasin implements ISerializableObject, Cloneable {
*/ */
public Float getValue(Date afterDate, Date beforeDate) { public Float getValue(Date afterDate, Date beforeDate) {
Float val = 0.0f; Float val = 0.0f;
synchronized (values) { synchronized (valuesSynchronization) {
Date checkDate = values.ceilingKey(afterDate); Date checkDate = values.ceilingKey(afterDate);
if ((checkDate != null) && checkDate.before(beforeDate)) { if ((checkDate != null) && checkDate.before(beforeDate)) {
val = values.get(checkDate); val = values.get(checkDate);
@ -260,7 +270,7 @@ public class FFMPBasin implements ISerializableObject, Cloneable {
Float val = 0.0f; Float val = 0.0f;
int i = 0; int i = 0;
synchronized (values) { synchronized (valuesSynchronization) {
for (Date date : values.keySet()) { for (Date date : values.keySet()) {
if (date.before(beforeDate) && date.after(afterDate)) { if (date.before(beforeDate) && date.after(afterDate)) {
@ -290,7 +300,7 @@ public class FFMPBasin implements ISerializableObject, Cloneable {
public Float getMaxValue(Date afterDate, Date beforeDate) { public Float getMaxValue(Date afterDate, Date beforeDate) {
Float val = 0.0f; Float val = 0.0f;
synchronized (values) { synchronized (valuesSynchronization) {
for (Date date : values.keySet()) { for (Date date : values.keySet()) {
if (date.before(beforeDate) && date.after(afterDate)) { if (date.before(beforeDate) && date.after(afterDate)) {
@ -312,13 +322,7 @@ public class FFMPBasin implements ISerializableObject, Cloneable {
* @param value * @param value
*/ */
public void setValue(Date date, Float dvalue) { public void setValue(Date date, Float dvalue) {
synchronized (values) { synchronized (valuesSynchronization) {
if (!(values instanceof TreeMap) && !values.containsKey(dvalue)) {
// ArrayBackedMap may have been used if this basin was
// deserialized. It is much faster to do inserts on a TreeMap so
// convert now.
values = new TreeMap<Date, Float>(values);
}
values.put(date, dvalue); values.put(date, dvalue);
} }
} }
@ -339,6 +343,7 @@ public class FFMPBasin implements ISerializableObject, Cloneable {
*/ */
public void setValues(TreeMap<Date, Float> values) { public void setValues(TreeMap<Date, Float> values) {
this.values = values; this.values = values;
this.valuesSynchronization = values;
} }
/** /**
@ -347,6 +352,7 @@ public class FFMPBasin implements ISerializableObject, Cloneable {
public FFMPBasin() { public FFMPBasin() {
values = new TreeMap<Date, Float>(Collections.reverseOrder()); values = new TreeMap<Date, Float>(Collections.reverseOrder());
valuesSynchronization = values;
} }
/** /**
@ -358,23 +364,32 @@ public class FFMPBasin implements ISerializableObject, Cloneable {
setPfaf(pfaf); setPfaf(pfaf);
setAggregated(aggregated); setAggregated(aggregated);
values = new TreeMap<Date, Float>(Collections.reverseOrder()); values = new TreeMap<Date, Float>(Collections.reverseOrder());
valuesSynchronization = values;
} }
public FFMPBasin(Long pfaf, boolean aggregated,
BasinMapFactory<Date> mapFactory) {
setPfaf(pfaf);
setAggregated(aggregated);
values = mapFactory.getMap();
valuesSynchronization = mapFactory;
}
/** /**
* Populates the map from the serialized values * Populates the map from the serialized values
* *
* @param times * @param times
*/ */
public void deserialize(long[] times) { public void deserialize(long[] times, BasinMapFactory<Date> mapFactory) {
// safe to avoid Array Index Exceptions / shouldn't happen but..... // safe to avoid Array Index Exceptions / shouldn't happen but.....
if (serializedValues != null if (serializedValues != null
&& (times.length == serializedValues.length)) { && (times.length == serializedValues.length)) {
NavigableMap<Date, Float> fastMap = new ArrayBackedMap(times, NavigableMap<Date, Float> fastMap = new ArrayBackedMap(times,
serializedValues); serializedValues);
values = fastMap.descendingMap();
// values = new TreeMap<Date, Float>(fastMap.descendingMap()); values = mapFactory.getMap(fastMap.descendingMap());
valuesSynchronization = mapFactory;
} }
serializedValues = null; serializedValues = null;
} }
@ -401,7 +416,7 @@ public class FFMPBasin implements ISerializableObject, Cloneable {
*/ */
public void purgeData(Date date) { public void purgeData(Date date) {
if (values != null) { if (values != null) {
synchronized (values) { synchronized (valuesSynchronization) {
ArrayList<Date> removes = new ArrayList<Date>(); ArrayList<Date> removes = new ArrayList<Date>();
for (Date mdate : values.keySet()) { for (Date mdate : values.keySet()) {
if (mdate.before(date)) { if (mdate.before(date)) {

View file

@ -22,6 +22,7 @@ package com.raytheon.uf.common.dataplugin.ffmp;
import java.io.File; import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
@ -29,6 +30,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import com.raytheon.uf.common.dataplugin.ffmp.FFMPDataRecordLoader.LoadTask; import com.raytheon.uf.common.dataplugin.ffmp.FFMPDataRecordLoader.LoadTask;
import com.raytheon.uf.common.dataplugin.ffmp.collections.BasinMapFactory;
import com.raytheon.uf.common.datastorage.DataStoreFactory; import com.raytheon.uf.common.datastorage.DataStoreFactory;
import com.raytheon.uf.common.datastorage.records.FloatDataRecord; import com.raytheon.uf.common.datastorage.records.FloatDataRecord;
import com.raytheon.uf.common.monitor.config.FFMPSourceConfigurationManager; import com.raytheon.uf.common.monitor.config.FFMPSourceConfigurationManager;
@ -54,6 +56,9 @@ import com.raytheon.uf.common.serialization.annotations.DynamicSerializeElement;
* 05/09/13 1919 mpduff Use parent pfaf instead of lookupId. * 05/09/13 1919 mpduff Use parent pfaf instead of lookupId.
* 07/09/13 2152 njensen Ensure purgeData() does not load data * 07/09/13 2152 njensen Ensure purgeData() does not load data
* Jul 15, 2013 2184 dhladky Remove all HUC's for storage except ALL * Jul 15, 2013 2184 dhladky Remove all HUC's for storage except ALL
* 07/16/13 2197 njensen Added hasAnyBasins() and moved getBasins() calls out of loops
* Jul 31, 2013 2242 bsteffen Optimize FFMP NavigableMap memory.
*
* *
* </pre> * </pre>
* *
@ -82,6 +87,20 @@ public class FFMPBasinData implements ISerializableObject {
*/ */
private final Map<String, FFMPBasin[]> orderedBasinsCache = new HashMap<String, FFMPBasin[]>(); private final Map<String, FFMPBasin[]> orderedBasinsCache = new HashMap<String, FFMPBasin[]>();
/**
* Shared factory for efficient storage of data in basins.
*/
private BasinMapFactory<Date> mapFactory = null;
/**
* Public one arg constructor
*
* @param huc_level
*/
public FFMPBasinData(String hucLevel) {
setHucLevel(hucLevel);
}
/** /**
* No arg hibernate constructor * No arg hibernate constructor
*/ */
@ -666,13 +685,18 @@ public class FFMPBasinData implements ISerializableObject {
* @param times * @param times
*/ */
public void populate(List<Long> times) { public void populate(List<Long> times) {
if (mapFactory == null) {
mapFactory = new BasinMapFactory<Date>(Collections.reverseOrder(),
getBasins().size());
}
long[] timesArr = new long[times.size()]; long[] timesArr = new long[times.size()];
for (int i = 0; i < timesArr.length; i += 1) { for (int i = 0; i < timesArr.length; i += 1) {
timesArr[i] = times.get(i); timesArr[i] = times.get(i);
} }
for (FFMPBasin basin : getBasins().values()) { for (FFMPBasin basin : getBasins().values()) {
basin.deserialize(timesArr); basin.deserialize(timesArr, mapFactory);
} }
} }
@ -730,7 +754,12 @@ public class FFMPBasinData implements ISerializableObject {
if (guidance) { if (guidance) {
basin = new FFMPGuidanceBasin(pfaf, aggregate); basin = new FFMPGuidanceBasin(pfaf, aggregate);
} else { } else {
basin = new FFMPBasin(pfaf, aggregate); if (mapFactory == null) {
mapFactory = new BasinMapFactory<Date>(
Collections.reverseOrder(),
orderedPfafs.size());
}
basin = new FFMPBasin(pfaf, aggregate, mapFactory);
} }
this.basins.put(pfaf, basin); this.basins.put(pfaf, basin);
} }

View file

@ -17,7 +17,7 @@
* See the AWIPS II Master Rights File ("Master Rights File.pdf") for * See the AWIPS II Master Rights File ("Master Rights File.pdf") for
* further licensing information. * further licensing information.
**/ **/
package com.raytheon.uf.common.dataplugin.ffmp; package com.raytheon.uf.common.dataplugin.ffmp.collections;
import java.util.AbstractMap; import java.util.AbstractMap;
import java.util.AbstractSet; import java.util.AbstractSet;
@ -34,7 +34,6 @@ import java.util.NavigableSet;
import java.util.NoSuchElementException; import java.util.NoSuchElementException;
import java.util.Set; import java.util.Set;
import java.util.SortedMap; import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeMap; import java.util.TreeMap;
/** /**
@ -66,7 +65,9 @@ import java.util.TreeMap;
* *
* Date Ticket# Engineer Description * Date Ticket# Engineer Description
* ------------ ---------- ----------- -------------------------- * ------------ ---------- ----------- --------------------------
* Apr 18, 2013 bsteffen Initial creation * Apr 18, 2013 bsteffen Initial creation
* Jul 31, 2013 2242 bsteffen Extracted NavigableKeySet to its own
* file and moved to collections package.
* *
* </pre> * </pre>
* *
@ -264,7 +265,7 @@ public class ArrayBackedMap extends AbstractMap<Date, Float> implements
@Override @Override
public NavigableSet<Date> navigableKeySet() { public NavigableSet<Date> navigableKeySet() {
return new NavigableKeySet(this); return new NavigableKeySet<Date>(this);
} }
private int lowerIndex(Date key, boolean inclusive) { private int lowerIndex(Date key, boolean inclusive) {
@ -592,113 +593,7 @@ public class ArrayBackedMap extends AbstractMap<Date, Float> implements
} }
private static class NavigableKeySet extends AbstractSet<Date> implements
NavigableSet<Date> {
private final NavigableMap<Date, Float> map;
public NavigableKeySet(NavigableMap<Date, Float> map) {
this.map = map;
}
@Override
public Iterator<Date> iterator() {
return map.keySet().iterator();
}
@Override
public int size() {
return map.size();
}
@Override
public Comparator<? super Date> comparator() {
return map.comparator();
}
@Override
public Date first() {
return map.firstKey();
}
@Override
public Date last() {
return map.lastKey();
}
@Override
public Date lower(Date e) {
return map.lowerKey(e);
}
@Override
public Date floor(Date e) {
return map.floorKey(e);
}
@Override
public Date ceiling(Date e) {
return map.ceilingKey(e);
}
@Override
public Date higher(Date e) {
return map.higherKey(e);
}
@Override
public Date pollFirst() {
return map.pollFirstEntry().getKey();
}
@Override
public Date pollLast() {
return map.pollLastEntry().getKey();
}
@Override
public NavigableSet<Date> descendingSet() {
return map.descendingKeySet();
}
@Override
public Iterator<Date> descendingIterator() {
return descendingSet().iterator();
}
@Override
public NavigableSet<Date> subSet(Date fromElement,
boolean fromInclusive, Date toElement, boolean toInclusive) {
return map.subMap(fromElement, fromInclusive, toElement,
toInclusive).navigableKeySet();
}
@Override
public NavigableSet<Date> headSet(Date toElement, boolean inclusive) {
return map.headMap(toElement, inclusive).navigableKeySet();
}
@Override
public NavigableSet<Date> tailSet(Date fromElement, boolean inclusive) {
return map.tailMap(fromElement, inclusive).navigableKeySet();
}
@Override
public SortedSet<Date> subSet(Date fromElement, Date toElement) {
return subSet(fromElement, true, toElement, false);
}
@Override
public SortedSet<Date> headSet(Date toElement) {
return headSet(toElement, false);
}
@Override
public SortedSet<Date> tailSet(Date fromElement) {
return tailSet(fromElement, true);
}
}
private class SubMap extends AbstractMap<Date, Float> implements private class SubMap extends AbstractMap<Date, Float> implements
NavigableMap<Date, Float> { NavigableMap<Date, Float> {
@ -854,7 +749,7 @@ public class ArrayBackedMap extends AbstractMap<Date, Float> implements
@Override @Override
public NavigableSet<Date> navigableKeySet() { public NavigableSet<Date> navigableKeySet() {
return new NavigableKeySet(this); return new NavigableKeySet<Date>(this);
} }
@Override @Override
@ -1066,7 +961,7 @@ public class ArrayBackedMap extends AbstractMap<Date, Float> implements
@Override @Override
public NavigableSet<Date> navigableKeySet() { public NavigableSet<Date> navigableKeySet() {
return new NavigableKeySet(this); return new NavigableKeySet<Date>(this);
} }
@Override @Override

View file

@ -0,0 +1,621 @@
/**
* This software was developed and / or modified by Raytheon Company,
* pursuant to Contract DG133W-05-CQ-1067 with the US Government.
*
* U.S. EXPORT CONTROLLED TECHNICAL DATA
* This software product contains export-restricted data whose
* export/transfer/disclosure is restricted by U.S. law. Dissemination
* to non-U.S. persons whether in the United States or abroad requires
* an export license or other authorization.
*
* Contractor Name: Raytheon Company
* Contractor Address: 6825 Pine Street, Suite 340
* Mail Stop B8
* Omaha, NE 68106
* 402.291.0100
*
* See the AWIPS II Master Rights File ("Master Rights File.pdf") for
* further licensing information.
**/
package com.raytheon.uf.common.dataplugin.ffmp.collections;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Comparator;
import java.util.Iterator;
import java.util.Map.Entry;
import java.util.NavigableMap;
import java.util.NavigableSet;
import java.util.Set;
import java.util.TreeMap;
/**
* Factory for constructing maps to use in FFMPBasins for organizing values.
*
* Each basin in FFMP requires a NavigableMap<Date,Float> in order to achieve
* fast dynamic calculations. Unfortunately due to the large number of basins
* TreeMaps require too much memory. This factory produces maps that have the
* same performance as TreeMaps but are much more memory efficient.
*
* This factory achieves efficiency by taking advantage of the fact that all
* basins will have data for the same times, so it can use a single TreeMap as
* the backing map for all basins. Each NavigableMap returned from this factory
* is simply a view into the backing map.
*
* Because all maps returned from this factory use the same backing map, they
* cannot be synchronized individually. Internally all map modifications are
* synchronized on the factory object to prevent concurrent modification. If any
* iterators are being used from any maps then the iteration should also be
* synchronized on the factory.
*
* <pre>
*
* SOFTWARE HISTORY
*
* Date Ticket# Engineer Description
* ------------ ---------- ----------- --------------------------
* Jul 30, 2013 2242 bsteffen Initial creation
*
* </pre>
*
* @author bsteffen
* @version 1.0
*/
public class BasinMapFactory<K> {
/*
* How much to grow the backing arrays to make room for new maps, this is
* much more conservative than the algorithms used by ArrayList or HashMap
* because typically growth will occur very briefly during initialization
* and the extra overhead of a slower growth rate is minimal. Once all
* basins are created, growth will stop and a conservative growth algorithm
* limits wasted memory after the initial growth phase.
*/
private static final int GROWTH_RATE = 32;
/* The amount of space allocated in each value. */
protected volatile int allocated_size;
/* The number of indices used in maps so far. */
private volatile int used_size;
/* This is the real navigable map, responsible for all real work. */
private final NavigableMap<K, MultiValue> backingMap;
/**
* Create a new factory
*
* @param comparator
* the comparator to use for map keys
* @param initialSize
* the amount of space to preallocate for basin maps.
*/
public BasinMapFactory(Comparator<? super K> comparator, int initialSize) {
allocated_size = initialSize;
used_size = 0;
this.backingMap = new TreeMap<K, MultiValue>(comparator);
}
/**
* Get a new map from this factory.
*
* @return
*/
public NavigableMap<K, Float> getMap() {
return new MapView<K>(this, getNextIndex(),
backingMap);
}
/**
* Get a new map from this factory, populated with all the values from m. If
* m has the same keyset as other maps in this factory than this will
* populate the new map faster than putAll.
*
* @param m
* NavigableMap, must have the same comparator as this factory.
* @return
*/
public NavigableMap<K, Float> getMap(NavigableMap<K, Float> m) {
Comparator<? super K> bc = backingMap.comparator();
Comparator<? super K> mc = m.comparator();
if (mc != bc && (mc == null || !mc.equals(bc))) {
throw new IllegalArgumentException(
"Maps can only be constructed if the compators are the same.");
}
int index = getNextIndex();
Iterator<Entry<K, MultiValue>> bit = backingMap.entrySet().iterator();
Iterator<Entry<K, Float>> mit = m.entrySet().iterator();
NavigableMap<K, Float> r = new MapView<K>(this, index,
backingMap);
/*
* If both maps have the same keys, then iterating both simultaneously
* is faster than multiple puts because it avoids doing multiple lookups
*/
while (bit.hasNext() && mit.hasNext()) {
Entry<K, MultiValue> bent = bit.next();
Entry<K, Float> ment = mit.next();
if (ment.getKey().equals(bent.getKey())) {
bent.getValue().put(index, ment.getValue());
} else {
/* It turns out keys are not equals */
r.put(ment.getKey(), ment.getValue());
break;
}
}
/*
* This loop is only used if the backingMap was empty or if for some
* reason the backingMap and the new map do not have the same keys.
*/
while (mit.hasNext()) {
Entry<K, Float> ment = mit.next();
r.put(ment.getKey(), ment.getValue());
}
return r;
}
/* get the next free index for use in a MapView */
private int getNextIndex() {
synchronized (this) {
int index = used_size;
used_size += 1;
if (used_size >= allocated_size) {
allocated_size += GROWTH_RATE;
for (MultiValue v : backingMap.values()) {
v.grow(allocated_size);
}
}
return index;
}
}
/**
* Value within the backing map. This contains the raw data values for each
* basin. In addition it contains a boolean for each basin indicating if the
* value has been set. While most of the time all basins will be set, the
* extra boolean is needed to allow different views to function as
* independent maps, which is needed while new data is getting added.
*/
private static class MultiValue {
/* actual values for all basins. */
private float[] data;
/* booleans indicating whether a value has been set yet for a basin */
private BitSet occupied;
/*
* The number of occupied basins for this time, when its zero the time
* can be removed from the backing map.
*/
private int size = 0;
public MultiValue(int allocated_size) {
this.data = new float[allocated_size];
this.occupied = new BitSet(allocated_size);
}
public void grow(int allocated_size) {
data = Arrays.copyOf(data, allocated_size);
BitSet occupied = new BitSet(allocated_size);
occupied.or(this.occupied);
this.occupied = occupied;
}
public Float put(int index, float value) {
Float oldValue = null;
if (occupied.get(index)) {
oldValue = data[index];
} else {
occupied.set(index);
size += 1;
}
data[index] = value;
return oldValue;
}
public Float get(int index) {
if (occupied.get(index)) {
return data[index];
} else {
return null;
}
}
public boolean contains(int index) {
return occupied.get(index);
}
public Float remove(int index) {
if (occupied.get(index)) {
occupied.clear(index);
size -= 1;
return data[index];
}
return null;
}
public boolean isEmpty() {
return size == 0;
}
}
/**
* NavigableMap implementation which provides a view into the backingMap at
* a specific index. This class can map any NavigableMap<K, MultiValue>
* which simplifies creating sub maps or descending map because all that is
* required is getting new maps from the backingMap and wrapping them in a
* new MapView.
*/
private static class MapView<K> extends AbstractMap<K, Float> implements
NavigableMap<K, Float> {
/* Factory, for syncronization */
private final BasinMapFactory<K> factory;
/* index into the MultiValue */
private final int index;
/* backingMap where all the data really lives. */
private final NavigableMap<K, MultiValue> backingMap;
public MapView(BasinMapFactory<K> factory, int index,
NavigableMap<K, MultiValue> backingMap) {
this.factory = factory;
this.index = index;
this.backingMap = backingMap;
}
@Override
public Float put(K key, Float value) {
synchronized (factory) {
MultiValue v = backingMap.get(key);
if (v == null) {
v = new MultiValue(factory.allocated_size);
backingMap.put(key, v);
}
return v.put(index, value);
}
}
@Override
public boolean containsKey(Object key) {
MultiValue v = backingMap.get(key);
if (v != null) {
return v.contains(index);
} else {
return false;
}
}
@Override
public Float get(Object key) {
MultiValue v = backingMap.get(key);
if (v != null) {
return v.get(index);
} else {
return null;
}
}
@Override
public Float remove(Object key) {
Float oldValue = null;
MultiValue v = backingMap.get(key);
if (v != null) {
synchronized (factory) {
oldValue = v.remove(index);
if (v.isEmpty()) {
backingMap.remove(key);
}
}
}
return oldValue;
}
@Override
public boolean isEmpty() {
/*
* The default implementation uses size() which will iterate over
* all elements, this is much faster.
*/
return !entrySet().iterator().hasNext();
}
@Override
public Comparator<? super K> comparator() {
return backingMap.comparator();
}
@Override
public K firstKey() {
Entry<K, Float> e = firstEntry();
if (e != null) {
return e.getKey();
}
return null;
}
@Override
public K lastKey() {
Entry<K, Float> e = lastEntry();
if (e != null) {
return e.getKey();
}
return null;
}
@Override
public Entry<K, Float> lowerEntry(K key) {
return headMap(key).lastEntry();
}
@Override
public K lowerKey(K key) {
return headMap(key).lastKey();
}
@Override
public Entry<K, Float> floorEntry(K key) {
return headMap(key, true).lastEntry();
}
@Override
public K floorKey(K key) {
return headMap(key, true).lastKey();
}
@Override
public Entry<K, Float> ceilingEntry(K key) {
return tailMap(key).firstEntry();
}
@Override
public K ceilingKey(K key) {
return tailMap(key).firstKey();
}
@Override
public Entry<K, Float> higherEntry(K key) {
return tailMap(key, false).firstEntry();
}
@Override
public K higherKey(K key) {
return tailMap(key, false).firstKey();
}
@Override
public Entry<K, Float> firstEntry() {
for (Entry<K, Float> e : entrySet()) {
return e;
}
return null;
}
@Override
public Entry<K, Float> lastEntry() {
return descendingMap().firstEntry();
}
@Override
public Entry<K, Float> pollFirstEntry() {
Iterator<Entry<K, Float>> it = entrySet().iterator();
if (it.hasNext()) {
Entry<K, Float> e = it.next();
it.remove();
return e;
}
return null;
}
@Override
public Entry<K, Float> pollLastEntry() {
return descendingMap().pollFirstEntry();
}
@Override
public NavigableMap<K, Float> descendingMap() {
return new MapView<K>(factory, index, backingMap.descendingMap());
}
@Override
public NavigableSet<K> navigableKeySet() {
return new NavigableKeySet<K>(this);
}
@Override
public NavigableSet<K> descendingKeySet() {
return descendingMap().navigableKeySet();
}
@Override
public NavigableMap<K, Float> subMap(K fromKey, boolean fromInclusive,
K toKey, boolean toInclusive) {
return new MapView<K>(factory, index, backingMap.subMap(fromKey,
fromInclusive, toKey, toInclusive));
}
@Override
public NavigableMap<K, Float> headMap(K toKey, boolean inclusive) {
return new MapView<K>(factory, index, backingMap.headMap(toKey,
inclusive));
}
@Override
public NavigableMap<K, Float> tailMap(K fromKey, boolean inclusive) {
return new MapView<K>(factory, index, backingMap.tailMap(fromKey,
inclusive));
}
@Override
public NavigableMap<K, Float> subMap(K fromKey, K toKey) {
return new MapView<K>(factory, index, backingMap.subMap(fromKey,
true, toKey, false));
}
@Override
public NavigableMap<K, Float> headMap(K toKey) {
return new MapView<K>(factory, index, backingMap.headMap(toKey,
false));
}
@Override
public NavigableMap<K, Float> tailMap(K fromKey) {
return new MapView<K>(factory, index, backingMap.headMap(fromKey,
true));
}
@Override
public Set<Entry<K, Float>> entrySet() {
return new EntrySet<K>(index, backingMap.entrySet());
}
}
/**
* Entry set for a MapView, just a wrapper over a Set<Entry<K, MultiValue>>
*/
private static class EntrySet<K> extends AbstractSet<Entry<K, Float>> {
private final int index;
private final Set<Entry<K, MultiValue>> backingSet;
public EntrySet(int index, Set<Entry<K, MultiValue>> backingSet) {
this.index = index;
this.backingSet = backingSet;
}
@Override
public Iterator<Entry<K, Float>> iterator() {
return new EntryIterator<K>(index, backingSet.iterator());
}
@Override
public int size() {
Iterator<Entry<K, Float>> it = iterator();
int i = 0;
while (it.hasNext()) {
it.next();
i += 1;
}
return i;
}
}
/**
* Iterator implementation for an EntrySet, This wraps an Iterator<Entry<K,
* MultiValue>> but has extra logic for skipping over values that exist in
* the backingSet but are not occupied within the MultiValue.
*/
private static class EntryIterator<K> implements Iterator<Entry<K, Float>> {
private final int index;
private final Iterator<Entry<K, MultiValue>> backingIterator;
/* The next valid entry in the backingMap */
private transient Entry<K, MultiValue> next;
/* Previous Value returned by next() */
private transient Entry<K, MultiValue> previous;
public EntryIterator(int index,
Iterator<Entry<K, MultiValue>> backingIterator) {
this.index = index;
this.backingIterator = backingIterator;
}
private Entry<K, MultiValue> checkNext() {
if (next == null) {
while (backingIterator.hasNext()) {
Entry<K, MultiValue> e = backingIterator.next();
previous = null;
MultiValue v = e.getValue();
if (v.contains(index)) {
next = e;
return next;
}
}
}
return next;
}
@Override
public boolean hasNext() {
return checkNext() != null;
}
@Override
public Entry<K, Float> next() {
Entry<K, MultiValue> next = checkNext();
previous = next;
if (next == null) {
return null;
} else {
this.next = null;
return new EntryImpl<K>(index, next);
}
}
@Override
public void remove() {
if (previous == null) {
throw new IllegalStateException(
"Cannot remove from iterator because next() was not called.");
}
MultiValue v = previous.getValue();
previous = null;
v.remove(index);
if (v.isEmpty()) {
backingIterator.remove();
}
}
}
/**
* Entry for a MapView, just a wrapper over a Entry<K, MultiValue>.
*/
private static class EntryImpl<K> implements Entry<K, Float> {
private final int index;
private final Entry<K, MultiValue> backingEntry;
public EntryImpl(int index, Entry<K, MultiValue> timeEntry) {
this.index = index;
this.backingEntry = timeEntry;
}
@Override
public K getKey() {
return backingEntry.getKey();
}
@Override
public Float getValue() {
return backingEntry.getValue().get(index);
}
@Override
public Float setValue(Float value) {
return backingEntry.getValue().put(index, value);
}
}
}

View file

@ -0,0 +1,153 @@
/**
* This software was developed and / or modified by Raytheon Company,
* pursuant to Contract DG133W-05-CQ-1067 with the US Government.
*
* U.S. EXPORT CONTROLLED TECHNICAL DATA
* This software product contains export-restricted data whose
* export/transfer/disclosure is restricted by U.S. law. Dissemination
* to non-U.S. persons whether in the United States or abroad requires
* an export license or other authorization.
*
* Contractor Name: Raytheon Company
* Contractor Address: 6825 Pine Street, Suite 340
* Mail Stop B8
* Omaha, NE 68106
* 402.291.0100
*
* See the AWIPS II Master Rights File ("Master Rights File.pdf") for
* further licensing information.
**/
package com.raytheon.uf.common.dataplugin.ffmp.collections;
import java.util.AbstractSet;
import java.util.Comparator;
import java.util.Iterator;
import java.util.NavigableMap;
import java.util.NavigableSet;
import java.util.SortedSet;
/**
*
* Generic NavigableSet which is implemented by wrapping a fully implemented
* NavigableMap. Very useful for custom NavigableMap implementations.
*
* <pre>
*
* SOFTWARE HISTORY
*
* Date Ticket# Engineer Description
* ------------ ---------- ----------- --------------------------
* Jul 31, 2013 2242 bsteffen Extracted from ArrayBackedMap
*
* </pre>
*
* @author bsteffen
* @version 1.0
* @param <K>
*/
class NavigableKeySet<K> extends AbstractSet<K> implements NavigableSet<K> {
private final NavigableMap<K, ?> map;
public NavigableKeySet(NavigableMap<K, ?> map) {
this.map = map;
}
@Override
public Iterator<K> iterator() {
return map.keySet().iterator();
}
@Override
public int size() {
return map.size();
}
@Override
public Comparator<? super K> comparator() {
return map.comparator();
}
@Override
public K first() {
return map.firstKey();
}
@Override
public K last() {
return map.lastKey();
}
@Override
public K lower(K e) {
return map.lowerKey(e);
}
@Override
public K floor(K e) {
return map.floorKey(e);
}
@Override
public K ceiling(K e) {
return map.ceilingKey(e);
}
@Override
public K higher(K e) {
return map.higherKey(e);
}
@Override
public K pollFirst() {
return map.pollFirstEntry().getKey();
}
@Override
public K pollLast() {
return map.pollLastEntry().getKey();
}
@Override
public NavigableSet<K> descendingSet() {
return map.descendingKeySet();
}
@Override
public Iterator<K> descendingIterator() {
return descendingSet().iterator();
}
@Override
public NavigableSet<K> subSet(K fromElement, boolean fromInclusive,
K toElement, boolean toInclusive) {
return map.subMap(fromElement, fromInclusive, toElement,
toInclusive).navigableKeySet();
}
@Override
public NavigableSet<K> headSet(K toElement, boolean inclusive) {
return map.headMap(toElement, inclusive).navigableKeySet();
}
@Override
public NavigableSet<K> tailSet(K fromElement, boolean inclusive) {
return map.tailMap(fromElement, inclusive).navigableKeySet();
}
@Override
public SortedSet<K> subSet(K fromElement, K toElement) {
return subSet(fromElement, true, toElement, false);
}
@Override
public SortedSet<K> headSet(K toElement) {
return headSet(toElement, false);
}
@Override
public SortedSet<K> tailSet(K fromElement) {
return tailSet(fromElement, true);
}
}