/* * Copyright 2015 the original author or authors. * * 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 io.atomix.collections.internal; import io.atomix.catalyst.concurrent.Scheduled; import io.atomix.collections.DistributedMultiMap; import io.atomix.copycat.server.Commit; import io.atomix.resource.ResourceStateMachine; import java.time.Duration; import java.util.*; /** * Map state machine. * * @author <a href="http://github.com/kuujo">Jordan Halterman</a> */ public class MultiMapState extends ResourceStateMachine { private final Map<Object, Map<Object, Commit<? extends MultiMapCommands.TtlCommand>>> map = new HashMap<>(); private final Map<Long, Scheduled> timers = new HashMap<>(); private final DistributedMultiMap.Order order; public MultiMapState(Properties properties) { super(properties); this.order = DistributedMultiMap.Order.valueOf(config.getProperty("order", DistributedMultiMap.Order.INSERT.name().toLowerCase()).toUpperCase()); } /** * Creates a new value map. */ private Map<Object, Commit<? extends MultiMapCommands.TtlCommand>> createValueMap() { switch (order) { case NONE: return new HashMap<>(); case NATURAL: return new TreeMap<>(); case INSERT: return new LinkedHashMap<>(); default: return new HashMap<>(); } } /** * Handles a contains key commit. */ public boolean containsKey(Commit<MultiMapCommands.ContainsKey> commit) { try { return map.containsKey(commit.operation().key()); } finally { commit.close(); } } /** * Handles a get commit. */ public Collection get(Commit<MultiMapCommands.Get> commit) { try { Map<Object, Commit<? extends MultiMapCommands.TtlCommand>> values = map.get(commit.operation().key()); if (values == null) { return Collections.EMPTY_LIST; } Collection<Object> results = new ArrayList<>(values.size()); for (Commit<? extends MultiMapCommands.TtlCommand> value : values.values()) { results.add(value.operation().value()); } return results; } finally { commit.close(); } } /** * Handles a put commit. */ public boolean put(Commit<MultiMapCommands.Put> commit) { try { Map<Object, Commit<? extends MultiMapCommands.TtlCommand>> values = map.get(commit.operation().key()); if (values == null) { values = createValueMap(); map.put(commit.operation().key(), values); } final Map<Object, Commit<? extends MultiMapCommands.TtlCommand>> keyValues = values; if (!values.containsKey(commit.operation().value())) { Scheduled timer = commit.operation().ttl() > 0 ? executor.schedule(Duration.ofMillis(commit.operation().ttl()), () -> { keyValues.remove(commit.operation().value()).close(); }) : null; values.put(commit.operation().value(), commit); timers.put(commit.index(), timer); return true; } else { commit.close(); return false; } } catch (Exception e) { commit.close(); throw e; } } /** * Handles a remove commit. */ public Object remove(Commit<MultiMapCommands.Remove> commit) { try { if (commit.operation().value() != null) { Map<Object, Commit<? extends MultiMapCommands.TtlCommand>> values = map.get(commit.operation().key()); if (values == null) { return false; } Commit<? extends MultiMapCommands.TtlCommand> previous = values.remove(commit.operation().value()); if (previous == null) { return false; } Scheduled timer = timers.remove(previous.index()); if (timer != null) timer.cancel(); previous.close(); if (values.isEmpty()) map.remove(commit.operation().key()); return true; } else { Map<Object, Commit<? extends MultiMapCommands.TtlCommand>> values = map.remove(commit.operation().key()); if (values != null) { Collection<Object> results = new ArrayList<>(values.size()); for (Commit<? extends MultiMapCommands.TtlCommand> value : values.values()) { Scheduled timer = timers.remove(value.index()); if (timer != null) timer.cancel(); results.add(value.operation().value()); value.close(); } return results; } return Collections.EMPTY_LIST; } } finally { commit.close(); } } /** * Handles a remove value commit. */ public void removeValue(Commit<MultiMapCommands.RemoveValue> commit) { try { Iterator<Map.Entry<Object, Map<Object, Commit<? extends MultiMapCommands.TtlCommand>>>> outerIterator = map.entrySet().iterator(); while (outerIterator.hasNext()) { Map<Object, Commit<? extends MultiMapCommands.TtlCommand>> map = outerIterator.next().getValue(); Iterator<Map.Entry<Object, Commit<? extends MultiMapCommands.TtlCommand>>> innerIterator = map.entrySet().iterator(); while (innerIterator.hasNext()) { Map.Entry<Object, Commit<? extends MultiMapCommands.TtlCommand>> entry = innerIterator.next(); if ((entry.getValue().operation().value() == null && commit.operation().value() == null) || (entry.getValue().operation().value() != null && commit.operation().value() != null && entry.getValue().operation().value().equals(commit.operation().value()))) { Scheduled timer = timers.remove(entry.getValue().index()); if (timer != null) timer.cancel(); entry.getValue().close(); innerIterator.remove(); } } if (map.isEmpty()) { outerIterator.remove(); } } } finally { commit.close(); } } /** * Handles a size commit. */ public int size(Commit<MultiMapCommands.Size> commit) { try { if (commit.operation().key() != null) { Map<Object, Commit<? extends MultiMapCommands.TtlCommand>> values = map.get(commit.operation().key()); return values != null ? values.size() : 0; } else { int size = 0; for (Map.Entry<Object, Map<Object, Commit<? extends MultiMapCommands.TtlCommand>>> entry : map.entrySet()) { size += entry.getValue().size(); } return size; } } finally { commit.close(); } } /** * Handles an is empty commit. */ public boolean isEmpty(Commit<MultiMapCommands.IsEmpty> commit) { try { return map == null || map.isEmpty(); } finally { commit.close(); } } /** * Handles a clear commit. */ public void clear(Commit<MultiMapCommands.Clear> commit) { try { delete(); } finally { commit.close(); } } @Override public void delete() { Iterator<Map.Entry<Object, Map<Object, Commit<? extends MultiMapCommands.TtlCommand>>>> iterator = map.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry<Object, Map<Object, Commit<? extends MultiMapCommands.TtlCommand>>> entry = iterator.next(); for (Commit<? extends MultiMapCommands.TtlCommand> value : entry.getValue().values()) { Scheduled timer = timers.remove(value.index()); if (timer != null) timer.cancel(); value.close(); } iterator.remove(); } } }