001package com.identityworksllc.iiq.common.cache;
002
003import java.io.Serializable;
004import java.util.Date;
005import java.util.Objects;
006import java.util.Optional;
007import java.util.StringJoiner;
008import java.util.concurrent.TimeUnit;
009import java.util.function.Supplier;
010
011/**
012 * Cache entry for use with the CacheMap class or other purposes. This class allows you
013 * to store a dated object of any type. The entry will be considered expired when the
014 * current date is after the expiration date.
015 *
016 * Instances of this class are {@link Serializable} if the contained type T is serializable.
017 *
018 * @param <T> The type of the object to store
019 */
020public final class CacheEntry<T> implements Serializable, Supplier<Optional<T>> {
021
022        /**
023         * Serialization UID
024         */
025        private static final long serialVersionUID = 2L;
026
027        /**
028         * Returns either the entry (if it is not null and not expired), or invokes the Supplier
029         * to generate a new entry if it is expired.
030         *
031         * @param entry The existing entry (which can be null)
032         * @param valueSupplier A function to calculate a new value
033         * @return The value supplier
034         * @param <T> The type of thing that is cached
035         */
036        public static <T> CacheEntry<T> computeIfExpired(CacheEntry<T> entry, Supplier<CacheEntry<T>> valueSupplier) {
037                if (entry == null || entry.isExpired()) {
038                        return valueSupplier.get();
039                } else {
040                        return entry;
041                }
042        }
043        /**
044         * Expiration millisecond timestamp for this entry
045         */
046        private final long expiration;
047
048        /**
049         * The object type
050         */
051        private final T value;
052
053        /**
054         * Construct a new cache entry that expires a specific amount of time in the future
055         * @param entryValue The entry value
056         * @param futureTimeAmount The amount of time in the future (from right now) to expire it
057         * @param timeUnit The units in which the amount of time is specified
058         */
059        public CacheEntry(T entryValue, long futureTimeAmount, TimeUnit timeUnit) {
060                this(entryValue, System.currentTimeMillis() + TimeUnit.MILLISECONDS.convert(futureTimeAmount, timeUnit));
061        }
062
063        /**
064         * Construct a new cache entry that expires on a specific date
065         * @param entryValue The value of this cache entry
066         * @param entryExpiration The expiration date of this cache entry
067         */
068        public CacheEntry(T entryValue, Date entryExpiration) {
069                this(entryValue, entryExpiration.getTime());
070        }
071
072        /**
073         * Construct a new cache entry that expires at a specific Unix timestamp (in milliseconds)
074         * @param entryValue The value of this cache entry
075         * @param entryExpirationTimestampMillis The expiration date of this cache entry
076         */
077        public CacheEntry(T entryValue, long entryExpirationTimestampMillis) {
078                this.value = entryValue;
079                this.expiration = entryExpirationTimestampMillis;
080        }
081
082        /**
083         * @see Object#equals(Object)
084         */
085        @Override
086        public boolean equals(Object o) {
087                if (this == o) return true;
088                if (o == null || getClass() != o.getClass()) return false;
089                CacheEntry<?> that = (CacheEntry<?>) o;
090                return Objects.equals(value, that.value);
091        }
092
093        /**
094         * If the entry is expired, returns {@link Optional#empty()}.
095         * If the entry is not expired, returns {@link Optional#ofNullable(Object)}.
096         *
097         * @return An optional containing a non-expired entry value
098         */
099        @Override
100        public Optional<T> get() {
101                if (isExpired()) {
102                        return Optional.empty();
103                } else {
104                        return Optional.ofNullable(value);
105                }
106        }
107
108        /**
109         * Returns the expiration instant, in Unix timestamp millis
110         * @return The expiration timestamp
111         */
112        public long getExpiration() {
113                return expiration;
114        }
115
116        /**
117         * Gets the value associated with this cache entry
118         *
119         * @return The cached value
120         */
121        public T getValue() {
122                return this.value;
123        }
124
125        /**
126         * @see Object#hashCode()
127         */
128        @Override
129        public int hashCode() {
130                return Objects.hash(value);
131        }
132
133        /**
134         * The entry is expired if the current time is after the expiration date
135         *
136         * @return True if expired
137         */
138        public boolean isExpired() {
139                return System.currentTimeMillis() >= expiration;
140        }
141
142        @Override
143        public String toString() {
144                return new StringJoiner(", ", CacheEntry.class.getSimpleName() + "[", "]")
145                                .add("expiration timestamp=" + expiration)
146                                .add("value=" + value)
147                                .toString();
148        }
149}