001/** 002 * The MIT License (MIT) 003 * 004 * Copyright (c) 2019 nobark (tools4j), Marco Terzer, Anton Anufriev 005 * 006 * Permission is hereby granted, free of charge, to any person obtaining a copy 007 * of this software and associated documentation files (the "Software"), to deal 008 * in the Software without restriction, including without limitation the rights 009 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 010 * copies of the Software, and to permit persons to whom the Software is 011 * furnished to do so, subject to the following conditions: 012 * 013 * The above copyright notice and this permission notice shall be included in all 014 * copies or substantial portions of the Software. 015 * 016 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 017 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 018 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 019 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 020 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 021 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 022 * SOFTWARE. 023 */ 024package org.tools4j.nobark.queue; 025 026import java.util.List; 027import java.util.Map; 028import java.util.Objects; 029import java.util.Queue; 030import java.util.concurrent.ConcurrentHashMap; 031import java.util.function.BiConsumer; 032import java.util.function.Supplier; 033 034/** 035 * A conflation queue implementation that atomically evicts old values from the queue if a new one is enqueued with the 036 * same conflation key. The {@link EvictConflationQueue} is very similar but not atomic and it supports value exchange 037 * on polling, which this queue does not. A backing queue is supplied to the constructor and it determines whether 038 * single or multiple producers and consumers are supported. 039 * <p> 040 * {@link #appender() Appender} and {@link #poller()} are both stateless and hence thread-safe. Note that appender 041 * listener and poller listener must also be thread safe if multiple producers or consumers are used, e.g. use 042 * {@link AppenderListener#threadLocal(Supplier)} and {@link PollerListener#threadLocal(Supplier)} to create thread 043 * local listener instances. 044 * 045 * @param <K> the type of the conflation key 046 * @param <V> the type of elements in the queue 047 */ 048public class AtomicConflationQueue<K,V> implements ConflationQueue<K,V> { 049 private final Queue<Entry<K,V>> queue; 050 private final Map<K,Entry<K,V>> entryMap; 051 052 private final Appender<K,V> appender = new AtomicQueueAppender(); 053 private final Poller<K,V> poller = new AtomicQueuePoller(); 054 055 private final AppenderListener<? super K, ? super V> appenderListener; 056 private final PollerListener<? super K, ? super V> pollerListener; 057 058 private AtomicConflationQueue(final Queue<Entry<K,V>> queue, 059 final Map<K,Entry<K,V>> entryMap, 060 final AppenderListener<? super K, ? super V> appenderListener, 061 final PollerListener<? super K, ? super V> pollerListener) { 062 this.queue = Objects.requireNonNull(queue); 063 this.entryMap = Objects.requireNonNull(entryMap); 064 this.appenderListener= Objects.requireNonNull(appenderListener); 065 this.pollerListener = Objects.requireNonNull(pollerListener); 066 } 067 068 /** 069 * Constructor with queue factory. A concurrent hash map is used to manage entries per conflation key. 070 * 071 * @param queueFactory the factory to create the backing queue 072 */ 073 public AtomicConflationQueue(final Supplier<? extends Queue<Object>> queueFactory) { 074 this(queueFactory, AppenderListener.NOOP, PollerListener.NOOP); 075 } 076 077 /** 078 * Constructor with queue factory. A concurrent hash map is used to manage entries per conflation key. 079 * 080 * @param queueFactory the factory to create the backing queue 081 * @param appenderListener a listener to monitor the enqueue operations 082 * @param pollerListener a listener to monitor the poll operations 083 */ 084 public AtomicConflationQueue(final Supplier<? extends Queue<Object>> queueFactory, 085 final AppenderListener<? super K, ? super V> appenderListener, 086 final PollerListener<? super K, ? super V> pollerListener) { 087 this(queueFactory, ConcurrentHashMap::new, appenderListener, pollerListener); 088 } 089 090 /** 091 * Constructor with queue factory and entry map factory. 092 * 093 * @param queueFactory the factory to create the backing queue 094 * @param entryMapFactory the factory to create the map that manages entries per conflation key 095 * @param appenderListener a listener to monitor the enqueue operations 096 * @param pollerListener a listener to monitor the poll operations 097 */ 098 public AtomicConflationQueue(final Supplier<? extends Queue<Object>> queueFactory, 099 final Supplier<? extends Map<Object, Object>> entryMapFactory, 100 final AppenderListener<? super K, ? super V> appenderListener, 101 final PollerListener<? super K, ? super V> pollerListener) { 102 this(Factories.createQueue(queueFactory), Factories.createMap(entryMapFactory), appenderListener, pollerListener); 103 } 104 105 /** 106 * Constructor with queue factory and the exhaustive list of conflation keys. A hash map is pre-initialized with 107 * all the conflation keys and pre-allocated entries. 108 * 109 * @param queueFactory the factory to create the backing queue 110 * @param allConflationKeys all conflation keys that will ever be used with this conflation queue instance 111 */ 112 public AtomicConflationQueue(final Supplier<? extends Queue<Object>> queueFactory, 113 final List<? extends K> allConflationKeys) { 114 this(queueFactory, allConflationKeys, AppenderListener.NOOP, PollerListener.NOOP); 115 } 116 117 /** 118 * Constructor with queue factory and the exhaustive list of conflation keys. A hash map is pre-initialized with 119 * all the conflation keys and pre-allocated entries. 120 * 121 * @param queueFactory the factory to create the backing queue 122 * @param allConflationKeys all conflation keys that will ever be used with this conflation queue instance 123 * @param appenderListener a listener to monitor the enqueue operations 124 * @param pollerListener a listener to monitor the poll operations 125 */ 126 public AtomicConflationQueue(final Supplier<? extends Queue<Object>> queueFactory, 127 final List<? extends K> allConflationKeys, 128 final AppenderListener<? super K, ? super V> appenderListener, 129 final PollerListener<? super K, ? super V> pollerListener) { 130 this(Factories.createQueue(queueFactory), Entry.eagerlyInitialiseEntryMap(allConflationKeys, () -> null), 131 appenderListener, pollerListener); 132 } 133 134 /** 135 * Static constructor method for a conflation queue with queue factory and the conflation key enum class. An enum 136 * map is pre-initialized with all the conflation keys and pre-allocated entries. 137 * 138 * @param queueFactory the factory to create the backing queue 139 * @param conflationKeyClass the conflation key enum class 140 * @param <K> the type of the conflation key 141 * @param <V> the type of elements in the queue 142 * @return the new conflation queue instance 143 */ 144 public static <K extends Enum<K>,V> AtomicConflationQueue<K,V> forEnumConflationKey(final Supplier<? extends Queue<Object>> queueFactory, 145 final Class<K> conflationKeyClass) { 146 return forEnumConflationKey(queueFactory, conflationKeyClass, AppenderListener.NOOP, PollerListener.NOOP); 147 } 148 149 /** 150 * Static constructor method for a conflation queue with queue factory and the conflation key enum class. An enum 151 * map is pre-initialized with all the conflation keys and pre-allocated entries. 152 * 153 * @param queueFactory the factory to create the backing queue 154 * @param conflationKeyClass the conflation key enum class 155 * @param appenderListener a listener to monitor the enqueue operations 156 * @param pollerListener a listener to monitor the poll operations 157 * @param <K> the type of the conflation key 158 * @param <V> the type of elements in the queue 159 * @return the new conflation queue instance 160 */ 161 public static <K extends Enum<K>,V> AtomicConflationQueue<K,V> forEnumConflationKey(final Supplier<? extends Queue<Object>> queueFactory, 162 final Class<K> conflationKeyClass, 163 final AppenderListener<? super K, ? super V> appenderListener, 164 final PollerListener<? super K, ? super V> pollerListener) { 165 return new AtomicConflationQueue<>(Factories.createQueue(queueFactory), 166 Entry.eagerlyInitialiseEntryEnumMap(conflationKeyClass, () -> null), appenderListener, pollerListener); 167 } 168 169 @Override 170 public Appender<K, V> appender() { 171 return appender; 172 } 173 174 @Override 175 public Poller<K, V> poller() { 176 return poller; 177 } 178 179 @Override 180 public int size() { 181 return queue.size(); 182 } 183 184 private final class AtomicQueueAppender implements Appender<K,V> { 185 @Override 186 public V enqueue(final K conflationKey, final V value) { 187 Objects.requireNonNull(value); 188 final Entry<K,V> entry = entryMap.computeIfAbsent(conflationKey, k -> new Entry<>(k, null)); 189 final V old = entry.value.getAndSet(value); 190 final AppenderListener.Conflation conflation; 191 if (old == null) { 192 queue.add(entry); 193 conflation = AppenderListener.Conflation.UNCONFLATED; 194 } else { 195 conflation = AppenderListener.Conflation.EVICTED; 196 } 197 appenderListener.enqueued(AtomicConflationQueue.this, conflationKey, value, old, conflation); 198 return old; 199 } 200 } 201 202 private final class AtomicQueuePoller implements Poller<K,V> { 203 @Override 204 public V poll(final BiConsumer<? super K, ? super V> consumer) { 205 final Entry<K,V> entry = queue.poll(); 206 if (entry != null) { 207 final V value = entry.value.getAndSet(null); 208 consumer.accept(entry.key, value); 209 pollerListener.polled(AtomicConflationQueue.this, entry.key, value); 210 return value; 211 } else { 212 pollerListener.polledButFoundEmpty(AtomicConflationQueue.this); 213 return null; 214 } 215 } 216 } 217}