org.voltcore.utils.COWMap Maven / Gradle / Ivy
/* This file is part of VoltDB.
* Copyright (C) 2008-2020 VoltDB Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with VoltDB. If not, see .
*/
package org.voltcore.utils;
import java.util.Map;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicReference;
import com.google_voltpatches.common.base.Preconditions;
import com.google_voltpatches.common.collect.ForwardingMap;
import com.google_voltpatches.common.collect.ImmutableMap;
import com.google_voltpatches.common.collect.ImmutableMap.Builder;
/**
* Key set, value set, and entry set are all immutable as are their iterators.
* Otherwise behaves as you would expect.
*/
public class COWMap extends ForwardingMap implements ConcurrentMap {
private final AtomicReference> m_map;
public COWMap() {
m_map = new AtomicReference>(new Builder().build());
}
public COWMap(Map map) {
if (map == null) {
throw new IllegalArgumentException("Wrapped map cannot be null");
}
m_map = new AtomicReference>(new Builder().putAll(map).build());
}
@Override
public V put(K key, V value) {
while (true) {
ImmutableMap original = m_map.get();
Builder builder = new Builder();
V oldValue = null;
boolean replaced = false;
for (Map.Entry entry : original.entrySet()) {
if (entry.getKey().equals(key)) {
oldValue = entry.getValue();
builder.put(key, value);
replaced = true;
} else {
builder.put(entry);
}
}
if (!replaced) {
builder.put(key, value);
}
ImmutableMap copy = builder.build();
if (m_map.compareAndSet(original, copy)) {
return oldValue;
}
}
}
@Override
public V remove(Object key) {
Preconditions.checkNotNull(key);
while (true) {
ImmutableMap original = m_map.get();
Builder builder = new Builder();
V oldValue = null;
for (Map.Entry entry : original.entrySet()) {
if (entry.getKey().equals(key)) {
oldValue = entry.getValue();
} else {
builder.put(entry);
}
}
ImmutableMap copy = builder.build();
if (m_map.compareAndSet(original,copy)) {
return oldValue;
}
}
}
@Override
public void putAll(Map m) {
while (true) {
ImmutableMap original = m_map.get();
Builder builder = new Builder();
for (Map.Entry entry : original.entrySet()) {
if (!m.containsKey(entry.getKey())) {
builder.put(entry);
}
}
builder.putAll(m);
ImmutableMap copy = builder.build();
if (m_map.compareAndSet(original, copy)) {
return;
}
}
}
@Override
public void clear() {
m_map.set(new Builder().build());
}
@Override
protected Map delegate() {
return m_map.get();
}
@Override
public V putIfAbsent(K key, V value) {
V existingValue;
while ((existingValue = get(key)) == null) {
ImmutableMap original = m_map.get();
if ((existingValue = original.get(key)) != null) break;
Builder builder = new Builder();
for (Map.Entry entry : original.entrySet()) {
if (entry.getKey().equals(key)) {
throw new RuntimeException("Shouldn't happen already checked");
} else {
builder.put(entry);
}
}
builder.put(key, value);
ImmutableMap copy = builder.build();
if (m_map.compareAndSet(original, copy)) {
break;
}
}
return existingValue;
}
@Override
public V get(Object key) {
Preconditions.checkNotNull(key);
return delegate().get(key);
}
@Override
public boolean containsKey(Object key) {
Preconditions.checkNotNull(key);
return delegate().containsKey(key);
}
@Override
public boolean containsValue(Object value) {
Preconditions.checkNotNull(value);
return delegate().containsValue(value);
}
@Override
public boolean remove(Object key, Object value) {
Preconditions.checkNotNull(key);
if (value == null) return false;
while (true) {
ImmutableMap original = m_map.get();
V existingValue = original.get(key);
if (existingValue == null) break;
if (!existingValue.equals(value)) break;
Builder builder = new Builder();
for (Map.Entry entry : original.entrySet()) {
if (entry.getKey().equals(key)) {
continue;
} else {
builder.put(entry);
}
}
ImmutableMap copy = builder.build();
if (m_map.compareAndSet(original, copy)) {
return true;
}
}
return false;
}
@Override
public boolean replace(K key, V oldValue, V newValue) {
Preconditions.checkNotNull(key);
Preconditions.checkNotNull(oldValue);
Preconditions.checkNotNull(newValue);
while (true) {
ImmutableMap original = m_map.get();
V existingValue = original.get(key);
if (existingValue == null) break;
if (!existingValue.equals(oldValue)) break;
Builder builder = new Builder();
for (Map.Entry entry : original.entrySet()) {
if (entry.getKey().equals(key)) {
continue;
} else {
builder.put(entry);
}
}
builder.put(key, newValue);
ImmutableMap copy = builder.build();
if (m_map.compareAndSet(original, copy)) {
return true;
}
}
return false;
}
@Override
public V replace(K key, V value) {
Preconditions.checkNotNull(key);
Preconditions.checkNotNull(value);
while (true) {
ImmutableMap original = m_map.get();
V existingValue = original.get(key);
if (existingValue == null) break;
Builder builder = new Builder();
for (Map.Entry entry : original.entrySet()) {
if (entry.getKey().equals(key)) {
continue;
} else {
builder.put(entry);
}
}
builder.put(key, value);
ImmutableMap copy = builder.build();
if (m_map.compareAndSet(original, copy)) {
return existingValue;
}
}
return null;
}
}