From 7e3835fd0a38133853efb87b54f43833e7dbe6a3 Mon Sep 17 00:00:00 2001 From: Ben Steffensmeier Date: Thu, 1 Aug 2013 12:48:50 -0500 Subject: [PATCH] Issue #2242 Optimize FFMP NavigableMap memory. Former-commit-id: f0ceddacf1b337ba91a6876858280eb093d7715d [formerly f0ceddacf1b337ba91a6876858280eb093d7715d [formerly 5208c63e9c6eab03bc42736ffe2254fb802d566b]] Former-commit-id: b06171b4f2ceb7a9d416295f0e20cc3ffa4187bb Former-commit-id: 3eb869c65ec4a50422b50c8222966e1b0fea94dc --- .../uf/common/dataplugin/ffmp/FFMPBasin.java | 47 +- .../common/dataplugin/ffmp/FFMPBasinData.java | 33 +- .../{ => collections}/ArrayBackedMap.java | 119 +--- .../ffmp/collections/BasinMapFactory.java | 621 ++++++++++++++++++ .../ffmp/collections/NavigableKeySet.java | 153 +++++ 5 files changed, 843 insertions(+), 130 deletions(-) rename edexOsgi/com.raytheon.uf.common.dataplugin.ffmp/src/com/raytheon/uf/common/dataplugin/ffmp/{ => collections}/ArrayBackedMap.java (92%) create mode 100644 edexOsgi/com.raytheon.uf.common.dataplugin.ffmp/src/com/raytheon/uf/common/dataplugin/ffmp/collections/BasinMapFactory.java create mode 100644 edexOsgi/com.raytheon.uf.common.dataplugin.ffmp/src/com/raytheon/uf/common/dataplugin/ffmp/collections/NavigableKeySet.java diff --git a/edexOsgi/com.raytheon.uf.common.dataplugin.ffmp/src/com/raytheon/uf/common/dataplugin/ffmp/FFMPBasin.java b/edexOsgi/com.raytheon.uf.common.dataplugin.ffmp/src/com/raytheon/uf/common/dataplugin/ffmp/FFMPBasin.java index 4f0ebe8216..eb83dfa078 100644 --- a/edexOsgi/com.raytheon.uf.common.dataplugin.ffmp/src/com/raytheon/uf/common/dataplugin/ffmp/FFMPBasin.java +++ b/edexOsgi/com.raytheon.uf.common.dataplugin.ffmp/src/com/raytheon/uf/common/dataplugin/ffmp/FFMPBasin.java @@ -28,6 +28,8 @@ import java.util.TreeMap; 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.annotations.DynamicSerialize; 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. * Apr 26, 2013 1954 bsteffen Minor code cleanup throughout FFMP. * Jul 15, 2013 2184 dhladky Remove all HUC's for storage except ALL + * Jul 31, 2013 2242 bsteffen Optimize FFMP NavigableMap memory. * * * @@ -71,6 +74,13 @@ public class FFMPBasin implements ISerializableObject, Cloneable { @Transient protected NavigableMap 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 **/ @DynamicSerializeElement public float[] serializedValues; @@ -172,9 +182,9 @@ public class FFMPBasin implements ISerializableObject, Cloneable { Date prevDate = null; // 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; @@ -226,7 +236,7 @@ public class FFMPBasin implements ISerializableObject, Cloneable { */ public Float getValue(Date afterDate, Date beforeDate) { Float val = 0.0f; - synchronized (values) { + synchronized (valuesSynchronization) { Date checkDate = values.ceilingKey(afterDate); if ((checkDate != null) && checkDate.before(beforeDate)) { val = values.get(checkDate); @@ -260,7 +270,7 @@ public class FFMPBasin implements ISerializableObject, Cloneable { Float val = 0.0f; int i = 0; - synchronized (values) { + synchronized (valuesSynchronization) { for (Date date : values.keySet()) { if (date.before(beforeDate) && date.after(afterDate)) { @@ -290,7 +300,7 @@ public class FFMPBasin implements ISerializableObject, Cloneable { public Float getMaxValue(Date afterDate, Date beforeDate) { Float val = 0.0f; - synchronized (values) { + synchronized (valuesSynchronization) { for (Date date : values.keySet()) { if (date.before(beforeDate) && date.after(afterDate)) { @@ -312,13 +322,7 @@ public class FFMPBasin implements ISerializableObject, Cloneable { * @param value */ public void setValue(Date date, Float dvalue) { - synchronized (values) { - 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(values); - } + synchronized (valuesSynchronization) { values.put(date, dvalue); } } @@ -339,6 +343,7 @@ public class FFMPBasin implements ISerializableObject, Cloneable { */ public void setValues(TreeMap values) { this.values = values; + this.valuesSynchronization = values; } /** @@ -347,6 +352,7 @@ public class FFMPBasin implements ISerializableObject, Cloneable { public FFMPBasin() { values = new TreeMap(Collections.reverseOrder()); + valuesSynchronization = values; } /** @@ -358,23 +364,32 @@ public class FFMPBasin implements ISerializableObject, Cloneable { setPfaf(pfaf); setAggregated(aggregated); values = new TreeMap(Collections.reverseOrder()); + valuesSynchronization = values; } + public FFMPBasin(Long pfaf, boolean aggregated, + BasinMapFactory mapFactory) { + setPfaf(pfaf); + setAggregated(aggregated); + values = mapFactory.getMap(); + valuesSynchronization = mapFactory; + } + /** * Populates the map from the serialized values * * @param times */ - public void deserialize(long[] times) { + public void deserialize(long[] times, BasinMapFactory mapFactory) { // safe to avoid Array Index Exceptions / shouldn't happen but..... if (serializedValues != null && (times.length == serializedValues.length)) { NavigableMap fastMap = new ArrayBackedMap(times, serializedValues); - values = fastMap.descendingMap(); - // values = new TreeMap(fastMap.descendingMap()); + values = mapFactory.getMap(fastMap.descendingMap()); + valuesSynchronization = mapFactory; } serializedValues = null; } @@ -401,7 +416,7 @@ public class FFMPBasin implements ISerializableObject, Cloneable { */ public void purgeData(Date date) { if (values != null) { - synchronized (values) { + synchronized (valuesSynchronization) { ArrayList removes = new ArrayList(); for (Date mdate : values.keySet()) { if (mdate.before(date)) { diff --git a/edexOsgi/com.raytheon.uf.common.dataplugin.ffmp/src/com/raytheon/uf/common/dataplugin/ffmp/FFMPBasinData.java b/edexOsgi/com.raytheon.uf.common.dataplugin.ffmp/src/com/raytheon/uf/common/dataplugin/ffmp/FFMPBasinData.java index 56e46518d6..6e7196c3ce 100644 --- a/edexOsgi/com.raytheon.uf.common.dataplugin.ffmp/src/com/raytheon/uf/common/dataplugin/ffmp/FFMPBasinData.java +++ b/edexOsgi/com.raytheon.uf.common.dataplugin.ffmp/src/com/raytheon/uf/common/dataplugin/ffmp/FFMPBasinData.java @@ -22,6 +22,7 @@ package com.raytheon.uf.common.dataplugin.ffmp; import java.io.File; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.Iterator; @@ -29,6 +30,7 @@ import java.util.List; import java.util.Map; 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.records.FloatDataRecord; 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. * 07/09/13 2152 njensen Ensure purgeData() does not load data * 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. + * * * * @@ -82,6 +87,20 @@ public class FFMPBasinData implements ISerializableObject { */ private final Map orderedBasinsCache = new HashMap(); + /** + * Shared factory for efficient storage of data in basins. + */ + private BasinMapFactory mapFactory = null; + + /** + * Public one arg constructor + * + * @param huc_level + */ + public FFMPBasinData(String hucLevel) { + setHucLevel(hucLevel); + } + /** * No arg hibernate constructor */ @@ -666,13 +685,18 @@ public class FFMPBasinData implements ISerializableObject { * @param times */ public void populate(List times) { + + if (mapFactory == null) { + mapFactory = new BasinMapFactory(Collections.reverseOrder(), + getBasins().size()); + } long[] timesArr = new long[times.size()]; for (int i = 0; i < timesArr.length; i += 1) { timesArr[i] = times.get(i); } for (FFMPBasin basin : getBasins().values()) { - basin.deserialize(timesArr); + basin.deserialize(timesArr, mapFactory); } } @@ -730,7 +754,12 @@ public class FFMPBasinData implements ISerializableObject { if (guidance) { basin = new FFMPGuidanceBasin(pfaf, aggregate); } else { - basin = new FFMPBasin(pfaf, aggregate); + if (mapFactory == null) { + mapFactory = new BasinMapFactory( + Collections.reverseOrder(), + orderedPfafs.size()); + } + basin = new FFMPBasin(pfaf, aggregate, mapFactory); } this.basins.put(pfaf, basin); } diff --git a/edexOsgi/com.raytheon.uf.common.dataplugin.ffmp/src/com/raytheon/uf/common/dataplugin/ffmp/ArrayBackedMap.java b/edexOsgi/com.raytheon.uf.common.dataplugin.ffmp/src/com/raytheon/uf/common/dataplugin/ffmp/collections/ArrayBackedMap.java similarity index 92% rename from edexOsgi/com.raytheon.uf.common.dataplugin.ffmp/src/com/raytheon/uf/common/dataplugin/ffmp/ArrayBackedMap.java rename to edexOsgi/com.raytheon.uf.common.dataplugin.ffmp/src/com/raytheon/uf/common/dataplugin/ffmp/collections/ArrayBackedMap.java index dfad10c039..6b425079e7 100644 --- a/edexOsgi/com.raytheon.uf.common.dataplugin.ffmp/src/com/raytheon/uf/common/dataplugin/ffmp/ArrayBackedMap.java +++ b/edexOsgi/com.raytheon.uf.common.dataplugin.ffmp/src/com/raytheon/uf/common/dataplugin/ffmp/collections/ArrayBackedMap.java @@ -17,7 +17,7 @@ * See the AWIPS II Master Rights File ("Master Rights File.pdf") for * 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.AbstractSet; @@ -34,7 +34,6 @@ import java.util.NavigableSet; import java.util.NoSuchElementException; import java.util.Set; import java.util.SortedMap; -import java.util.SortedSet; import java.util.TreeMap; /** @@ -66,7 +65,9 @@ import java.util.TreeMap; * * 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. * * * @@ -264,7 +265,7 @@ public class ArrayBackedMap extends AbstractMap implements @Override public NavigableSet navigableKeySet() { - return new NavigableKeySet(this); + return new NavigableKeySet(this); } private int lowerIndex(Date key, boolean inclusive) { @@ -592,113 +593,7 @@ public class ArrayBackedMap extends AbstractMap implements } - private static class NavigableKeySet extends AbstractSet implements - NavigableSet { - private final NavigableMap map; - - public NavigableKeySet(NavigableMap map) { - this.map = map; - } - - @Override - public Iterator iterator() { - return map.keySet().iterator(); - } - - @Override - public int size() { - return map.size(); - } - - @Override - public Comparator 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 descendingSet() { - return map.descendingKeySet(); - } - - @Override - public Iterator descendingIterator() { - return descendingSet().iterator(); - } - - @Override - public NavigableSet subSet(Date fromElement, - boolean fromInclusive, Date toElement, boolean toInclusive) { - return map.subMap(fromElement, fromInclusive, toElement, - toInclusive).navigableKeySet(); - } - - @Override - public NavigableSet headSet(Date toElement, boolean inclusive) { - return map.headMap(toElement, inclusive).navigableKeySet(); - } - - @Override - public NavigableSet tailSet(Date fromElement, boolean inclusive) { - return map.tailMap(fromElement, inclusive).navigableKeySet(); - } - - @Override - public SortedSet subSet(Date fromElement, Date toElement) { - return subSet(fromElement, true, toElement, false); - } - - @Override - public SortedSet headSet(Date toElement) { - return headSet(toElement, false); - } - - @Override - public SortedSet tailSet(Date fromElement) { - return tailSet(fromElement, true); - } - - } private class SubMap extends AbstractMap implements NavigableMap { @@ -854,7 +749,7 @@ public class ArrayBackedMap extends AbstractMap implements @Override public NavigableSet navigableKeySet() { - return new NavigableKeySet(this); + return new NavigableKeySet(this); } @Override @@ -1066,7 +961,7 @@ public class ArrayBackedMap extends AbstractMap implements @Override public NavigableSet navigableKeySet() { - return new NavigableKeySet(this); + return new NavigableKeySet(this); } @Override diff --git a/edexOsgi/com.raytheon.uf.common.dataplugin.ffmp/src/com/raytheon/uf/common/dataplugin/ffmp/collections/BasinMapFactory.java b/edexOsgi/com.raytheon.uf.common.dataplugin.ffmp/src/com/raytheon/uf/common/dataplugin/ffmp/collections/BasinMapFactory.java new file mode 100644 index 0000000000..2b2f28e464 --- /dev/null +++ b/edexOsgi/com.raytheon.uf.common.dataplugin.ffmp/src/com/raytheon/uf/common/dataplugin/ffmp/collections/BasinMapFactory.java @@ -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 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. + * + *
+ * 
+ * SOFTWARE HISTORY
+ * 
+ * Date         Ticket#    Engineer    Description
+ * ------------ ---------- ----------- --------------------------
+ * Jul 30, 2013 2242       bsteffen    Initial creation
+ * 
+ * 
+ * + * @author bsteffen + * @version 1.0 + */ + +public class BasinMapFactory { + + /* + * 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 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 comparator, int initialSize) { + allocated_size = initialSize; + used_size = 0; + this.backingMap = new TreeMap(comparator); + } + + /** + * Get a new map from this factory. + * + * @return + */ + public NavigableMap getMap() { + return new MapView(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 getMap(NavigableMap m) { + Comparator bc = backingMap.comparator(); + Comparator 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> bit = backingMap.entrySet().iterator(); + Iterator> mit = m.entrySet().iterator(); + NavigableMap r = new MapView(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 bent = bit.next(); + Entry 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 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 + * 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 extends AbstractMap implements + NavigableMap { + + /* Factory, for syncronization */ + private final BasinMapFactory factory; + + /* index into the MultiValue */ + private final int index; + + /* backingMap where all the data really lives. */ + private final NavigableMap backingMap; + + public MapView(BasinMapFactory factory, int index, + NavigableMap 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 comparator() { + return backingMap.comparator(); + } + + @Override + public K firstKey() { + Entry e = firstEntry(); + if (e != null) { + return e.getKey(); + } + return null; + } + + @Override + public K lastKey() { + Entry e = lastEntry(); + if (e != null) { + return e.getKey(); + } + return null; + } + + @Override + public Entry lowerEntry(K key) { + return headMap(key).lastEntry(); + } + + @Override + public K lowerKey(K key) { + return headMap(key).lastKey(); + + } + + @Override + public Entry floorEntry(K key) { + return headMap(key, true).lastEntry(); + + } + + @Override + public K floorKey(K key) { + return headMap(key, true).lastKey(); + + } + + @Override + public Entry ceilingEntry(K key) { + return tailMap(key).firstEntry(); + } + + @Override + public K ceilingKey(K key) { + return tailMap(key).firstKey(); + + } + + @Override + public Entry higherEntry(K key) { + return tailMap(key, false).firstEntry(); + } + + @Override + public K higherKey(K key) { + return tailMap(key, false).firstKey(); + + } + + @Override + public Entry firstEntry() { + for (Entry e : entrySet()) { + return e; + } + return null; + } + + @Override + public Entry lastEntry() { + return descendingMap().firstEntry(); + } + + @Override + public Entry pollFirstEntry() { + Iterator> it = entrySet().iterator(); + if (it.hasNext()) { + Entry e = it.next(); + it.remove(); + return e; + } + return null; + } + + @Override + public Entry pollLastEntry() { + return descendingMap().pollFirstEntry(); + } + + @Override + public NavigableMap descendingMap() { + return new MapView(factory, index, backingMap.descendingMap()); + } + + @Override + public NavigableSet navigableKeySet() { + return new NavigableKeySet(this); + } + + @Override + public NavigableSet descendingKeySet() { + return descendingMap().navigableKeySet(); + } + + @Override + public NavigableMap subMap(K fromKey, boolean fromInclusive, + K toKey, boolean toInclusive) { + return new MapView(factory, index, backingMap.subMap(fromKey, + fromInclusive, toKey, toInclusive)); + } + + @Override + public NavigableMap headMap(K toKey, boolean inclusive) { + return new MapView(factory, index, backingMap.headMap(toKey, + inclusive)); + } + + @Override + public NavigableMap tailMap(K fromKey, boolean inclusive) { + return new MapView(factory, index, backingMap.tailMap(fromKey, + inclusive)); + } + + @Override + public NavigableMap subMap(K fromKey, K toKey) { + return new MapView(factory, index, backingMap.subMap(fromKey, + true, toKey, false)); + } + + @Override + public NavigableMap headMap(K toKey) { + return new MapView(factory, index, backingMap.headMap(toKey, + false)); + + } + + @Override + public NavigableMap tailMap(K fromKey) { + return new MapView(factory, index, backingMap.headMap(fromKey, + true)); + + } + + @Override + public Set> entrySet() { + return new EntrySet(index, backingMap.entrySet()); + } + + } + + /** + * Entry set for a MapView, just a wrapper over a Set> + */ + private static class EntrySet extends AbstractSet> { + + private final int index; + + private final Set> backingSet; + + public EntrySet(int index, Set> backingSet) { + this.index = index; + this.backingSet = backingSet; + } + + @Override + public Iterator> iterator() { + return new EntryIterator(index, backingSet.iterator()); + } + + @Override + public int size() { + Iterator> it = iterator(); + int i = 0; + while (it.hasNext()) { + it.next(); + i += 1; + } + return i; + } + + } + + /** + * Iterator implementation for an EntrySet, This wraps an Iterator> but has extra logic for skipping over values that exist in + * the backingSet but are not occupied within the MultiValue. + */ + private static class EntryIterator implements Iterator> { + + private final int index; + + private final Iterator> backingIterator; + + /* The next valid entry in the backingMap */ + private transient Entry next; + + /* Previous Value returned by next() */ + private transient Entry previous; + + public EntryIterator(int index, + Iterator> backingIterator) { + this.index = index; + this.backingIterator = backingIterator; + } + + private Entry checkNext() { + if (next == null) { + while (backingIterator.hasNext()) { + Entry 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 next() { + Entry next = checkNext(); + previous = next; + if (next == null) { + return null; + } else { + this.next = null; + return new EntryImpl(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. + */ + private static class EntryImpl implements Entry { + + private final int index; + + private final Entry backingEntry; + + public EntryImpl(int index, Entry 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); + } + + } + +} diff --git a/edexOsgi/com.raytheon.uf.common.dataplugin.ffmp/src/com/raytheon/uf/common/dataplugin/ffmp/collections/NavigableKeySet.java b/edexOsgi/com.raytheon.uf.common.dataplugin.ffmp/src/com/raytheon/uf/common/dataplugin/ffmp/collections/NavigableKeySet.java new file mode 100644 index 0000000000..1c654cc4c6 --- /dev/null +++ b/edexOsgi/com.raytheon.uf.common.dataplugin.ffmp/src/com/raytheon/uf/common/dataplugin/ffmp/collections/NavigableKeySet.java @@ -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. + * + *
+ * 
+ * SOFTWARE HISTORY
+ * 
+ * Date         Ticket#    Engineer    Description
+ * ------------ ---------- ----------- --------------------------
+ * Jul 31, 2013 2242       bsteffen    Extracted from ArrayBackedMap
+ * 
+ * 
+ * + * @author bsteffen + * @version 1.0 + * @param + */ +class NavigableKeySet extends AbstractSet implements NavigableSet { + + private final NavigableMap map; + + public NavigableKeySet(NavigableMap map) { + this.map = map; + } + + @Override + public Iterator iterator() { + return map.keySet().iterator(); + } + + @Override + public int size() { + return map.size(); + } + + @Override + public Comparator 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 descendingSet() { + return map.descendingKeySet(); + } + + @Override + public Iterator descendingIterator() { + return descendingSet().iterator(); + } + + @Override + public NavigableSet subSet(K fromElement, boolean fromInclusive, + K toElement, boolean toInclusive) { + return map.subMap(fromElement, fromInclusive, toElement, + toInclusive).navigableKeySet(); + } + + @Override + public NavigableSet headSet(K toElement, boolean inclusive) { + return map.headMap(toElement, inclusive).navigableKeySet(); + } + + @Override + public NavigableSet tailSet(K fromElement, boolean inclusive) { + return map.tailMap(fromElement, inclusive).navigableKeySet(); + } + + @Override + public SortedSet subSet(K fromElement, K toElement) { + return subSet(fromElement, true, toElement, false); + } + + @Override + public SortedSet headSet(K toElement) { + return headSet(toElement, false); + } + + @Override + public SortedSet tailSet(K fromElement) { + return tailSet(fromElement, true); + } + +}