blob: 33b2e6032bc04f35075eff244b767c320f0fe780 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.ignite.internal.util;
import java.util.Comparator;
import java.util.Map;
import java.util.SortedMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.ignite.internal.util.typedef.internal.A;
import org.apache.ignite.lang.IgniteBiInClosure;
import org.jetbrains.annotations.Nullable;
/**
* Concurrent ordered map that automatically manages its maximum size.
* Once it exceeds its maximum, it will start removing smallest elements
* until the maximum is reached again. If optional listener is set
* via {@link #evictionListener(org.apache.ignite.lang.IgniteBiInClosure)} method, then listener
* will be notified for every eviction.
* <p>
* Note that due to concurrent nature of this map, it may grow slightly
* larger than its maximum allowed size, but in this case it will quickly
* readjust back to allowed size.
* <p>
* Note that {@link #remove(Object)} and {@link #remove(Object, Object)} methods
* are not supported for this kind of map.
*/
public class GridBoundedConcurrentOrderedMap<K, V> extends ConcurrentSkipListMap<K, V> {
/** */
private static final long serialVersionUID = 0L;
/** Element count. */
private final AtomicInteger cnt = new AtomicInteger();
/** Maximum size. */
private int max;
/** Listener. */
private volatile IgniteBiInClosure<K, V> lsnr;
/**
* Constructs a new, empty map that orders its elements according to
* their {@linkplain Comparable natural ordering}.
*
* @param max Upper bound of this map.
*/
public GridBoundedConcurrentOrderedMap(int max) {
A.ensure(max > 0, "max > 0");
this.max = max;
}
/**
* Constructs a new, empty set that orders its elements according to
* the specified comparator.
*
* @param max Upper bound of this map.
* @param comparator The comparator that will be used to order this map.
* If <tt>null</tt>, the {@linkplain Comparable natural
* ordering} of the elements will be used.
*/
public GridBoundedConcurrentOrderedMap(int max, Comparator<? super K> comparator) {
super(comparator);
A.ensure(max > 0, "max > 0");
this.max = max;
}
/**
* Constructs a new map containing the elements in the specified
* map, that orders its elements according to their
* {@linkplain Comparable natural ordering}.
*
* @param max Upper bound of this map.
* @param map The elements that will comprise the new map.
* @throws ClassCastException if the elements in <tt>map</tt> are
* not {@link Comparable}, or are not mutually comparable.
* @throws NullPointerException if the specified map or any
* of its elements are {@code null}.
*/
public GridBoundedConcurrentOrderedMap(int max, Map<? extends K, ? extends V> map) {
super(map);
A.ensure(max > 0, "max > 0");
this.max = max;
}
/**
* Constructs a new map containing the same elements and using the
* same ordering as the specified sorted map.
*
* @param max Upper bound of this map.
* @param map Sorted map whose elements will comprise the new map.
* @throws NullPointerException if the specified sorted map or any
* of its elements are {@code null}.
*/
public GridBoundedConcurrentOrderedMap(int max, SortedMap<K, V> map) {
super(map);
A.ensure(max > 0, "max > 0");
this.max = max;
}
/**
* Sets closure listener to be called for every eviction event.
*
* @param lsnr Closure to be called for every eviction event.
*/
public void evictionListener(IgniteBiInClosure<K, V> lsnr) {
this.lsnr = lsnr;
}
/**
* Gets closure listener to be called for every eviction event.
*
* @return Closure to be called for every eviction event.
*/
public IgniteBiInClosure<K, V> evictionListener() {
return lsnr;
}
/** {@inheritDoc} */
@Nullable @Override public V put(K k, V v) {
A.notNull(k, "k", v, "v");
V ret = super.put(k, v);
onPut();
return ret;
}
/** {@inheritDoc} */
@Nullable @Override public V putIfAbsent(K k, V v) {
A.notNull(k, "k", v, "v");
V ret = super.putIfAbsent(k, v);
// Handle size only if put succeeded.
if (ret == null)
onPut();
return ret;
}
/**
* Handles size after put.
*/
private void onPut() {
cnt.incrementAndGet();
IgniteBiInClosure<K, V> lsnr = this.lsnr;
int delta = cnt.get() - max;
for (int i = 0; i < delta && cnt.get() > max; i++) {
Entry<K, V> e = pollFirstEntry();
if (e == null)
return;
cnt.decrementAndGet();
// Listener notification.
if (lsnr != null)
lsnr.apply(e.getKey(), e.getValue());
}
}
/**
* Approximate size at this point of time. Note, that unlike {@code size}
* methods on other {@code concurrent} collections, this method executes
* in constant time without traversal of the elements.
*
* @return Approximate set size at this point of time.
*/
@Override public int size() {
return cnt.get();
}
/** {@inheritDoc} */
@SuppressWarnings({"unchecked"})
@Override public GridBoundedConcurrentOrderedMap<K, V> clone() {
GridBoundedConcurrentOrderedMap<K, V> map = (GridBoundedConcurrentOrderedMap<K, V>)super.clone();
map.max = max;
return map;
}
/**
* This method is not supported and always throws {@link UnsupportedOperationException}.
*
* @param o {@inheritDoc}
* @return {@inheritDoc}
*/
@Override public V remove(Object o) {
V old = super.remove(o);
if (old != null)
cnt.decrementAndGet();
return old;
}
/**
* This method is not supported and always throws {@link UnsupportedOperationException}.
*
* @param key {@inheritDoc}
* @param val {@inheritDoc}
* @return {@inheritDoc}
*/
@Override public boolean remove(Object key, Object val) {
boolean rmvd = super.remove(key, val);
if (rmvd)
cnt.decrementAndGet();
return rmvd;
}
/** {@inheritDoc} */
@Override public void clear() {
throw new UnsupportedOperationException();
}
}