/*
* (C) Copyright 2016 Pantheon Technologies, s.r.o. and others.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.opendaylight.yangtools.triemap;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static org.opendaylight.yangtools.triemap.PresencePredicate.ABSENT;
import static org.opendaylight.yangtools.triemap.PresencePredicate.PRESENT;
import com.google.common.annotations.Beta;
import com.google.common.base.Verify;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
/**
* A mutable TrieMap.
*
* @author Robert Varga
*
* @param <K> the type of keys maintained by this map
* @param <V> the type of mapped values
*/
@Beta
final class MutableTrieMap<K, V> extends TrieMap<K, V> {
private static final long serialVersionUID = 1L;
@SuppressWarnings("rawtypes")
private static final AtomicReferenceFieldUpdater<MutableTrieMap, Object> ROOT_UPDATER =
AtomicReferenceFieldUpdater.newUpdater(MutableTrieMap.class, Object.class, "root");
private volatile Object root;
MutableTrieMap(final Equivalence<? super K> equiv) {
this(equiv, newRootNode());
}
MutableTrieMap(final Equivalence<? super K> equiv, final INode<K, V> root) {
super(equiv);
this.root = checkNotNull(root);
}
@Override
public void clear() {
boolean success;
do {
final INode<K, V> r = RDCSS_READ_ROOT();
success = RDCSS_ROOT(r, r.gcasRead(this), newRootNode());
} while (!success);
}
@Override
public V put(final K key, final V value) {
final K k = checkNotNull(key);
return toNullable(insertifhc(k, computeHash(k), checkNotNull(value), null));
}
@Override
public V putIfAbsent(final K key, final V value) {
final K k = checkNotNull(key);
return toNullable(insertifhc(k, computeHash(k), checkNotNull(value), ABSENT));
}
@Override
public V remove(final Object key) {
@SuppressWarnings("unchecked")
final K k = (K) checkNotNull(key);
return toNullable(removehc(k, null, computeHash(k)));
}
@SuppressFBWarnings(value = "NP_PARAMETER_MUST_BE_NONNULL_BUT_MARKED_AS_NULLABLE",
justification = "API contract allows null value, but we are not")
@Override
public boolean remove(final Object key, final Object value) {
@SuppressWarnings("unchecked")
final K k = (K) checkNotNull(key);
return removehc(k, checkNotNull(value), computeHash(k)).isPresent();
}
@Override
public boolean replace(final K key, final V oldValue, final V newValue) {
final K k = checkNotNull(key);
return insertifhc(k, computeHash(k), checkNotNull(newValue), checkNotNull(oldValue)).isPresent();
}
@Override
public V replace(final K key, final V value) {
final K k = checkNotNull(key);
return toNullable(insertifhc(k, computeHash(k), checkNotNull(value), PRESENT));
}
@Override
public int size() {
return immutableSnapshot().size();
}
@Override
public ImmutableTrieMap<K, V> immutableSnapshot() {
while (true) {
final INode<K, V> r = RDCSS_READ_ROOT();
final MainNode<K, V> expmain = r.gcasRead(this);
if (RDCSS_ROOT(r, expmain, r.copyToGen(new Gen(), this))) {
return new ImmutableTrieMap<>(r, equiv());
}
// Tail recursion: return readOnlySnapshot();
}
}
@Override
public MutableTrieMap<K, V> mutableSnapshot() {
while (true) {
final INode<K, V> r = RDCSS_READ_ROOT();
final MainNode<K, V> expmain = r.gcasRead(this);
if (RDCSS_ROOT(r, expmain, r.copyToGen(new Gen(), this))) {
return new MutableTrieMap<>(equiv(), r.copyToGen(new Gen(), this));
}
// Tail recursion: return snapshot();
}
}
@Override
MutableEntrySet<K, V> createEntrySet() {
// FIXME: it would be nice to have a ReadWriteTrieMap with read-only iterator
// if (readOnlyEntrySet) return ImmutableEntrySet(this);
return new MutableEntrySet<>(this);
}
@Override
MutableKeySet<K> createKeySet() {
return new MutableKeySet<>(this);
}
@Override
MutableIterator<K, V> iterator() {
return new MutableIterator<>(this);
}
@Override
boolean isReadOnly() {
return false;
}
@Override
INode<K, V> RDCSS_READ_ROOT(final boolean abort) {
final Object r = /* READ */ root;
if (r instanceof INode) {
return (INode<K, V>) r;
}
checkState(r instanceof RDCSS_Descriptor, "Unhandled root %s", r);
return RDCSS_Complete(abort);
}
void add(final K key, final V value) {
final K k = checkNotNull(key);
inserthc(k, computeHash(k), checkNotNull(value));
}
private static <K,V> INode<K, V> newRootNode() {
final Gen gen = new Gen();
return new INode<>(gen, new CNode<>(gen));
}
private void inserthc(final K key, final int hc, final V value) {
// TODO: this is called from serialization only, which means we should not be observing any races,
// hence we should not need to pass down the entire tree, just equality (I think).
final boolean success = RDCSS_READ_ROOT().rec_insert(key, value, hc, 0, null, this);
Verify.verify(success, "Concurrent modification during serialization of map %s", this);
}
private Optional<V> insertifhc(final K key, final int hc, final V value, final Object cond) {
Optional<V> res;
do {
// Keep looping as long as we do not get a reply
res = RDCSS_READ_ROOT().rec_insertif(key, value, hc, cond, 0, null, this);
} while (res == null);
return res;
}
private Optional<V> removehc(final K key, final Object cond, final int hc) {
Optional<V> res;
do {
// Keep looping as long as we do not get a reply
res = RDCSS_READ_ROOT().rec_remove(key, cond, hc, 0, null, this);
} while (res == null);
return res;
}
private boolean CAS_ROOT(final Object ov, final Object nv) {
return ROOT_UPDATER.compareAndSet(this, ov, nv);
}
private boolean RDCSS_ROOT(final INode<K, V> ov, final MainNode<K, V> expectedmain, final INode<K, V> nv) {
final RDCSS_Descriptor<K, V> desc = new RDCSS_Descriptor<>(ov, expectedmain, nv);
if (CAS_ROOT(ov, desc)) {
RDCSS_Complete(false);
return /* READ */desc.committed;
}
return false;
}
private INode<K, V> RDCSS_Complete(final boolean abort) {
while (true) {
final Object r = /* READ */ root;
if (r instanceof INode) {
return (INode<K, V>) r;
}
checkState(r instanceof RDCSS_Descriptor, "Unhandled root %s", r);
@SuppressWarnings("unchecked")
final RDCSS_Descriptor<K, V> desc = (RDCSS_Descriptor<K, V>) r;
final INode<K, V> ov = desc.old;
final MainNode<K, V> exp = desc.expectedmain;
final INode<K, V> nv = desc.nv;
if (abort) {
if (CAS_ROOT(desc, ov)) {
return ov;
}
// Tail recursion: return RDCSS_Complete(abort);
continue;
}
final MainNode<K, V> oldmain = ov.gcasRead(this);
if (oldmain == exp) {
if (CAS_ROOT(desc, nv)) {
desc.committed = true;
return nv;
}
// Tail recursion: return RDCSS_Complete(abort);
continue;
}
if (CAS_ROOT(desc, ov)) {
return ov;
}
// Tail recursion: return RDCSS_Complete(abort);
}
}
private static final class RDCSS_Descriptor<K, V> {
final INode<K, V> old;
final MainNode<K, V> expectedmain;
final INode<K, V> nv;
volatile boolean committed = false;
RDCSS_Descriptor(final INode<K, V> old, final MainNode<K, V> expectedmain, final INode<K, V> nv) {
this.old = old;
this.expectedmain = expectedmain;
this.nv = nv;
}
}
}