| /* |
| * 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} */ |
| @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(); |
| } |
| } |