Skip to content

Instantly share code, notes, and snippets.

@DarkSeraphim
Created June 24, 2014 13:56
Show Gist options
  • Save DarkSeraphim/127dace8d32708a5c971 to your computer and use it in GitHub Desktop.
Save DarkSeraphim/127dace8d32708a5c971 to your computer and use it in GitHub Desktop.
Later, synchronized implementation. Might add MapMaker in the future
package com.aaomidi.dev.skyblock.engine.objects;
import lombok.EqualsAndHashCode;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang.Validate;
import java.util.AbstractSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author DarkSeraphim
*/
public class MultiKeyMap<K, L, V>
{
private final Class<K> k;
private final Class<L> l;
private final Map<K, Entry<K, L, V>> kv = new ConcurrentHashMap<K, Entry<K, L, V>>();
private final Map<L, Entry<K, L, V>> lv = new ConcurrentHashMap<L, Entry<K, L, V>>();
private Entry<K, L, V> head;
private MultiKeyMap(Class<K> k, Class<L> l)
{
this.k = k;
this.l = l;
}
public static <K, L, V> MultiKeyMap<K, L, V> create(Class<K> k, Class<L> l, Class<V> v)
{
if (k == l)
{
throw new IllegalArgumentException("K cannot be the same as L");
}
return new MultiKeyMap<K, L, V>(k, l);
}
synchronized public V get(Object key)
{
Validate.notNull(key, "null keys not supported");
if (key.getClass().isAssignableFrom(this.k))
{
return this.getK((K) key);
}
else if (key.getClass().isAssignableFrom(this.l))
{
return this.getL((L) key);
}
throw new ClassCastException(String.format("Cannot cast %s to %s nor %s", key.getClass().toString(), this.k.toString(), this.l.toString()));
}
private V getK(K key)
{
Entry<K, L, V> e = this.kv.get(key);
return e != null ? (V) e.getValue() : null;
}
private V getL(L key)
{
Entry<K, L, V> e = this.lv.get(key);
return e != null ? (V) e.getValue() : null;
}
synchronized public V put(K key, L key2, V val)
{
Entry<K, L, V> entry = new Entry<K, L, V>(key, key2, val);
if (this.head != null)
{
if (this.head.equals(entry))
{
V old = (V) this.head.getValue();
this.head.setValue(val);
return old;
}
entry.next = this.head;
entry.prev = null;
}
this.head = entry;
Entry<K, L, V> a = this.kv.put(key, entry);
Entry<K, L, V> b = this.lv.put(key2, entry);
if (a != b)
{
throw new IllegalStateException("Contract violated");
}
// Link the next and prev Entries
fixEntry(a);
return a != null ? (V) a.getValue() : null;
}
synchronized public V remove(Object key)
{
Validate.notNull(key, "null keys not supported");
if (key.getClass().isAssignableFrom(this.k))
{
return this.removeK((K) key);
}
else if (key.getClass().isAssignableFrom(this.l))
{
return this.removeL((L) key);
}
throw new ClassCastException(String.format("Cannot cast %s to %s nor %s", key.getClass().toString(), this.k.toString(), this.l.toString()));
}
private V removeK(K key)
{
// Get entry, remove entry
Entry<K, L, V> a = this.kv.remove(key);
Entry<K, L, V> b = null;
if (a != null)
b = this.lv.remove(a.getSecondKey());
if (a != b)
throw new IllegalStateException("Contract violated");
fixEntry(a);
return a != null ? (V) a.getValue() : null;
}
private V removeL(L key) {
// Get entry, remove entry
Entry<K, L, V> a = this.lv.remove(key);
Entry<K, L, V> b = null;
if (a != null)
b = this.kv.remove(a.getKey());
if (a != b)
throw new IllegalStateException("Contract violated");
fixEntry(a);
return a != null ? (V) a.getValue() : null;
}
synchronized public boolean containsKey(Object key)
{
Validate.notNull(key, "null keys not supported");
if (key.getClass().isAssignableFrom(this.k))
{
return this.containsKeyK((K) key);
}
else if (key.getClass().isAssignableFrom(this.l))
{
return this.containsKeyL((L) key);
}
throw new ClassCastException(String.format("Cannot cast %s to %s nor %s", key.getClass().toString(), this.k.toString(), this.l.toString()));
}
synchronized public boolean containsKeyK(K key)
{
Entry<K, L, V> entry = this.kv.get(key);
if (entry != null && this.lv.get(entry.getSecondKey()) == null)
{
throw new IllegalStateException("Contract violated");
}
return entry != null;
}
synchronized public boolean containsKeyL(L key)
{
Entry<K, L, V> entry = this.lv.get(key);
if (entry != null && this.kv.get(entry.getKey()) == null)
{
throw new IllegalStateException("Contract violated");
}
return entry != null;
}
private void fixEntry(Entry<K, L, V> entry)
{
if (entry != null)
{
if (entry.next != null)
{
entry.next.prev = entry.prev;
}
if (entry.prev != null)
{
entry.prev.next = entry.next;
}
}
}
public Set<Entry<K, L, V>> entrySet()
{
return new AbstractSet<Entry<K, L, V>>()
{
@Override
public Iterator<Entry<K, L, V>> iterator()
{
return new EntryIterator(MultiKeyMap.this);
}
@Override
public int size()
{
return MultiKeyMap.this.kv.size();
}
};
}
public boolean isEmpty()
{
boolean ret = this.kv.isEmpty();
if (this.lv.isEmpty() != ret)
throw new IllegalStateException("Contract violated");
return ret;
}
@EqualsAndHashCode
public class Entry<K, L, V> implements Map.Entry
{
private final K k;
private final L l;
private transient Entry<K, L, V> prev;
private transient Entry<K, L, V> next;
private transient volatile V v;
private Entry(K k, L l, V v)
{
this.k = k;
this.l = l;
this.v = v;
}
/**
* Returns key K for the Entry
*
* @return
*/
@Override
public Object getKey()
{
return this.k;
}
/**
* Returns key L for the entry
*
* @return
*/
public Object getSecondKey()
{
return this.l;
}
@Override
public Object getValue()
{
return this.v;
}
@Override
public Object setValue(Object value)
{
V old = this.v;
this.v = (V) value;
return old;
}
private void remove(Map<K, ?> a, Map<L, ?> b)
{
a.remove(this.getKey());
b.remove(this.getSecondKey());
}
}
@RequiredArgsConstructor
class EntryIterator implements Iterator<Entry<K, L, V>>
{
private final MultiKeyMap backend;
Entry<K, L, V> current;
private boolean before = true;
@Override
public boolean hasNext()
{
return before ? backend.head != null : current.next != null;
}
@Override
public Entry<K, L, V> next()
{
if (!hasNext())
throw new IllegalStateException("No next entry found");
current = before ? backend.head : current.next;
if (before)
before = false;
return current;
}
@Override
public void remove()
{
current.remove(backend.kv, backend.lv);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment