org.diqube.cache.ConstantTimeCache.java Source code

Java tutorial

Introduction

Here is the source code for org.diqube.cache.ConstantTimeCache.java

Source

/**
 * diqube: Distributed Query Base.
 *
 * Copyright (C) 2015 Bastian Gloeckle
 *
 * This file is part of diqube.
 *
 * diqube 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 this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.diqube.cache;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ConcurrentSkipListSet;

import org.diqube.util.Pair;
import org.diqube.util.Triple;

import com.google.common.collect.Iterators;
import com.google.common.collect.PeekingIterator;

/**
 * {@link Cache} that caches each entry for a specific amount of time and evicts it afterwards.
 * 
 * <p>
 * Calls to {@link #offer(Comparable, Comparable, Object)} will not accept a new object, if one with the same keys is
 * registered already. The timeout will not be updated in that case, either.
 * 
 * If no object with the same keys is registered, {@link #offer(Comparable, Comparable, Object)} will always accept new
 * values.
 *
 * @author Bastian Gloeckle
 */
public class ConstantTimeCache<K1 extends Comparable<K1>, K2 extends Comparable<K2>, V>
        implements WritableCache<K1, K2, V>, DeletableCache<K1, K2, V> {

    private long cacheTimeMs;

    private ConcurrentMap<Pair<K1, K2>, V> values = new ConcurrentHashMap<>();
    private ConcurrentSkipListSet<Triple<Long, K1, K2>> timeouts = new ConcurrentSkipListSet<>();
    private ConcurrentMap<K1, Set<K2>> secondLevelKeys = new ConcurrentHashMap<>();

    public ConstantTimeCache(long cacheTimeMs) {
        this.cacheTimeMs = cacheTimeMs;
    }

    @Override
    public V get(K1 key1, K2 key2) {
        cleanupCache(System.currentTimeMillis());

        return values.get(new Pair<>(key1, key2));
    }

    @Override
    public Collection<V> getAll(K1 key1) {
        cleanupCache(System.currentTimeMillis());
        Set<K2> key2s = secondLevelKeys.get(key1);
        if (key2s == null)
            return new ArrayList<>();

        List<V> res = new ArrayList<>();
        for (K2 k2 : key2s) {
            V value = values.get(new Pair<>(key1, k2));
            if (value != null)
                res.add(value);
        }

        return res;
    }

    @Override
    public int size() {
        cleanupCache(System.currentTimeMillis());
        return values.size();
    }

    @Override
    public boolean offer(K1 key1, K2 key2, V value) {
        cleanupCache(System.currentTimeMillis());

        Pair<K1, K2> keyPair = new Pair<>(key1, key2);

        if (values.putIfAbsent(keyPair, value) != null)
            // value set already (perhaps by a concurrent thread?)
            return false;

        timeouts.add(new Triple<>(System.currentTimeMillis() + cacheTimeMs, key1, key2));
        secondLevelKeys.compute(key1, (k, v) -> {
            if (v == null)
                return new ConcurrentSkipListSet<>(Arrays.asList(key2));

            Set<K2> res = new ConcurrentSkipListSet<>(v);
            res.add(key2);
            return res;
        });
        return true;
    }

    @Override
    public void delete(K1 key1, K2 key2) {
        values.remove(new Pair<>(key1, key2));
        // timeouts and secondLevelKeys will be cleaned up by cleanupCache later.
    }

    private void cleanupCache(long now) {
        PeekingIterator<Triple<Long, K1, K2>> it = Iterators.peekingIterator(timeouts.iterator());
        while (it.hasNext() && it.peek().getLeft() < now) {
            Triple<Long, K1, K2> t = it.next();
            values.remove(new Pair<>(t.getMiddle(), t.getRight()));
            secondLevelKeys.computeIfPresent(t.getMiddle(), (k, v) -> {
                Set<K2> res = new ConcurrentSkipListSet<>(v);
                res.remove(t.getRight());
                if (res.isEmpty())
                    return null;
                return res;
            });
            it.remove();
        }
    }

}