package de.blau.android.util.collections; import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; import android.annotation.SuppressLint; import android.os.Build; import de.blau.android.osm.OsmElement; import de.blau.android.osm.OsmElementFactory; /** * long to OsmElement HashMap * * This only implements the, roughly equivalent to Map, interfaces that we * current (might) need and is rather Vespucci specific, however could easily be * adopted to different implementations of an OSM element as long as you can * easily get the element id. * * This implementation will only require at most 8*next_power_of_2(capacity/fillfactor) * space. * * This is based on public domain code see http://unlicense.org from Mikhail * Vorontsov, see https://github.com/mikvor The original code used a interleaved * key/value implementation in one array, however since the stored value already * contains the key additional copies are not needed. * * This code is not thread safe with the exception of iterating over the array when rehashing * and requires external synchronization if inserts and removals need to be consistent. * * @version 0.3 * @author simon */ @SuppressLint("UseSparseArrays") public class LongOsmElementMap<V extends OsmElement> implements Iterable<V>, Serializable { /** * */ private static final long serialVersionUID = 2L; // NOTE if you change the // hashing algorithm you // need to increment // this private static final OsmElement FREE_KEY = null; private final OsmElement removedKey; // Note see constructor for important note private static final float DEFAULT_FILLFACTOR = 0.75f; private static final int DEFAULT_CAPACITY = 16; /** Keys and values */ private OsmElement[] m_data; /** Fill factor, must be between (0 and 1) */ private final float m_fillFactor; /** We will resize a map once it reaches this size */ private int m_threshold; /** Current map size */ private int m_size; /** Mask to calculate the original position */ private long m_mask; /** * Create a new map with default values for capacity and fill factor */ public LongOsmElementMap() { this(DEFAULT_CAPACITY, DEFAULT_FILLFACTOR); } /** * Create a new map with the specified size and the default fill factor * @param size */ public LongOsmElementMap(final int size) { this(size, DEFAULT_FILLFACTOR); } /** * Create a new map with the specified size and fill factor * @param size * @param fillFactor */ private LongOsmElementMap(final int size, final float fillFactor) { if (fillFactor <= 0 || fillFactor >= 1) { throw new IllegalArgumentException("FillFactor must be in (0, 1)"); } if (size <= 0) { throw new IllegalArgumentException("Size must be positive!"); } final int capacity = Tools.arraySize(size, fillFactor); m_mask = capacity - 1; m_fillFactor = fillFactor; m_data = new OsmElement[capacity]; m_threshold = (int) (capacity * fillFactor); // NOTE can't be static as it has to be serialized and de-serialized removedKey = OsmElementFactory.createNode(Long.MIN_VALUE, 1, OsmElement.STATE_CREATED, 0, 0); } /** * Create a shallow copy of the specified map * @param map */ @SuppressLint("NewApi") public LongOsmElementMap(LongOsmElementMap<? extends V> map) { m_mask = map.m_mask; m_fillFactor = map.m_fillFactor; m_threshold = map.m_threshold; m_size = map.m_size; removedKey = map.removedKey; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) { m_data = Arrays.copyOf(map.m_data, map.m_data.length); } else { // sigh m_data = new OsmElement[map.m_data.length]; //noinspection ManualArrayCopy for (int i = 0; i < m_data.length; i++) { m_data[i] = map.m_data[i]; } } } /** * Return a single element with the specified key * @param key * @return the required element or null if it cannot be found */ @SuppressWarnings("unchecked") public V get(final long key) { int ptr = (int) ((Tools.phiMix(key) & m_mask)); OsmElement e = m_data[ptr]; if (e == FREE_KEY) { return null; } if (e.getOsmId() == key) { return (V) e; } while (true) { ptr = (int) ((ptr + 1) & m_mask); // that's next index e = m_data[ptr]; if (e == FREE_KEY) { return null; } if (e.getOsmId() == key) { return (V) e; } } } /** * Add a single element to the map * @param key * @param value * @return */ @SuppressWarnings("unchecked") public V put(final long key, final V value) { int ptr = (int) ((Tools.phiMix(key) & m_mask)); OsmElement e = m_data[ptr]; if (e == FREE_KEY) // end of chain already { m_data[ptr] = (OsmElement) value; if (m_size >= m_threshold) { rehash(m_data.length * 2); // size is set inside } else { ++m_size; } return null; } else if (e.getOsmId() == key) // we check FREE and REMOVED prior to this call { m_data[ptr] = (OsmElement) value; return (V) e; } int firstRemoved = -1; if (e == removedKey) { firstRemoved = ptr; // we may find a key later } while (true) { ptr = (int) ((ptr + 1) & m_mask); // the next index calculation e = m_data[ptr]; if (e == FREE_KEY) { if (firstRemoved != -1) { ptr = firstRemoved; } m_data[ptr] = (OsmElement) value; if (m_size >= m_threshold) { rehash(m_data.length * 2); // size is set inside } else { ++m_size; } return null; } else if (e.getOsmId() == key) { m_data[ptr] = (OsmElement) value; return (V) e; } else if (e == removedKey) { if (firstRemoved == -1) { firstRemoved = ptr; } } } } /** * Add all elements from map * @param map */ public void putAll(LongOsmElementMap<V> map) { ensureCapacity(m_data.length + map.size()); for (V e : map) { // trivial implementation for now put(e.getOsmId(), e); } } /** * Add all elements from c * @param c */ public void putAll(Collection<V> c) { ensureCapacity(m_data.length + c.size()); for (V e : c) { // trivial implementation for now put(e.getOsmId(), e); } } /** * Remove element with the specified key from the map, * does not shrink the underlying array * @param key * @return */ public OsmElement remove(final long key) { int ptr = (int) (Tools.phiMix(key) & m_mask); OsmElement e = m_data[ptr]; if (e == FREE_KEY) { return null; // end of chain already } else if (e.getOsmId() == key) // we check FREE and REMOVED prior to this call { --m_size; if (m_data[(int) ((ptr + 1) & m_mask)] == FREE_KEY) { // this shortens the chain m_data[ptr] = FREE_KEY; } else { m_data[ptr] = removedKey; } return e; } while (true) { ptr = (int) ((ptr + 1) & m_mask); // that's next index calculation e = m_data[ptr]; if (e == FREE_KEY) { return null; } else if (e.getOsmId() == key) { --m_size; if (m_data[(int) ((ptr + 1) & m_mask)] == FREE_KEY) { // this shortens the chain m_data[ptr] = FREE_KEY; } else { m_data[ptr] = removedKey; } return e; } } } /** * Return true if the map contains an object with the specified key * @param key * @return */ public boolean containsKey(long key) { int ptr = (int) ((Tools.phiMix(key) & m_mask)); OsmElement e = m_data[ptr]; if (e == FREE_KEY) { return false; } if (e.getOsmId() == key) { // note this assumes REMOVED_KEY doesn't match return true; } while (true) { ptr = (int) ((ptr + 1) & m_mask); // the next index e = m_data[ptr]; if (e == FREE_KEY) { return false; } if (e.getOsmId() == key) { return true; } } } /** * Return all values in the map. * Note: they are returned unordered * @return */ @SuppressWarnings("unchecked") public List<V> values() { int found = 0; ArrayList<V> result = new ArrayList<V>(m_size); for (OsmElement v : m_data) { if (v != FREE_KEY && v != removedKey) { result.add((V) v); found++; if (found >= m_size) { // found all break; } } } return result; } /** * Return the number of elements in the map * @return */ public int size() { return m_size; } /** * Return true if the map is empty * @return */ public boolean isEmpty() { return m_size == 0; } /** * Provide capacity for minimumCapacity elements * without need for growing the underlying array and * rehashing. * @param minimumCapacity */ private void ensureCapacity(int minimumCapacity) { int newCapacity = Tools.arraySize(minimumCapacity, m_fillFactor); if (newCapacity > m_data.length) { rehash(newCapacity); } } @SuppressWarnings("unchecked") private void rehash(final int newCapacity) { synchronized(this) { m_threshold = (int) (newCapacity * m_fillFactor); m_mask = newCapacity - 1; final int oldCapacity = m_data.length; final OsmElement[] oldData = m_data; m_data = new OsmElement[newCapacity]; m_size = 0; for (int i = 0; i < oldCapacity; i++) { final OsmElement e = oldData[i]; if (e != FREE_KEY && e != removedKey) { put(e.getOsmId(), (V) e); } } } } /** * Rehash the map - needed when id's have changed etc. */ @SuppressWarnings("unchecked") public void rehash() { synchronized(this) { final OsmElement[] oldData = m_data; m_data = new OsmElement[m_data.length]; m_size = 0; for (int i = 0; i < m_data.length; i++) { final OsmElement e = oldData[i]; if (e != FREE_KEY && e != removedKey) { put(e.getOsmId(), (V) e); } } } } /** * Iterator that skips FREE_KEY and REMOVED_KEY values */ @Override public Iterator<V> iterator() { return new SafeIterator(); } class SafeIterator implements Iterator<V> { int index = 0; int found = 0; int m_size_temp = 0; OsmElement[] m_data_temp = null; OsmElement cachedNext = null; SafeIterator() { synchronized(LongOsmElementMap.this) { m_size_temp = m_size; m_data_temp = m_data; } } @Override public boolean hasNext() { cachedNext = null; while (true) { if (found >= m_size_temp || index >= m_data_temp.length) { // already returned all elements return false; } else { OsmElement e = m_data_temp[index]; if (e != FREE_KEY && e != removedKey) { found++; cachedNext = e; return true; } else { index++; } } } } @SuppressWarnings("unchecked") @Override public V next() { if (cachedNext != null) { index++; return (V)cachedNext; } while (true) { if (index >= m_data_temp.length) { // already returned all elements throw new NoSuchElementException(); } else { OsmElement e = m_data_temp[index]; if (e != FREE_KEY && e != removedKey) { index++; return (V) e; } else { index++; } } } } @Override public void remove() { throw new UnsupportedOperationException(); // could be implemented } } /** * for stats and debugging * * @return */ public HashMap<Integer, Integer> getChainStats() { HashMap<Integer, Integer> result = new HashMap<Integer, Integer>(); for (V v : values()) { int len = 0; long key = v.getOsmId(); int ptr = (int) ((Tools.phiMix(key) & m_mask)); OsmElement e = m_data[ptr]; if (e.getOsmId() == key) { // note this assumes REMOVED_KEY doesn't match if (result.containsKey(len)) { result.put(len, result.get(len) + 1); } else { result.put(len, 1); } continue; } while (true) { len++; ptr = (int) ((ptr + 1) & m_mask); // the next index e = m_data[ptr]; if (e.getOsmId() == key) { if (result.containsKey(len)) { result.put(len, result.get(len) + 1); } else { result.put(len, 1); } break; } } } return result; } }