/* * Copyright 2016 higherfrequencytrading.com * * Licensed 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 net.openhft.chronicle.engine.map; import net.openhft.chronicle.bytes.BytesUtil; import net.openhft.chronicle.engine.api.map.KeyValueStore; import net.openhft.chronicle.engine.api.map.MapEvent; import net.openhft.chronicle.engine.api.map.MapView; import net.openhft.chronicle.engine.api.pubsub.InvalidSubscriberException; import net.openhft.chronicle.engine.api.pubsub.Reference; import net.openhft.chronicle.engine.api.pubsub.Subscriber; import net.openhft.chronicle.engine.api.pubsub.TopicSubscriber; import net.openhft.chronicle.engine.api.set.EntrySetView; import net.openhft.chronicle.engine.api.set.KeySetView; import net.openhft.chronicle.engine.api.tree.Asset; import net.openhft.chronicle.engine.api.tree.RequestContext; import net.openhft.chronicle.engine.api.tree.RequestContext.Operation; import net.openhft.chronicle.engine.query.Filter; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.*; import static java.util.EnumSet.of; import static net.openhft.chronicle.engine.api.tree.RequestContext.Operation.BOOTSTRAP; public class VanillaMapView<K, V> implements MapView<K, V> { protected final Class keyClass; protected final Class valueType; @NotNull protected final Asset asset; @NotNull protected final RequestContext context; protected final boolean putReturnsNull; protected final boolean removeReturnsNull; @NotNull private final KeyValueStore<K, V> kvStore; private AbstractCollection<V> values; public VanillaMapView(@NotNull RequestContext context, @NotNull Asset asset, @NotNull KeyValueStore<K, V> kvStore) { this.context = context; this.keyClass = context.keyType(); this.valueType = context.valueType(); this.asset = asset; this.kvStore = kvStore; this.putReturnsNull = context.putReturnsNull() != Boolean.FALSE; this.removeReturnsNull = context.removeReturnsNull() != Boolean.FALSE; } @Override public Class<K> keyType() { return keyClass; } @Override public Class<V> valueType() { return valueType; } @Nullable @Override public V getUsing(K key, Object usingValue) { return kvStore.getUsing(key, usingValue); } @NotNull @Override public KeySetView<K> keySet() { return asset.acquireView(KeySetView.class, context); } @NotNull @Override public Collection<V> values() { if (values == null) { values = new AbstractCollection<V>() { @NotNull public Iterator<V> iterator() { return new Iterator<V>() { @NotNull private final Iterator<Entry<K, V>> i = entrySet().iterator(); public boolean hasNext() { return i.hasNext(); } public V next() { return i.next().getValue(); } public void remove() { i.remove(); } }; } public int size() { return VanillaMapView.this.size(); } public boolean isEmpty() { return VanillaMapView.this.isEmpty(); } public void clear() { VanillaMapView.this.clear(); } public boolean contains(Object v) { return VanillaMapView.this.containsValue(v); } }; } return values; } @Override public boolean isEmpty() { return longSize() == 0; } @Override public boolean containsKey(final Object key) { checkKey(key); return keyClass.isInstance(key) && kvStore.containsKey((K) key); } @Override public boolean containsValue(Object value) { checkValue(value); try { for (int i = 0; i < kvStore.segments(); i++) { kvStore.entriesFor(i, e -> { if (BytesUtil.equals(e.getValue(), value)) throw new InvalidSubscriberException(); }); } return false; } catch (InvalidSubscriberException e) { return true; } } protected void checkKey(@Nullable final Object key) { if (key == null) throw new NullPointerException("key can not be null"); } protected void checkValue(@Nullable final Object value) { if (value == null) throw new NullPointerException("value can not be null"); } @NotNull @Override public Asset asset() { return asset; } @Override public KeyValueStore<K, V> underlying() { return kvStore; } @Nullable @Override public V get(Object key) { checkKey(key); return kvStore.isKeyType(key) ? kvStore.getUsing((K) key, null) : null; } @Nullable @Override public V put(K key, V value) { checkKey(key); checkValue(value); if (putReturnsNull) { kvStore.put(key, value); return null; } else { return kvStore.getAndPut(key, value); } } @Override public void set(K key, V value) { checkKey(key); checkValue(value); kvStore.put(key, value); } @Nullable @Override public V remove(Object key) { checkKey(key); if (!kvStore.isKeyType(key)) { return null; } @NotNull K key2 = (K) key; if (removeReturnsNull) { kvStore.remove(key2); return null; } else { return kvStore.getAndRemove(key2); } } @Override public void putAll(@net.openhft.chronicle.core.annotation.NotNull Map<? extends K, ? extends V> m) { for (@NotNull Entry<? extends K, ? extends V> entry : m.entrySet()) { put(entry.getKey(), entry.getValue()); } } @Override public long longSize() { return kvStore.longSize(); } @Nullable @Override public V getAndPut(K key, V value) { return kvStore.getAndPut(key, value); } @Nullable @Override public V getAndRemove(K key) { return kvStore.getAndRemove(key); } @NotNull @Override public EntrySetView<K, Object, V> entrySet() { //noinspection unchecked return asset.acquireView(EntrySetView.class); } @Override public void clear() { kvStore.clear(); } @Nullable @Override public V putIfAbsent(@net.openhft.chronicle.core.annotation.NotNull K key, V value) { checkKey(key); checkValue(value); return kvStore.putIfAbsent(key, value); } @Override public boolean remove(@net.openhft.chronicle.core.annotation.NotNull Object key, Object value) { checkKey(key); checkValue(value); return kvStore.isKeyType(key) && kvStore.removeIfEqual((K) key, (V) value); } @Override public boolean replace(@net.openhft.chronicle.core.annotation.NotNull K key, @net.openhft.chronicle.core.annotation.NotNull V oldValue, @net.openhft.chronicle.core.annotation.NotNull V newValue) { checkKey(key); checkValue(oldValue); checkValue(newValue); return kvStore.replaceIfEqual(key, oldValue, newValue); } @Nullable @Override public V replace(@net.openhft.chronicle.core.annotation.NotNull K key, @net.openhft.chronicle.core.annotation.NotNull V value) { checkKey(key); checkValue(value); return kvStore.replace(key, value); } @Override public void registerTopicSubscriber(@NotNull TopicSubscriber<K, V> topicSubscriber) { @NotNull KVSSubscription<K, V> subscription = (KVSSubscription<K, V>) asset.subscription(true); subscription.registerTopicSubscriber(RequestContext.requestContext().bootstrap(true).type(keyClass).type2(valueType), topicSubscriber); } @Override public void registerKeySubscriber(@NotNull Subscriber<K> subscriber) { registerKeySubscriber(subscriber, Filter.empty(), of(BOOTSTRAP)); } @Override public void registerKeySubscriber(@NotNull Subscriber<K> subscriber, @NotNull Filter filter, @NotNull Set<Operation> operations) { @NotNull final RequestContext rc = context.clone(); operations.forEach(e -> e.apply(rc)); @NotNull KVSSubscription<K, V> subscription = (KVSSubscription<K, V>) asset.subscription(true); subscription.registerKeySubscriber(rc.type(keyClass), subscriber, filter); } @Override public void registerSubscriber(@NotNull Subscriber<MapEvent<K, V>> subscriber) { registerSubscriber(subscriber, Filter.empty(), of(BOOTSTRAP)); } @Override public void registerSubscriber(@NotNull Subscriber<MapEvent<K, V>> subscriber, @NotNull Filter<MapEvent<K, V>> filter, @NotNull Set<Operation> operations) { @NotNull final RequestContext rc = context.clone(); rc.bootstrap(true).elementType(MapEvent.class); operations.forEach(e -> e.apply(rc)); @Nullable KVSSubscription<K, V> subscription = (KVSSubscription<K, V>) asset.subscription(true); subscription.registerSubscriber(rc, subscriber, filter); } @NotNull @Override public Reference<V> referenceFor(K key) { // TODO CE-101 throw new UnsupportedOperationException("todo"); } @Override public int hashCode() { return entrySet().hashCode(); } @Override public boolean equals(Object obj) { if (obj instanceof Map) { @NotNull Map map = (Map) obj; // todo use longSize() if (size() != map.size()) return false; try { for (int i = 0; i < kvStore.segments(); i++) { kvStore.entriesFor(i, e -> { if (!BytesUtil.equals(e.getValue(), map.get(e.getKey()))) throw new InvalidSubscriberException(); }); } return true; } catch (InvalidSubscriberException e) { return false; } } return false; } @NotNull @Override public String toString() { @NotNull StringBuilder sb = new StringBuilder(); sb.append("{"); try { for (int i = 0; i < kvStore.segments(); i++) { kvStore.entriesFor(i, e -> sb.append(e.getKey()).append("=").append(e.getValue()) .append(", ")); } if (sb.length() > 3) sb.setLength(sb.length() - 2); return sb.append("}").toString(); } catch (Exception e) { sb.append(e); return sb.toString(); } } }