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/ArrayBackedMap.java new file mode 100644 index 0000000000..dfad10c039 --- /dev/null +++ b/edexOsgi/com.raytheon.uf.common.dataplugin.ffmp/src/com/raytheon/uf/common/dataplugin/ffmp/ArrayBackedMap.java @@ -0,0 +1,1300 @@ +/** + * 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; + +import java.util.AbstractMap; +import java.util.AbstractSet; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.NavigableMap; +import java.util.NavigableSet; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.SortedMap; +import java.util.SortedSet; +import java.util.TreeMap; + +/** + * The ArrayBacked map is a navigable map implementation that is designed + * specifically for fast serialization/deserialization because the underlining + * data structure are simple sorted primitive arrays. It also has very efficient + * memory usage because there are no intermediary Objects associated with + * entries. + * + * Most operations are implemented as a binary search on the key array so they + * are guaranteed to complete in log(n) time. The exception to this rule is + * structural modifications (A structural modification is any operation that + * adds or deletes one or more mappings; merely changing the value associated + * with an existing key is not a structural modification.) Structural + * modification require copying both underlining arrays which will take up to n + * time and should not be considered a high performance operation. For any use + * of a NavigableMap which requires multiple structural modifications TreeMap + * would be a much better choice. For combining this map with other SortedMaps + * of similar size, putAll has been optimized and will perform much better then + * a naive loop which puts all elements. + * + * This map is not thread safe for modification and does not even throw + * ConcurrentModificationExceptions. Any concurrent modification will result in + * strange behavior and not explicit exceptions. + * + *
+ * 
+ * SOFTWARE HISTORY
+ * 
+ * Date         Ticket#    Engineer    Description
+ * ------------ ---------- ----------- --------------------------
+ * Apr 18, 2013            bsteffen     Initial creation
+ * 
+ * 
+ * + * @author bsteffen + * @version 1.0 + */ + +public class ArrayBackedMap extends AbstractMap implements + NavigableMap { + + private long[] keys; + + private float[] values; + + public ArrayBackedMap(long[] keys, float[] values) { + this.keys = keys; + this.values = values; + } + + public ArrayBackedMap(SortedMap map) { + this.keys = new long[map.size()]; + this.values = new float[keys.length]; + int i = 0; + for (Entry e : map.entrySet()) { + keys[i] = e.getKey().getTime(); + values[i] = e.getValue(); + i += 1; + } + } + + public long[] getKeys() { + return keys; + } + + public float[] getValues() { + return values; + } + + /** + * Replacing existing values is fast (log(n)) but inserting new values is + * slow(n). + */ + @Override + public Float put(Date key, Float value) { + int index = Arrays.binarySearch(keys, key.getTime()); + if (index >= 0) { + float old = values[index]; + values[index] = value; + return old; + } else { + long[] keys = new long[values.length + 1]; + float[] values = new float[keys.length]; + System.arraycopy(this.keys, 0, keys, 0, -index - 1); + System.arraycopy(this.values, 0, values, 0, -index - 1); + keys[-index - 1] = key.getTime(); + values[-index - 1] = value; + System.arraycopy(this.keys, -index - 1, keys, -index, + this.values.length + index + 1); + System.arraycopy(this.values, -index - 1, values, -index, + this.values.length + index + 1); + this.keys = keys; + this.values = values; + return null; + } + } + + /** + * Sorted maps which use the default sort order will be put in m+n time. + * Otherwise it will take m*log(m_n). + */ + @Override + @SuppressWarnings("unchecked") + public void putAll(Map map) { + if (map instanceof SortedMap && !map.isEmpty()) { + Comparator c = ((SortedMap) map).comparator(); + if (c == null) { + // much faster putAll. + long[] keys = new long[values.length + map.size()]; + float[] values = new float[keys.length]; + int oldIndex = 0; + int newIndex = 0; + Iterator it = map.entrySet().iterator(); + Entry next = (Entry) it.next(); + long nextLong = next.getKey().getTime(); + long myNextLong = this.keys[oldIndex]; + while (nextLong != Long.MAX_VALUE + || myNextLong != Long.MAX_VALUE) { + boolean incMe = false; + if (nextLong <= myNextLong) { + incMe = nextLong == myNextLong; + keys[newIndex] = nextLong; + values[newIndex] = next.getValue(); + if (it.hasNext()) { + next = (Entry) it.next(); + nextLong = next.getKey().getTime(); + } else { + nextLong = Long.MAX_VALUE; + } + } else { + keys[newIndex] = myNextLong; + values[newIndex] = this.values[oldIndex]; + incMe = true; + } + if (incMe) { + oldIndex += 1; + if (oldIndex < this.keys.length) { + myNextLong = this.keys[oldIndex]; + } else { + myNextLong = Long.MAX_VALUE; + } + } + newIndex += 1; + } + if (newIndex < keys.length) { + // there were some duplicates; + keys = Arrays.copyOf(keys, newIndex); + values = Arrays.copyOf(values, newIndex); + } + this.keys = keys; + this.values = values; + } else { + super.putAll(map); + } + } else { + super.putAll(map); + } + } + + @Override + public Float get(Object key) { + if (key instanceof Date) { + int index = Arrays.binarySearch(keys, ((Date) key).getTime()); + if (index >= 0) { + return values[index]; + } + } + return null; + } + + @Override + public boolean containsKey(Object key) { + return get(key) != null; + } + + @Override + public Comparator comparator() { + return null; + } + + @Override + public Date firstKey() { + if (keys.length == 0) { + return null; + } else { + return new Date(keys[0]); + } + } + + @Override + public Date lastKey() { + if (keys.length == 0) { + return null; + } else { + return new Date(keys[keys.length - 1]); + } + + } + + @Override + public Entry firstEntry() { + if (keys.length == 0) { + return null; + } else { + return new EntryImpl(0); + } + } + + @Override + public Entry lastEntry() { + if (keys.length == 0) { + return null; + } else { + return new EntryImpl(keys.length - 1); + } + } + + @Override + public Set> entrySet() { + return new EntrySet(null, null); + } + + public NavigableSet descendingKeySet() { + return descendingMap().navigableKeySet(); + } + + @Override + public NavigableSet navigableKeySet() { + return new NavigableKeySet(this); + } + + private int lowerIndex(Date key, boolean inclusive) { + int index = Arrays.binarySearch(keys, key.getTime()); + if (index < 0) { + return -index - 2; + } else if (inclusive) { + return index; + } else { + return index - 1; + } + } + + private int higherIndex(Date key, boolean inclusive) { + int index = Arrays.binarySearch(keys, key.getTime()); + if (index < 0) { + return -index - 1; + } else if (inclusive) { + return index; + } else { + return index + 1; + } + } + + @Override + public Entry lowerEntry(Date key) { + int index = lowerIndex(key, false); + if (index >= 0) { + return new EntryImpl(index); + } + return null; + } + + @Override + public Date lowerKey(Date key) { + int index = lowerIndex(key, false); + if (index >= 0) { + return new Date(keys[index]); + } + return null; + } + + @Override + public Entry floorEntry(Date key) { + int index = lowerIndex(key, true); + if (index >= 0) { + return new EntryImpl(index); + } + return null; + } + + @Override + public Date floorKey(Date key) { + int index = lowerIndex(key, true); + if (index >= 0) { + return new Date(keys[index]); + } + return null; + } + + @Override + public Entry higherEntry(Date key) { + int index = higherIndex(key, false); + if (index < keys.length) { + return new EntryImpl(index); + } + return null; + } + + @Override + public Date higherKey(Date key) { + int index = higherIndex(key, false); + if (index < keys.length) { + return new Date(keys[index]); + } + return null; + } + + @Override + public Entry ceilingEntry(Date key) { + int index = higherIndex(key, true); + if (index < keys.length) { + return new EntryImpl(index); + } + return null; + } + + public Date ceilingKey(Date key) { + int index = higherIndex(key, true); + if (index < keys.length) { + return new Date(keys[index]); + } + return null; + } + + @Override + public NavigableMap descendingMap() { + return new DescendingMap(new SubMap(null, null)); + } + + @Override + public NavigableMap subMap(Date fromKey, Date toKey) { + return subMap(fromKey, true, toKey, false); + } + + @Override + public NavigableMap headMap(Date toKey) { + return headMap(toKey, false); + } + + @Override + public NavigableMap tailMap(Date fromKey) { + return tailMap(fromKey, true); + } + + @Override + public NavigableMap subMap(Date fromKey, + boolean fromInclusive, Date toKey, boolean toInclusive) { + if (!fromInclusive) { + fromKey = new Date(fromKey.getTime() + 1); + } + if (!toInclusive) { + toKey = new Date(toKey.getTime() - 1); + } + return new SubMap(fromKey, toKey); + } + + @Override + public NavigableMap headMap(Date toKey, boolean inclusive) { + if (!inclusive) { + toKey = new Date(toKey.getTime() - 1); + } + return new SubMap(null, toKey); + } + + @Override + public NavigableMap tailMap(Date fromKey, boolean inclusive) { + if (!inclusive) { + fromKey = new Date(fromKey.getTime() + 1); + } + return new SubMap(fromKey, null); + } + + @Override + public Entry pollFirstEntry() { + // This could be made faster. + Entry e = firstEntry(); + remove(e.getKey()); + return e; + } + + @Override + public Entry pollLastEntry() { + // This could be made faster. + Entry e = lastEntry(); + remove(e.getKey()); + return e; + } + + private void remove(int index) { + long[] keys = new long[values.length - 1]; + float[] values = new float[keys.length]; + System.arraycopy(this.keys, 0, keys, 0, index); + System.arraycopy(this.values, 0, values, 0, index); + System.arraycopy(this.keys, index + 1, keys, index, this.values.length + - index - 1); + System.arraycopy(this.values, index + 1, values, index, + this.values.length - index - 1); + this.keys = keys; + this.values = values; + } + + private class EntrySet extends AbstractSet> { + + private final boolean descending; + + private final Date startTime; + + private final Date endTime; + + public EntrySet(Date startTime, Date endTime) { + this(startTime, endTime, false); + } + + public EntrySet(Date startTime, Date endTime, boolean descending) { + this.startTime = startTime; + this.endTime = endTime; + this.descending = descending; + } + + @Override + public Iterator> iterator() { + if (descending) { + return new DecsendingEntryIterator(startTime, endTime); + } else { + return new EntryIterator(startTime, endTime); + } + + } + + @Override + public int size() { + int startIndex = 0; + int endIndex = keys.length; + if (startTime != null) { + startIndex = higherIndex(startTime, true); + } + if (endTime != null) { + endIndex = lowerIndex(endTime, true) + 1; + } + return endIndex - startIndex; + } + + } + + private class EntryIterator implements Iterator> { + + private final long endTime; + + private int index; + + public EntryIterator(Date startTime, Date endTime) { + if (startTime != null) { + index = higherIndex(startTime, true); + } else { + index = 0; + } + if (endTime != null) { + this.endTime = endTime.getTime(); + } else { + this.endTime = Long.MAX_VALUE; + } + } + + @Override + public boolean hasNext() { + return index < keys.length && keys[index] <= endTime; + } + + @Override + public Entry next() { + if (hasNext()) { + EntryImpl e = new EntryImpl(index); + index += 1; + return e; + } else { + throw new NoSuchElementException(); + } + } + + @Override + public void remove() { + ArrayBackedMap.this.remove(index - 1); + } + + } + + private class DecsendingEntryIterator implements + Iterator> { + + private final long endTime; + + private int index; + + public DecsendingEntryIterator(Date startTime, Date endTime) { + if (endTime != null) { + index = lowerIndex(endTime, true); + } else { + index = keys.length - 1; + } + if (startTime != null) { + this.endTime = startTime.getTime(); + } else { + this.endTime = Long.MIN_VALUE; + } + } + + @Override + public boolean hasNext() { + return index >= 0 && keys[index] >= endTime; + } + + @Override + public Entry next() { + if (hasNext()) { + EntryImpl e = new EntryImpl(index); + index -= 1; + return e; + } else { + throw new NoSuchElementException(); + } + } + + @Override + public void remove() { + ArrayBackedMap.this.remove(index + 1); + } + + } + + private class EntryImpl implements Entry { + + private final int index; + + public EntryImpl(int index) { + this.index = index; + } + + @Override + public Date getKey() { + return new Date(keys[index]); + } + + @Override + public Float getValue() { + return values[index]; + } + + @Override + public Float setValue(Float value) { + Float f = values[index]; + values[index] = value; + return f; + } + + } + + 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 { + + private final Date min; + + private final Date max; + + public SubMap(Date min, Date max) { + this.min = min; + this.max = max; + } + + @Override + public Comparator comparator() { + return ArrayBackedMap.this.comparator(); + } + + @Override + public Date firstKey() { + if (min == null) { + return ArrayBackedMap.this.firstKey(); + } else { + return ArrayBackedMap.this.ceilingKey(min); + } + } + + @Override + public Date lastKey() { + if (max == null) { + return ArrayBackedMap.this.lastKey(); + } else { + return ArrayBackedMap.this.floorKey(max); + } + } + + @Override + public Entry lowerEntry(Date key) { + if (max != null && key.after(max)) { + return ArrayBackedMap.this.floorEntry(max); + } + Entry e = ArrayBackedMap.this.lowerEntry(key); + if (e != null && min != null && e.getKey().before(min)) { + return null; + } + return e; + } + + @Override + public Date lowerKey(Date key) { + Entry e = lowerEntry(key); + return e == null ? null : e.getKey(); + } + + @Override + public Entry floorEntry(Date key) { + if (max != null && key.after(max)) { + return ArrayBackedMap.this.floorEntry(max); + } + Entry e = ArrayBackedMap.this.floorEntry(key); + if (e != null && min != null && e.getKey().before(min)) { + return null; + } + return e; + } + + @Override + public Date floorKey(Date key) { + Entry e = floorEntry(key); + return e == null ? null : e.getKey(); + } + + @Override + public Entry ceilingEntry(Date key) { + if (min != null && key.before(min)) { + return ArrayBackedMap.this.floorEntry(min); + } + Entry e = ArrayBackedMap.this.ceilingEntry(key); + if (e != null && max != null && e.getKey().after(max)) { + return null; + } + return e; + } + + @Override + public Date ceilingKey(Date key) { + Entry e = ceilingEntry(key); + return e == null ? null : e.getKey(); + } + + @Override + public Entry higherEntry(Date key) { + if (min != null && key.before(min)) { + return ArrayBackedMap.this.floorEntry(min); + } + Entry e = ArrayBackedMap.this.higherEntry(key); + if (e != null && max != null && e.getKey().after(max)) { + return null; + } + return e; + } + + @Override + public Date higherKey(Date key) { + Entry e = higherEntry(key); + return e == null ? null : e.getKey(); + } + + @Override + public Entry firstEntry() { + if (min == null) { + return ArrayBackedMap.this.firstEntry(); + } else { + return ArrayBackedMap.this.ceilingEntry(min); + } + } + + @Override + public Entry lastEntry() { + if (max == null) { + return ArrayBackedMap.this.lastEntry(); + } else { + return ArrayBackedMap.this.floorEntry(max); + } + } + + @Override + public Entry pollFirstEntry() { + if (min == null) { + return ArrayBackedMap.this.pollFirstEntry(); + } else { + Entry e = firstEntry(); + ArrayBackedMap.this.remove(e.getKey()); + return e; + } + } + + @Override + public Entry pollLastEntry() { + if (min == null) { + return ArrayBackedMap.this.pollLastEntry(); + } else { + Entry e = lastEntry(); + ArrayBackedMap.this.remove(e.getKey()); + return e; + } + } + + @Override + public NavigableMap descendingMap() { + return new DescendingMap(this); + } + + @Override + public NavigableSet navigableKeySet() { + return new NavigableKeySet(this); + } + + @Override + public NavigableSet descendingKeySet() { + return descendingMap().navigableKeySet(); + } + + @Override + public SubMap subMap(Date fromKey, + boolean fromInclusive, Date toKey, boolean toInclusive) { + if (!fromInclusive) { + fromKey = new Date(fromKey.getTime() + 1); + } + if (!toInclusive) { + toKey = new Date(toKey.getTime() - 1); + } + if (checkKey(fromKey) && checkKey(toKey)) { + return new SubMap(fromKey, toKey); + } + throw new IllegalArgumentException(); + } + + @Override + public SubMap headMap(Date toKey, boolean inclusive) { + if (!inclusive) { + toKey = new Date(toKey.getTime() - 1); + } + if (checkKey(toKey)) { + return new SubMap(min, toKey); + } + throw new IllegalArgumentException(); + } + + @Override + public SubMap tailMap(Date fromKey, boolean inclusive) { + if (!inclusive) { + fromKey = new Date(fromKey.getTime() + 1); + } + if (checkKey(fromKey)) { + return new SubMap(fromKey, max); + } + throw new IllegalArgumentException(); + } + + public SubMap subMap(Date fromKey, Date toKey) { + return subMap(fromKey, true, toKey, false); + } + + @Override + public SubMap headMap(Date toKey) { + return headMap(toKey, false); + } + + @Override + public SubMap tailMap(Date fromKey) { + return tailMap(fromKey, true); + } + + @Override + public Set> entrySet() { + return new EntrySet(min, max); + } + + @Override + public boolean containsKey(Object key) { + return get(key) != null; + } + + @Override + public Float get(Object key) { + if (checkKey(key)) { + return ArrayBackedMap.this.get(key); + } + return null; + } + + @Override + public Float put(Date key, Float value) { + if (checkKey(key)) { + return ArrayBackedMap.this.put(key, value); + } + throw new IllegalArgumentException(); + } + + @Override + public void putAll(Map m) { + if (min != null || max != null) { + for (Date key : m.keySet()) { + if (!checkKey(key)) { + throw new IllegalArgumentException(); + } + } + } + ArrayBackedMap.this.putAll(m); + } + + @Override + public Float remove(Object key) { + if (checkKey(key)) { + return ArrayBackedMap.this.remove(key); + } + throw new IllegalArgumentException(); + } + + private boolean checkKey(Object key) { + if (key instanceof Date) { + Date k = (Date) key; + if (min != null && min.after(k)) { + return false; + } + if (max != null && max.before(k)) { + return false; + } + return true; + } + return false; + } + + } + + private class DescendingMap extends AbstractMap implements + NavigableMap { + + private final SubMap subMap; + + public DescendingMap(SubMap subMap) { + this.subMap = subMap; + } + + @Override + public Comparator comparator() { + return Collections.reverseOrder(subMap.comparator()); + } + + @Override + public Date firstKey() { + return subMap.lastKey(); + } + + @Override + public Date lastKey() { + return subMap.firstKey(); + } + + @Override + public Entry lowerEntry(Date key) { + return subMap.higherEntry(key); + } + + @Override + public Date lowerKey(Date key) { + return subMap.higherKey(key); + } + + @Override + public Entry floorEntry(Date key) { + return subMap.ceilingEntry(key); + } + + @Override + public Date floorKey(Date key) { + return subMap.ceilingKey(key); + } + + @Override + public Entry ceilingEntry(Date key) { + return subMap.floorEntry(key); + } + + @Override + public Date ceilingKey(Date key) { + return subMap.floorKey(key); + } + + @Override + public Entry higherEntry(Date key) { + return subMap.lowerEntry(key); + } + + @Override + public Date higherKey(Date key) { + return subMap.lowerKey(key); + } + + @Override + public Entry firstEntry() { + return subMap.lastEntry(); + } + + @Override + public Entry lastEntry() { + return subMap.firstEntry(); + } + + @Override + public Entry pollFirstEntry() { + return subMap.pollLastEntry(); + } + + @Override + public Entry pollLastEntry() { + return subMap.pollFirstEntry(); + } + + @Override + public NavigableMap descendingMap() { + return subMap; + } + + @Override + public NavigableSet navigableKeySet() { + return new NavigableKeySet(this); + } + + @Override + public NavigableSet descendingKeySet() { + return subMap.navigableKeySet(); + } + + @Override + public NavigableMap subMap(Date fromKey, + boolean fromInclusive, Date toKey, boolean toInclusive) { + return new DescendingMap(subMap.subMap(toKey, toInclusive, fromKey, + fromInclusive)); + } + + @Override + public NavigableMap headMap(Date toKey, boolean inclusive) { + return new DescendingMap(subMap.tailMap(toKey, inclusive)); + } + + @Override + public NavigableMap tailMap(Date fromKey, boolean inclusive) { + return new DescendingMap(subMap.headMap(fromKey, inclusive)); + } + + @Override + public NavigableMap subMap(Date fromKey, Date toKey) { + return new DescendingMap(subMap.subMap(toKey, false, fromKey, true)); + } + + @Override + public NavigableMap headMap(Date toKey) { + return new DescendingMap(subMap.tailMap(toKey, false)); + } + + @Override + public NavigableMap tailMap(Date fromKey) { + return new DescendingMap(subMap.headMap(fromKey, true)); + } + + @Override + public Set> entrySet() { + return new EntrySet(subMap.min, subMap.max, true); + } + + @Override + public boolean containsValue(Object value) { + return subMap.containsValue(value); + } + + @Override + public boolean containsKey(Object key) { + return subMap.containsKey(key); + } + + @Override + public Float get(Object key) { + return subMap.get(key); + } + + @Override + public Float put(Date key, Float value) { + return subMap.put(key, value); + } + + @Override + public Float remove(Object key) { + return subMap.remove(key); + } + + @Override + public void putAll(Map m) { + subMap.putAll(m); + } + + @Override + public void clear() { + subMap.clear(); + } + + } + + /** + * compares the behavior of the provided map to a treeMap and verify all + * operations produce equivalent results. + * + * @param map + */ + protected static void test(NavigableMap map) { + TreeMap treeMap = new TreeMap(map); + if (!map.descendingKeySet().equals(treeMap.descendingKeySet())) { + System.err.println("DescendingKeySet test failed"); + } + if (!map.navigableKeySet().equals(treeMap.navigableKeySet())) { + System.err.println("NavigableKeySet test failed"); + } + if (!map.firstKey().equals(treeMap.firstKey())) { + System.err.println("FirstKey test failed"); + } + if (!map.lastKey().equals(treeMap.lastKey())) { + System.err.println("LastKey test failed"); + } + + List testDates = new ArrayList(); + + Iterator it = map.keySet().iterator(); + Date prev = it.next(); + Date next = it.next(); + for (int i = 0; i < map.size() / 3; i += 1) { + prev = next; + next = it.next(); + } + testDates.add(next); + testDates.add(prev); + // a date about 1/3 of the way in and not equal to any keys + testDates.add(new Date(prev.getTime() / 2 + next.getTime() / 2)); + for (int i = 0; i < map.size() / 3; i += 1) { + prev = next; + next = it.next(); + } + testDates.add(next); + testDates.add(prev); + // a date about 2/3 of the way in and not equal to any keys + testDates.add(new Date(prev.getTime() / 2 + next.getTime() / 2)); + + testDates.add(map.firstKey()); + testDates.add(map.lastKey()); + // some dates just before and after the beginning and end. + testDates.add(new Date(map.firstKey().getTime() - 1000)); + testDates.add(new Date(map.firstKey().getTime() + 1000)); + testDates.add(new Date(map.lastKey().getTime() - 1000)); + testDates.add(new Date(map.lastKey().getTime() + 1000)); + for (Date test : testDates) { + Date d1 = map.ceilingKey(test); + Date d2 = treeMap.ceilingKey(test); + if (d1 == null) { + if (d2 != null) { + System.err.println("CeilingKey test failed"); + } + } else if (!d1.equals(d2)) { + System.err.println("CeilingKey test failed"); + } + } + for (Date test : testDates) { + Date d1 = map.higherKey(test); + Date d2 = treeMap.higherKey(test); + if (d1 == null) { + if (d2 != null) { + System.err.println("HigherKey test failed"); + } + } else if (!d1.equals(d2)) { + System.err.println("HigherKey test failed"); + } + } + for (Date test : testDates) { + Date d1 = map.floorKey(test); + Date d2 = treeMap.floorKey(test); + if (d1 == null) { + if (d2 != null) { + System.err.println("FloorKey test failed"); + } + } else if (!d1.equals(d2)) { + System.err.println("FloorKey test failed"); + } + } + for (Date test : testDates) { + Date d1 = map.lowerKey(test); + Date d2 = treeMap.lowerKey(test); + if (d1 == null) { + if (d2 != null) { + System.err.println("LowerKey test failed"); + } + } else if (!d1.equals(d2)) { + System.err.println("LowerKey test failed"); + } + } + for (Date test : testDates) { + NavigableMap map1 = map.headMap(test, true); + NavigableMap map2 = treeMap.headMap(test, true); + if (!map1.equals(map2)) { + System.err.println("HeadMap test failed"); + } + } + for (Date test : testDates) { + NavigableMap map1 = map.tailMap(test, false); + NavigableMap map2 = treeMap.tailMap(test, false); + if (!map1.equals(map2)) { + System.err.println("TailMap test failed"); + } + } + for (Date test1 : testDates) { + for (Date test2 : testDates) { + try { + NavigableMap map1 = map.subMap(test1, false, + test2, true); + NavigableMap map2 = treeMap.subMap(test1, + false, test2, true); + if (!map1.equals(map2)) { + System.err.println("SubMap test failed"); + } + } catch (IllegalArgumentException e) { + ;// dates aren't in order and tree map freaks. + } + } + } + for (Date test : testDates) { + map.remove(test); + treeMap.remove(test); + } + if (!map.equals(treeMap)) { + System.err.println("Remove test failed"); + } + for (Date test : testDates) { + map.put(test, 11.0f); + treeMap.put(test, 11.0f); + } + if (!map.equals(treeMap)) { + System.err.println("Put test failed"); + } + for (Date test : testDates) { + map.remove(test); + } + map.putAll(treeMap); + if (!map.equals(treeMap)) { + System.err.println("PutAll test failed"); + } + if (map instanceof ArrayBackedMap) { + test(map.descendingMap()); + } + } + +} 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 fba5e152cc..b494cf0bb6 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 @@ -20,10 +20,10 @@ package com.raytheon.uf.common.dataplugin.ffmp; import java.util.ArrayList; -import java.util.Comparator; +import java.util.Collections; import java.util.Date; -import java.util.List; import java.util.Map.Entry; +import java.util.NavigableMap; import java.util.TreeMap; import javax.persistence.Transient; @@ -31,7 +31,6 @@ import javax.persistence.Transient; import com.raytheon.uf.common.serialization.ISerializableObject; import com.raytheon.uf.common.serialization.annotations.DynamicSerialize; import com.raytheon.uf.common.serialization.annotations.DynamicSerializeElement; -import com.raytheon.uf.common.time.util.ImmutableDate; /** * FFMP basin/aggregated value holder @@ -44,6 +43,9 @@ import com.raytheon.uf.common.time.util.ImmutableDate; * ------------ ---------- ----------- -------------------------- * 06/22/09 2152 D. Hladky Initial release * 01/27/13 1478 D. Hladky Added support for writing aggregate record cache + * Apr 22, 2013 1912 bsteffen optimized the creation of NavigableMaps + * from aggregate records and delayed + * TreeMap creation to the tertiary loader. * * * @@ -65,7 +67,7 @@ public class FFMPBasin implements ISerializableObject, Cloneable { * not serialized! **/ @Transient - protected TreeMap values; + protected NavigableMap values; /** object used for serialization **/ @DynamicSerializeElement @@ -316,6 +318,12 @@ public class FFMPBasin implements ISerializableObject, Cloneable { */ 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); + } values.put(date, dvalue); } } @@ -325,7 +333,7 @@ public class FFMPBasin implements ISerializableObject, Cloneable { * * @return */ - public TreeMap getValues() { + public NavigableMap getValues() { return values; } @@ -343,13 +351,7 @@ public class FFMPBasin implements ISerializableObject, Cloneable { */ public FFMPBasin() { - values = new TreeMap(new Comparator() { - @Override - public int compare(Date o1, Date o2) { - // Null checks? - return (int)(o2.getTime() - o1.getTime()) ; - } - }); + values = new TreeMap(Collections.reverseOrder()); } /** @@ -360,14 +362,7 @@ public class FFMPBasin implements ISerializableObject, Cloneable { public FFMPBasin(Long pfaf, boolean aggregated) { setPfaf(pfaf); setAggregated(aggregated); - values = new TreeMap(new Comparator() { - @Override - public int compare(Date o1, Date o2) { - // Null checks? - return (int)(o2.getTime() - o1.getTime()) ; - } - - }); + values = new TreeMap(Collections.reverseOrder()); } /** @@ -375,18 +370,18 @@ public class FFMPBasin implements ISerializableObject, Cloneable { * * @param times */ - public void deserialize(List times) { + public void deserialize(long[] times) { // safe to avoid Array Index Exceptions / shouldn't happen but..... - if (serializedValues != null && (times.size() == serializedValues.length)) { + if (serializedValues != null + && (times.length == serializedValues.length)) { + NavigableMap fastMap = new ArrayBackedMap(times, + serializedValues); + values = fastMap.descendingMap(); - int i = 0; - for (Long time : times) { - values.put(new Date(time), serializedValues[i]); - i++; - } - //System.out.println("populated :"+i+" pfaf : "+pfaf); + // values = new TreeMap(fastMap.descendingMap()); } + serializedValues = null; } /** 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 9434e4b4da..60cf06b550 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 @@ -576,8 +576,12 @@ public class FFMPBasinData implements ISerializableObject { * @param times */ public void populate(List times) { + 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(times); + basin.deserialize(timesArr); } }