Newer
Older
ultramine_bukkit / src / main / java / org / bukkit / craftbukkit / util / LongObjectHashMap.java
@vlad20012 vlad20012 on 24 Feb 2017 9 KB initial
package org.bukkit.craftbukkit.util;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.AbstractCollection;
import java.util.AbstractSet;
import java.util.Arrays;
import java.util.Collection;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;

import static org.bukkit.craftbukkit.util.Java15Compat.Arrays_copyOf;

@SuppressWarnings("unchecked")
public class LongObjectHashMap<V> implements Cloneable, Serializable
{
	static final long serialVersionUID = 2841537710170573815L;

	private static final long EMPTY_KEY = Long.MIN_VALUE;
	private static final int BUCKET_SIZE = 4096;

	private transient long[][] keys;
	private transient V[][] values;
	private transient int modCount;
	private transient int size;
	private transient org.spigotmc.FlatMap<V> flat = new org.spigotmc.FlatMap<V>(); // Spigot

	public LongObjectHashMap()
	{
		initialize();
	}

	public LongObjectHashMap(Map<? extends Long, ? extends V> map)
	{
		this();
		putAll(map);
	}

	public int size()
	{
		return size;
	}

	public boolean isEmpty()
	{
		return size == 0;
	}

	public boolean containsKey(long key)
	{
		return get(key) != null;
	}

	public boolean containsValue(V value)
	{
		for(V val : values())
		{
			if(val == value || val.equals(value))
			{
				return true;
			}
		}

		return false;
	}

	public V get(long key)
	{
		// Spigot start
		if(size == 0)
		{
			return null;
		}
		V val = flat.get(key);
		if(val != null)
		{
			return val;
		}
		// Spigot end
		int index = (int) (keyIndex(key) & (BUCKET_SIZE - 1));
		long[] inner = keys[index];
		if(inner == null) return null;

		for(int i = 0; i < inner.length; i++)
		{
			long innerKey = inner[i];
			if(innerKey == EMPTY_KEY)
			{
				return null;
			}
			else if(innerKey == key)
			{
				return values[index][i];
			}
		}

		return null;
	}

	public V put(long key, V value)
	{
		flat.put(key, value); // Spigot
		int index = (int) (keyIndex(key) & (BUCKET_SIZE - 1));
		long[] innerKeys = keys[index];
		V[] innerValues = values[index];
		modCount++;

		if(innerKeys == null)
		{
			// need to make a new chain
			keys[index] = innerKeys = new long[8];
			Arrays.fill(innerKeys, EMPTY_KEY);
			values[index] = innerValues = (V[]) new Object[8];
			innerKeys[0] = key;
			innerValues[0] = value;
			size++;
		}
		else
		{
			int i;
			for(i = 0; i < innerKeys.length; i++)
			{
				// found an empty spot in the chain to put this
				if(innerKeys[i] == EMPTY_KEY)
				{
					size++;
					innerKeys[i] = key;
					innerValues[i] = value;
					return null;
				}

				// found an existing entry in the chain with this key, replace it
				if(innerKeys[i] == key)
				{
					V oldValue = innerValues[i];
					innerKeys[i] = key;
					innerValues[i] = value;
					return oldValue;
				}
			}

			// chain is full, resize it and add our new entry
			keys[index] = innerKeys = Arrays_copyOf(innerKeys, i << 1);
			Arrays.fill(innerKeys, i, innerKeys.length, EMPTY_KEY);
			values[index] = innerValues = Arrays_copyOf(innerValues, i << 1);
			innerKeys[i] = key;
			innerValues[i] = value;
			size++;
		}

		return null;
	}

	public V remove(long key)
	{
		flat.remove(key); // Spigot
		int index = (int) (keyIndex(key) & (BUCKET_SIZE - 1));
		long[] inner = keys[index];
		if(inner == null)
		{
			return null;
		}

		for(int i = 0; i < inner.length; i++)
		{
			// hit the end of the chain, didn't find this entry
			if(inner[i] == EMPTY_KEY)
			{
				break;
			}

			if(inner[i] == key)
			{
				V value = values[index][i];

				for(i++; i < inner.length; i++)
				{
					if(inner[i] == EMPTY_KEY)
					{
						break;
					}

					inner[i - 1] = inner[i];
					values[index][i - 1] = values[index][i];
				}

				inner[i - 1] = EMPTY_KEY;
				values[index][i - 1] = null;
				size--;
				modCount++;
				return value;
			}
		}

		return null;
	}

	public void putAll(Map<? extends Long, ? extends V> map)
	{
		for(Map.Entry entry : map.entrySet())
		{
			put((Long) entry.getKey(), (V) entry.getValue());
		}
	}

	public void clear()
	{
		if(size == 0)
		{
			return;
		}

		modCount++;
		size = 0;
		Arrays.fill(keys, null);
		Arrays.fill(values, null);
		flat = new org.spigotmc.FlatMap<V>();
	}

	public Set<Long> keySet()
	{
		return new KeySet();
	}

	public Collection<V> values()
	{
		return new ValueCollection();
	}

	/**
	 * Returns a Set of Entry objects for the HashMap. This is not how the internal
	 * implementation is laid out so this constructs the entire Set when called. For
	 * this reason it should be avoided if at all possible.
	 *
	 * @return Set of Entry objects
	 */
	public Set<Map.Entry<Long, V>> entrySet()
	{
		return new EntrySet();
	}

