package org.infinispan.commands.read; import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.Spliterator; import org.infinispan.Cache; import org.infinispan.CacheSet; import org.infinispan.CacheStream; import org.infinispan.commands.VisitableCommand; import org.infinispan.commands.Visitor; import org.infinispan.commons.util.CloseableIterator; import org.infinispan.commons.util.CloseableSpliterator; import org.infinispan.commons.util.Closeables; import org.infinispan.commons.util.EnumUtil; import org.infinispan.container.DataContainer; import org.infinispan.container.entries.CacheEntry; import org.infinispan.container.entries.ForwardingCacheEntry; import org.infinispan.context.Flag; import org.infinispan.context.InvocationContext; import org.infinispan.distribution.DistributionManager; import org.infinispan.distribution.ch.ConsistentHash; import org.infinispan.stream.impl.local.EntryStreamSupplier; import org.infinispan.stream.impl.local.LocalCacheStream; import org.infinispan.util.DataContainerRemoveIterator; /** * Command implementation for {@link java.util.Map#entrySet()} functionality. * * @author Galder ZamarreƱo * @author <a href="http://gleamynode.net/">Trustin Lee</a> * @author William Burns * @since 4.0 */ public class EntrySetCommand<K, V> extends AbstractLocalCommand implements VisitableCommand { private final Cache<K, V> cache; public EntrySetCommand(Cache<K, V> cache, long flagsBitSet) { setFlagsBitSet(flagsBitSet); if (flagsBitSet != EnumUtil.EMPTY_BIT_SET) { this.cache = cache.getAdvancedCache().withFlags(EnumUtil.enumArrayOf(flagsBitSet, Flag.class)); } else { this.cache = cache; } } @Override public Object acceptVisitor(InvocationContext ctx, Visitor visitor) throws Throwable { return visitor.visitEntrySetCommand(ctx, this); } @Override public LoadType loadType() { throw new UnsupportedOperationException(); } @Override public Set<CacheEntry<K, V>> perform(InvocationContext ctx) throws Throwable { return new BackingEntrySet<>(cache); } @Override public String toString() { return "EntrySetCommand{" + "cache=" + cache.getName() + '}'; } static class BackingEntrySet<K, V> extends AbstractCloseableIteratorCollection<CacheEntry<K, V>, K, V> implements CacheSet<CacheEntry<K, V>> { BackingEntrySet(Cache cache) { super(cache); } @Override public CloseableIterator<CacheEntry<K, V>> iterator() { Iterator<CacheEntry<K, V>> iterator = new DataContainerRemoveIterator<>(cache); return new EntryWrapperIterator<>(cache, iterator); } @Override public CloseableSpliterator<CacheEntry<K, V>> spliterator() { DataContainer<K, V> dc = cache.getAdvancedCache().getDataContainer(); return Closeables.spliterator(Closeables.iterator(new DataContainerRemoveIterator<>(cache, dc)), dc.sizeIncludingExpired(), Spliterator.CONCURRENT | Spliterator.NONNULL | Spliterator.DISTINCT); } @Override public int size() { return cache.getAdvancedCache().getDataContainer().size(); } @Override public boolean contains(Object o) { Map.Entry entry = toEntry(o); if (entry != null) { V value = cache.get(entry.getKey()); return value != null && value.equals(entry.getValue()); } return false; } @Override public boolean remove(Object o) { Map.Entry entry = toEntry(o); return entry != null && cache.remove(entry.getKey(), entry.getValue()); } @Override public boolean add(CacheEntry<K, V> internalCacheEntry) { /** * {@link Map#entrySet()} defines no support for add or addAll methods */ throw new UnsupportedOperationException(); } private Map.Entry<K, V> toEntry(Object obj) { if (obj instanceof Map.Entry) { return (Map.Entry) obj; } else { return null; } } private ConsistentHash getConsistentHash(Cache<K, V> cache) { DistributionManager dm = cache.getAdvancedCache().getDistributionManager(); if (dm != null) { return dm.getReadConsistentHash(); } return null; } @Override public CacheStream<CacheEntry<K, V>> stream() { return new LocalCacheStream<>(new EntryStreamSupplier<>(cache, getConsistentHash(cache), () -> super.stream()), false, cache.getAdvancedCache().getComponentRegistry()); } @Override public CacheStream<CacheEntry<K, V>> parallelStream() { return new LocalCacheStream<>(new EntryStreamSupplier<>(cache, getConsistentHash(cache), () -> super.stream()), true, cache.getAdvancedCache().getComponentRegistry()); } } /** * Wrapper for iterator that produces CacheEntry instances that allow for updating the cache when * the cache entry's value is updated * @param <K> The key type * @param <V> The value type */ private static class EntryWrapperIterator<K, V> implements CloseableIterator<CacheEntry<K, V>> { private final Cache<K, V> cache; private final Iterator<CacheEntry<K, V>> iterator; public EntryWrapperIterator(Cache<K, V> cache, Iterator<CacheEntry<K, V>> iterator) { this.cache = cache; this.iterator = iterator; } @Override public void close() { // Does nothing because data container iterator doesn't need to be closed } @Override public boolean hasNext() { return iterator.hasNext(); } @Override public CacheEntry<K, V> next() { CacheEntry<K, V> entry = iterator.next(); return new EntryWrapper<>(cache, entry); } @Override public void remove() { iterator.remove(); } } /** * Wrapper for CacheEntry(s) that can be used to update the cache when it's value is set. * @param <K> The key type * @param <V> The value type */ private static class EntryWrapper<K, V> extends ForwardingCacheEntry<K, V> { private final Cache<K, V> cache; private final CacheEntry<K, V> entry; public EntryWrapper(Cache<K, V> cache, CacheEntry<K, V> entry) { this.cache = cache; this.entry = entry; } @Override protected CacheEntry<K, V> delegate() { return entry; } @Override public V setValue(V value) { cache.put(entry.getKey(), value); return super.setValue(value); } } }