/* Copyright (c) 2008-2014, Avian Contributors Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. There is NO WARRANTY for this software. See license.txt for details. */ package java.util.concurrent; import avian.Data; import avian.PersistentSet; import avian.PersistentSet.Path; import sun.misc.Unsafe; import java.util.AbstractMap; import java.util.Map; import java.util.Set; import java.util.Collection; import java.util.Iterator; import java.util.NoSuchElementException; public class ConcurrentHashMap extends AbstractMap implements ConcurrentMap { private static final Unsafe unsafe = Unsafe.getUnsafe(); private static final long Content; private static final Content Empty = new Content(new PersistentSet(), 0); static { try { Content = unsafe.objectFieldOffset (ConcurrentHashMap.class.getDeclaredField("content")); } catch (NoSuchFieldException e) { throw new Error(e); } } private volatile Content content; public ConcurrentHashMap() { content = Empty; } public ConcurrentHashMap(int initialCapacity) { this(); } public ConcurrentHashMap(int initialCapacity, float loadFactor) { this(); } public ConcurrentHashMap(int initialCapacity, float loadFactor, int concurrencyLevel) { this(); } public boolean isEmpty() { return content.size == 0; } public int size() { return content.size; } public boolean containsKey(Object key) { return find(key) != null; } public boolean containsValue(Object value) { for (V v: values()) { if (value.equals(v)) { return true; } } return false; } public V get(Object key) { Cell cell = find(key); return cell == null ? null : cell.value; } private Cell find(Object key) { Content c = content; Path>> path = c.set.find(new Node(key.hashCode())); for (Cell cell = path.value().value; cell != null; cell = cell.next) { if (key.equals(cell.key)) { return cell; } } return null; } public V putIfAbsent(K key, V value) { Cell cell = put(key, value, PutCondition.IfAbsent, null); return cell == null ? null : cell.value; } public boolean remove(K key, V value) { Cell cell = remove(key, RemoveCondition.IfEqual, value); return cell != null && cell.value.equals(value); } public V replace(K key, V value) { Cell cell = put(key, value, PutCondition.IfPresent, null); return cell == null ? null : cell.value; } public boolean replace(K key, V oldValue, V newValue) { Cell cell = put(key, newValue, PutCondition.IfEqual, oldValue); return cell != null && cell.value.equals(oldValue); } public V put(K key, V value) { Cell cell = put(key, value, PutCondition.Always, null); return cell == null ? null : cell.value; } public V remove(Object key) { Cell cell = remove(key, RemoveCondition.Always, null); return cell == null ? null : cell.value; } private enum PutCondition { Always() { public boolean addIfAbsent() { return true; } public boolean addIfPresent(V a, V b) { return true; } }, IfAbsent() { public boolean addIfAbsent() { return true; } public boolean addIfPresent(V a, V b) { return false; } }, IfPresent() { public boolean addIfAbsent() { return false; } public boolean addIfPresent(V a, V b) { return true; } }, IfEqual() { public boolean addIfAbsent() { return false; } public boolean addIfPresent(V a, V b) { return a.equals(b); } }; public boolean addIfAbsent() { throw new AssertionError(); } public boolean addIfPresent(V a, V b) { throw new AssertionError(); } } private enum RemoveCondition { Always() { public boolean remove(V a, V b) { return true; } }, IfEqual() { public boolean remove(V a, V b) { return a.equals(b); } }; public boolean remove(V a, V b) { throw new AssertionError(); } } private Cell put(K key, V value, PutCondition condition, V oldValue) { Node> node = new Node(key.hashCode()); loop: while (true) { node.value = null; Content content = this.content; Path>> path = content.set.find(node); for (Cell cell = path.value().value; cell != null; cell = cell.next) { if (key.equals(cell.key)) { if (! condition.addIfPresent(cell.value, oldValue)) { return cell; } Cell start = null; Cell last = null; for (Cell cell2 = path.value().value; true; cell2 = cell2.next) { Cell c; c = cell2.clone(); if (last == null) { last = start = c; } else { last.next = c; last = c; } if (cell2 == cell) { c.value = value; break; } } node.value = start; if (unsafe.compareAndSwapObject (this, Content, content, new Content (path.replaceWith(node), content.size))) { return cell; } else { continue loop; } } } // no mapping found -- add a new one if appropriate if (! condition.addIfAbsent()) { return null; } node.value = new Cell(key, value, null); if (unsafe.compareAndSwapObject (this, Content, content, new Content (path.fresh() ? path.add() : path.replaceWith(node), content.size + 1))) { return null; } } } public void putAll(Map map) { for (Map.Entry e: map.entrySet()) { put(e.getKey(), e.getValue()); } } private Cell remove(Object key, RemoveCondition condition, V oldValue) { Node> node = new Node(key.hashCode()); loop: while (true) { node.value = null; Content content = this.content; Path>> path = content.set.find(node); for (Cell cell = path.value().value; cell != null; cell = cell.next) { if (key.equals(cell.key)) { if (! condition.remove(cell.value, oldValue)) { return cell; } Cell start = null; Cell last = null; for (Cell cell2 = path.value().value; cell2 != cell; cell2 = cell2.next) { Cell c = cell2.clone(); if (last == null) { last = start = c; } else { last.next = c; last = c; } } if (last == null) { start = last = cell.next; } else { last.next = cell.next; } node.value = start; if (unsafe.compareAndSwapObject (this, Content, content, new Content (start == null ? path.remove() : path.replaceWith(node), content.size - 1))) { return cell; } else { continue loop; } } } return null; } } public void clear() { content = Empty; } public Set> entrySet() { return new Data.EntrySet(new MyEntryMap()); } public Set keySet() { return new Data.KeySet(new MyEntryMap()); } public Collection values() { return new Data.Values(new MyEntryMap()); } private class MyEntryMap implements Data.EntryMap { public int size() { return ConcurrentHashMap.this.size(); } public Map.Entry find(Object key) { return new MyEntry(ConcurrentHashMap.this.find(key)); } public Map.Entry remove(Object key) { return new MyEntry (ConcurrentHashMap.this.remove(key, RemoveCondition.Always, null)); } public void clear() { ConcurrentHashMap.this.clear(); } public Iterator> iterator() { return new MyIterator(content); } } private static class Content { private final PersistentSet>> set; private final int size; public Content(PersistentSet>> set, int size) { this.set = set; this.size = size; } } private static class Cell implements Cloneable { public final K key; public V value; public Cell next; public Cell(K key, V value, Cell next) { this.key = key; this.value = value; this.next = next; } public Cell clone() { try { return (Cell) super.clone(); } catch (CloneNotSupportedException e) { throw new AssertionError(); } } } private static class Node implements Comparable> { public final int key; public T value; public Node(int key) { this.key = key; } public int compareTo(Node n) { return key - n.key; } } private class MyEntry implements Map.Entry { private final K key; private V value; public MyEntry(Cell cell) { key = cell.key; value = cell.value; } public K getKey() { return key; } public V getValue() { return value; } public V setValue(V value) { V v = value; this.value = value; put(key, value); return v; } } private class MyIterator implements Iterator> { private final Content content; private final Iterator>> iterator; private Cell currentCell; private Cell nextCell; public MyIterator(Content content) { this.content = content; this.iterator = content.set.iterator(); hasNext(); } public Map.Entry next() { if (hasNext()) { currentCell = nextCell; nextCell = nextCell.next; return new MyEntry(currentCell); } else { throw new NoSuchElementException(); } } public boolean hasNext() { if (nextCell == null && iterator.hasNext()) { nextCell = iterator.next().value; } return nextCell != null; } public void remove() { if (currentCell != null) { ConcurrentHashMap.this.remove (currentCell.key, RemoveCondition.Always, null); currentCell = null; } else { throw new IllegalStateException(); } } } }