	public Object clone() throws CloneNotSupportedException
	{
		LongObjectHashMap clone = (LongObjectHashMap) super.clone();
		// Make sure we clear any existing information from the clone
		clone.clear();
		// Make sure the clone is properly setup for new entries
		clone.initialize();

		// Iterate through the data normally to do a safe clone
		for(long key : keySet())
		{
			final V value = get(key);
			clone.put(key, value);
		}

		return clone;
	}

	private void initialize()
	{
		keys = new long[BUCKET_SIZE][];
		values = (V[][]) new Object[BUCKET_SIZE][];
	}

	private long keyIndex(long key)
	{
		key ^= key >>> 33;
		key *= 0xff51afd7ed558ccdL;
		key ^= key >>> 33;
		key *= 0xc4ceb9fe1a85ec53L;
		key ^= key >>> 33;
		return key;
	}

	private void writeObject(ObjectOutputStream outputStream) throws IOException
	{
		outputStream.defaultWriteObject();

		for(long key : keySet())
		{
			V value = get(key);
			outputStream.writeLong(key);
			outputStream.writeObject(value);
		}

		outputStream.writeLong(EMPTY_KEY);
		outputStream.writeObject(null);
	}

	private void readObject(ObjectInputStream inputStream) throws ClassNotFoundException, IOException
	{
		inputStream.defaultReadObject();
		initialize();

		while(true)
		{
			long key = inputStream.readLong();
			V value = (V) inputStream.readObject();
			if(key == EMPTY_KEY && value == null)
			{
				break;
			}

			put(key, value);
		}
	}


	private class ValueIterator implements Iterator<V>
	{
		private int count;
		private int index;
		private int innerIndex;
		private int expectedModCount;
		private long lastReturned = EMPTY_KEY;

		long prevKey = EMPTY_KEY;
		V prevValue;

		ValueIterator()
		{
			expectedModCount = LongObjectHashMap.this.modCount;
		}

		public boolean hasNext()
		{
			return count < LongObjectHashMap.this.size;
		}

		public void remove()
		{
			if(LongObjectHashMap.this.modCount != expectedModCount)
			{
				throw new ConcurrentModificationException();
			}

			if(lastReturned == EMPTY_KEY)
			{
				throw new IllegalStateException();
			}

			count--;
			LongObjectHashMap.this.remove(lastReturned);
			lastReturned = EMPTY_KEY;
			expectedModCount = LongObjectHashMap.this.modCount;
		}

		public V next()
		{
			if(LongObjectHashMap.this.modCount != expectedModCount)
			{
				throw new ConcurrentModificationException();
			}

			if(!hasNext())
			{
				throw new NoSuchElementException();
			}

			long[][] keys = LongObjectHashMap.this.keys;
			count++;

			if(prevKey != EMPTY_KEY)
			{
				innerIndex++;
			}

			for(; index < keys.length; index++)
			{
				if(keys[index] != null)
				{
					for(; innerIndex < keys[index].length; innerIndex++)
					{
						long key = keys[index][innerIndex];
						V value = values[index][innerIndex];
						if(key == EMPTY_KEY)
						{
							break;
						}

						lastReturned = key;
						prevKey = key;
						prevValue = value;
						return prevValue;
					}
					innerIndex = 0;
				}
			}

			throw new NoSuchElementException();
		}
	}

	private class KeyIterator implements Iterator<Long>
	{
		final ValueIterator iterator;

		public KeyIterator()
		{
			iterator = new ValueIterator();
		}

		public void remove()
		{
			iterator.remove();
		}

		public boolean hasNext()
		{
			return iterator.hasNext();
		}

		public Long next()
		{
			iterator.next();
			return iterator.prevKey;
		}
	}


	private class KeySet extends AbstractSet<Long>
	{
		public void clear()
		{
			LongObjectHashMap.this.clear();
		}

		public int size()
		{
			return LongObjectHashMap.this.size();
		}

		public boolean contains(Object key)
		{
			return key instanceof Long && LongObjectHashMap.this.containsKey((Long) key);

		}

		public boolean remove(Object key)
		{
			return LongObjectHashMap.this.remove((Long) key) != null;
		}

		public Iterator<Long> iterator()
		{
			return new KeyIterator();
		}
	}


	private class ValueCollection extends AbstractCollection<V>
	{
		public void clear()
		{
			LongObjectHashMap.this.clear();
		}

		public int size()
		{
			return LongObjectHashMap.this.size();
		}

		public boolean contains(Object value)
		{
			return LongObjectHashMap.this.containsValue((V) value);
		}

		public Iterator<V> iterator()
		{
			return new ValueIterator();
		}
	}


	private class Entry implements Map.Entry<Long, V>
	{
		private Long key;
		private V value;

		public Long getKey()
		{
			return key;
		}

		public V getValue()
		{
			return value;
		}

		public V setValue(V v)
		{
			V old = value;
			value = v;
			put(key, v);
			return old;
		}

		private void bind(long key, V value)
		{
			this.key = key;
			this.value = value;
		}
	}

	private class EntrySet extends AbstractSet<Map.Entry<Long, V>>
	{
		@Override
		public Iterator<Map.Entry<Long, V>> iterator()
		{
			return new Iterator<Map.Entry<Long, V>>()
			{
				final Entry entry = new Entry();
				final ValueIterator valueIterator = new ValueIterator();

				@Override
				public boolean hasNext()
				{
					return valueIterator.hasNext();
				}

				@Override
				public LongObjectHashMap<V>.Entry next()
				{
					V value = valueIterator.next();
					entry.bind(valueIterator.prevKey, value);
					return entry;
				}

				@Override
				public void remove()
				{
					valueIterator.remove();
				}
			};
		}

		@Override
		public int size()
		{
			return LongObjectHashMap.this.size;
		}
	}
}