All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.commons.collections4.map.PassiveExpiringMap Maven / Gradle / Ivy

There is a newer version: 4.0.1
Show newest version
/*
 * 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.commons.collections4.map;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * Decorates a Map to evict expired entries once their expiration
 * time has been reached.
 * 

* When putting a key-value pair in the map this decorator uses a * {@link ExpirationPolicy} to determine how long the entry should remain alive * as defined by an expiration time value. *

*

* When accessing the mapped value for a key, its expiration time is checked, * and if it is a negative value or if it is greater than the current time, the * mapped value is returned. Otherwise, the key is removed from the decorated * map, and null is returned. *

*

* When invoking methods that involve accessing the entire map contents (i.e * {@link #containsKey(Object)}, {@link #entrySet()}, etc.) this decorator * removes all expired entries prior to actually completing the invocation. *

*

* Note that {@link PassiveExpiringMap} is not synchronized and is not * thread-safe. If you wish to use this map from multiple threads * concurrently, you must use appropriate synchronization. The simplest approach * is to wrap this map using {@link java.util.Collections#synchronizedMap(Map)}. * This class may throw exceptions when accessed by concurrent threads without * synchronization. *

* * @param the type of the keys in this map * @param the type of the values in this map * @since 4.0 */ public class PassiveExpiringMap extends AbstractMapDecorator implements Serializable { /** * A {@link org.apache.commons.collections4.map.PassiveExpiringMap.ExpirationPolicy ExpirationPolicy} * that returns a expiration time that is a * constant about of time in the future from the current time. * * @param the type of the keys in the map * @param the type of the values in the map * @since 4.0 */ public static class ConstantTimeToLiveExpirationPolicy implements ExpirationPolicy { /** Serialization version */ private static final long serialVersionUID = 1L; /** the constant time-to-live value measured in milliseconds. */ private final long timeToLiveMillis; /** * Default constructor. Constructs a policy using a negative * time-to-live value that results in entries never expiring. */ public ConstantTimeToLiveExpirationPolicy() { this(-1L); } /** * Construct a policy with the given time-to-live constant measured in * milliseconds. A negative time-to-live value indicates entries never * expire. A zero time-to-live value indicates entries expire (nearly) * immediately. * * @param timeToLiveMillis the constant amount of time (in milliseconds) * an entry is available before it expires. A negative value * results in entries that NEVER expire. A zero value results in * entries that ALWAYS expire. */ public ConstantTimeToLiveExpirationPolicy(final long timeToLiveMillis) { super(); this.timeToLiveMillis = timeToLiveMillis; } /** * Construct a policy with the given time-to-live constant measured in * the given time unit of measure. * * @param timeToLive the constant amount of time an entry is available * before it expires. A negative value results in entries that * NEVER expire. A zero value results in entries that ALWAYS * expire. * @param timeUnit the unit of time for the timeToLive * parameter, must not be null. * @throws NullPointerException if the time unit is null. */ public ConstantTimeToLiveExpirationPolicy(final long timeToLive, final TimeUnit timeUnit) { this(validateAndConvertToMillis(timeToLive, timeUnit)); } /** * Determine the expiration time for the given key-value entry. * * @param key the key for the entry (ignored). * @param value the value for the entry (ignored). * @return if {@link #timeToLiveMillis} ≥ 0, an expiration time of * {@link #timeToLiveMillis} + * {@link System#currentTimeMillis()} is returned. Otherwise, -1 * is returned indicating the entry never expires. */ @Override public long expirationTime(final K key, final V value) { if (timeToLiveMillis >= 0L) { // avoid numerical overflow final long now = System.currentTimeMillis(); if (now > Long.MAX_VALUE - timeToLiveMillis) { // expiration would be greater than Long.MAX_VALUE // never expire return -1; } // timeToLiveMillis in the future return now + timeToLiveMillis; } // never expire return -1L; } } /** * A policy to determine the expiration time for key-value entries. * * @param the key object type. * @param the value object type * @since 4.0 */ public interface ExpirationPolicy extends Serializable { /** * Determine the expiration time for the given key-value entry. * * @param key the key for the entry. * @param value the value for the entry. * @return the expiration time value measured in milliseconds. A * negative return value indicates the entry never expires. */ long expirationTime(K key, V value); } /** Serialization version */ private static final long serialVersionUID = 1L; /** * First validate the input parameters. If the parameters are valid, convert * the given time measured in the given units to the same time measured in * milliseconds. * * @param timeToLive the constant amount of time an entry is available * before it expires. A negative value results in entries that NEVER * expire. A zero value results in entries that ALWAYS expire. * @param timeUnit the unit of time for the timeToLive * parameter, must not be null. * @throws NullPointerException if the time unit is null. */ private static long validateAndConvertToMillis(final long timeToLive, final TimeUnit timeUnit) { if (timeUnit == null) { throw new NullPointerException("Time unit must not be null"); } return TimeUnit.MILLISECONDS.convert(timeToLive, timeUnit); } /** map used to manage expiration times for the actual map entries. */ private final Map expirationMap = new HashMap<>(); /** the policy used to determine time-to-live values for map entries. */ private final ExpirationPolicy expiringPolicy; /** * Default constructor. Constructs a map decorator that results in entries * NEVER expiring. */ public PassiveExpiringMap() { this(-1L); } /** * Construct a map decorator using the given expiration policy to determine * expiration times. * * @param expiringPolicy the policy used to determine expiration times of * entries as they are added. * @throws NullPointerException if expiringPolicy is null */ public PassiveExpiringMap(final ExpirationPolicy expiringPolicy) { this(expiringPolicy, new HashMap()); } /** * Construct a map decorator that decorates the given map and uses the given * expiration policy to determine expiration times. If there are any * elements already in the map being decorated, they will NEVER expire * unless they are replaced. * * @param expiringPolicy the policy used to determine expiration times of * entries as they are added. * @param map the map to decorate, must not be null. * @throws NullPointerException if the map or expiringPolicy is null. */ public PassiveExpiringMap(final ExpirationPolicy expiringPolicy, final Map map) { super(map); if (expiringPolicy == null) { throw new NullPointerException("Policy must not be null."); } this.expiringPolicy = expiringPolicy; } /** * Construct a map decorator that decorates the given map using the given * time-to-live value measured in milliseconds to create and use a * {@link ConstantTimeToLiveExpirationPolicy} expiration policy. * * @param timeToLiveMillis the constant amount of time (in milliseconds) an * entry is available before it expires. A negative value results in * entries that NEVER expire. A zero value results in entries that * ALWAYS expire. */ public PassiveExpiringMap(final long timeToLiveMillis) { this(new ConstantTimeToLiveExpirationPolicy(timeToLiveMillis), new HashMap()); } /** * Construct a map decorator using the given time-to-live value measured in * milliseconds to create and use a * {@link ConstantTimeToLiveExpirationPolicy} expiration policy. If there * are any elements already in the map being decorated, they will NEVER * expire unless they are replaced. * * @param timeToLiveMillis the constant amount of time (in milliseconds) an * entry is available before it expires. A negative value results in * entries that NEVER expire. A zero value results in entries that * ALWAYS expire. * @param map the map to decorate, must not be null. * @throws NullPointerException if the map is null. */ public PassiveExpiringMap(final long timeToLiveMillis, final Map map) { this(new ConstantTimeToLiveExpirationPolicy(timeToLiveMillis), map); } /** * Construct a map decorator using the given time-to-live value measured in * the given time units of measure to create and use a * {@link ConstantTimeToLiveExpirationPolicy} expiration policy. * * @param timeToLive the constant amount of time an entry is available * before it expires. A negative value results in entries that NEVER * expire. A zero value results in entries that ALWAYS expire. * @param timeUnit the unit of time for the timeToLive * parameter, must not be null. * @throws NullPointerException if the time unit is null. */ public PassiveExpiringMap(final long timeToLive, final TimeUnit timeUnit) { this(validateAndConvertToMillis(timeToLive, timeUnit)); } /** * Construct a map decorator that decorates the given map using the given * time-to-live value measured in the given time units of measure to create * {@link ConstantTimeToLiveExpirationPolicy} expiration policy. This policy * is used to determine expiration times. If there are any elements already * in the map being decorated, they will NEVER expire unless they are * replaced. * * @param timeToLive the constant amount of time an entry is available * before it expires. A negative value results in entries that NEVER * expire. A zero value results in entries that ALWAYS expire. * @param timeUnit the unit of time for the timeToLive * parameter, must not be null. * @param map the map to decorate, must not be null. * @throws NullPointerException if the map or time unit is null. */ public PassiveExpiringMap(final long timeToLive, final TimeUnit timeUnit, final Map map) { this(validateAndConvertToMillis(timeToLive, timeUnit), map); } /** * Constructs a map decorator that decorates the given map and results in * entries NEVER expiring. If there are any elements already in the map * being decorated, they also will NEVER expire. * * @param map the map to decorate, must not be null. * @throws NullPointerException if the map is null. */ public PassiveExpiringMap(final Map map) { this(-1L, map); } /** * Normal {@link Map#clear()} behavior with the addition of clearing all * expiration entries as well. */ @Override public void clear() { super.clear(); expirationMap.clear(); } /** * All expired entries are removed from the map prior to determining the * contains result. * {@inheritDoc} */ @Override public boolean containsKey(final Object key) { removeIfExpired(key, now()); return super.containsKey(key); } /** * All expired entries are removed from the map prior to determining the * contains result. * {@inheritDoc} */ @Override public boolean containsValue(final Object value) { removeAllExpired(now()); return super.containsValue(value); } /** * All expired entries are removed from the map prior to returning the entry set. * {@inheritDoc} */ @Override public Set> entrySet() { removeAllExpired(now()); return super.entrySet(); } /** * All expired entries are removed from the map prior to returning the entry value. * {@inheritDoc} */ @Override public V get(final Object key) { removeIfExpired(key, now()); return super.get(key); } /** * All expired entries are removed from the map prior to determining if it is empty. * {@inheritDoc} */ @Override public boolean isEmpty() { removeAllExpired(now()); return super.isEmpty(); } /** * Determines if the given expiration time is less than now. * * @param now the time in milliseconds used to compare against the * expiration time. * @param expirationTimeObject the expiration time value retrieved from * {@link #expirationMap}, can be null. * @return true if expirationTimeObject is ≥ 0 * and expirationTimeObject < now. * false otherwise. */ private boolean isExpired(final long now, final Long expirationTimeObject) { if (expirationTimeObject != null) { final long expirationTime = expirationTimeObject.longValue(); return expirationTime >= 0 && now >= expirationTime; } return false; } /** * All expired entries are removed from the map prior to returning the key set. * {@inheritDoc} */ @Override public Set keySet() { removeAllExpired(now()); return super.keySet(); } /** * The current time in milliseconds. */ private long now() { return System.currentTimeMillis(); } /** * Add the given key-value pair to this map as well as recording the entry's expiration time based on * the current time in milliseconds and this map's {@link #expiringPolicy}. *

* {@inheritDoc} */ @Override public V put(final K key, final V value) { // remove the previous record removeIfExpired(key, now()); // record expiration time of new entry final long expirationTime = expiringPolicy.expirationTime(key, value); expirationMap.put(key, Long.valueOf(expirationTime)); return super.put(key, value); } @Override public void putAll(final Map mapToCopy) { for (final Map.Entry entry : mapToCopy.entrySet()) { put(entry.getKey(), entry.getValue()); } } /** * Normal {@link Map#remove(Object)} behavior with the addition of removing * any expiration entry as well. * {@inheritDoc} */ @Override public V remove(final Object key) { expirationMap.remove(key); return super.remove(key); } /** * Removes all entries in the map whose expiration time is less than * now. The exceptions are entries with negative expiration * times; those entries are never removed. * * @see #isExpired(long, Long) */ private void removeAllExpired(final long now) { final Iterator> iter = expirationMap.entrySet().iterator(); while (iter.hasNext()) { final Map.Entry expirationEntry = iter.next(); if (isExpired(now, expirationEntry.getValue())) { // remove entry from collection super.remove(expirationEntry.getKey()); // remove entry from expiration map iter.remove(); } } } /** * Removes the entry with the given key if the entry's expiration time is * less than now. If the entry has a negative expiration time, * the entry is never removed. */ private void removeIfExpired(final Object key, final long now) { final Long expirationTimeObject = expirationMap.get(key); if (isExpired(now, expirationTimeObject)) { remove(key); } } /** * All expired entries are removed from the map prior to returning the size. * {@inheritDoc} */ @Override public int size() { removeAllExpired(now()); return super.size(); } /** * Read the map in using a custom routine. * * @param in the input stream * @throws IOException if an error occurs while reading from the stream * @throws ClassNotFoundException if an object read from the stream can not be loaded */ @SuppressWarnings("unchecked") // (1) should only fail if input stream is incorrect private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); map = (Map) in.readObject(); // (1) } /** * Write the map out using a custom routine. * * @param out the output stream * @throws IOException if an error occurs while writing to the stream */ private void writeObject(final ObjectOutputStream out) throws IOException { out.defaultWriteObject(); out.writeObject(map); } /** * All expired entries are removed from the map prior to returning the value collection. * {@inheritDoc} */ @Override public Collection values() { removeAllExpired(now()); return super.values(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy