2 * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
8 package org.opendaylight.yangtools.util;
10 import static com.google.common.base.Preconditions.checkArgument;
12 import com.google.common.collect.ImmutableMap;
13 import com.google.common.collect.Maps;
14 import java.util.Collections;
15 import java.util.HashMap;
17 import org.slf4j.Logger;
18 import org.slf4j.LoggerFactory;
19 import tech.pantheon.triemap.TrieMap;
22 * A simple layer on top of maps, which performs snapshot mediation and optimization of
23 * what the underlying implementation is.
25 public final class MapAdaptor {
26 public static final int DEFAULT_COPY_MAX_ITEMS = 100;
27 public static final String COPY_MAX_ITEMS_MAX_PROP = "org.opendaylight.yangtools.util.mapadaptor.maxcopy";
29 public static final int DEFAULT_PERSIST_MIN_ITEMS = 50;
30 public static final String PERSIST_MIN_ITEMS_PROP = "org.opendaylight.yangtools.util.mapadaptor.minpersist";
32 private static final Logger LOG = LoggerFactory.getLogger(MapAdaptor.class);
33 private static final MapAdaptor DEFAULT_INSTANCE;
35 private final boolean useSingleton;
36 private final int persistMinItems;
37 private final int copyMaxItems;
40 DEFAULT_INSTANCE = new MapAdaptor(true,
41 getProperty(COPY_MAX_ITEMS_MAX_PROP, DEFAULT_COPY_MAX_ITEMS),
42 getProperty(PERSIST_MIN_ITEMS_PROP, DEFAULT_PERSIST_MIN_ITEMS));
43 LOG.debug("Configured HashMap/TrieMap cutoff at {}/{} entries",
44 DEFAULT_INSTANCE.persistMinItems, DEFAULT_INSTANCE.copyMaxItems);
47 private static int getProperty(final String name, final int defaultValue) {
48 final int val = Integer.getInteger(name, defaultValue);
53 LOG.warn("Ignoring illegal value of {}: has to be a positive number", name);
57 private MapAdaptor(final boolean useSingleton, final int copyMaxItems, final int persistMinItems) {
58 this.useSingleton = useSingleton;
59 this.copyMaxItems = copyMaxItems;
60 this.persistMinItems = persistMinItems;
64 * Return the default-configured instance.
66 * @return the singleton global instance
68 public static MapAdaptor getDefaultInstance() {
69 return DEFAULT_INSTANCE;
72 public static MapAdaptor getInstance(final boolean useSingleton, final int copyMaxItems,
73 final int persistMinItems) {
74 checkArgument(copyMaxItems >= 0, "copyMaxItems has to be a non-negative integer");
75 checkArgument(persistMinItems >= 0, "persistMinItems has to be a positive integer");
76 checkArgument(persistMinItems <= copyMaxItems, "persistMinItems must be less than or equal to copyMaxItems");
77 return new MapAdaptor(useSingleton, copyMaxItems, persistMinItems);
81 * Creates an initial snapshot. The backing map is selected according to the expected size.
83 * @param expectedSize Expected map size
84 * @return An empty mutable map.
86 public <K, V> Map<K, V> initialSnapshot(final int expectedSize) {
87 checkArgument(expectedSize >= 0);
88 if (expectedSize > persistMinItems) {
89 return new ReadWriteTrieMap<>();
92 if (expectedSize < 2) {
93 return new HashMap<>(1);
95 if (expectedSize == 2) {
96 return new HashMap<>(2);
98 return Maps.newHashMapWithExpectedSize(expectedSize);
102 * Input is treated is supposed to be left unmodified, result must be mutable.
104 * @param input input map
105 * @return An isolated, read-write snapshot of input map
106 * @throws NullPointerException if input is null
108 @SuppressWarnings("static-method")
109 public <K, V> Map<K, V> takeSnapshot(final Map<K, V> input) {
110 return input instanceof ReadOnlyTrieMap<K, V> rotm ? rotm.toReadWrite() : toReadWrite(input);
113 private static <K, V> Map<K, V> toReadWrite(final Map<K, V> input) {
114 LOG.trace("Converting input {} to a HashMap", input);
117 * The default HashMap copy constructor performs a bad thing for small maps, using the default capacity of 16
118 * as the minimum sizing hint, which can lead to wasted memory. Since the HashMap grows in powers-of-two, we
119 * only kick this in if we are storing 6 entries or less, as that results in 8-entry map -- the next power is
120 * 16, which is the default.
123 final int size = input.size();
125 final var target = switch (size) {
131 ret = new HashMap<>(target);
134 ret = switch (input) {
135 case HashMap<K, V> hm -> {
136 // HashMap supports cloning, but we want to make sure we trim it down if entries were removed, so we
137 // do this only after having checked for small sizes.
138 @SuppressWarnings("unchecked")
139 final var tmp = (Map<K, V>) hm.clone();
142 default -> new HashMap<>(input);
146 LOG.trace("Read-write HashMap is {}", ret);
151 * Input will be thrown away, result will be retained for read-only access or {@link #takeSnapshot(Map)} purposes.
153 * @param input non-optimized (read-write) map
154 * @return optimized read-only map
155 * @throws NullPointerException if input is null
157 public <K, V> Map<K, V> optimize(final Map<K, V> input) {
158 if (input instanceof ReadOnlyTrieMap) {
159 LOG.warn("Optimizing read-only map {}", input);
162 final int size = input.size();
168 LOG.trace("Reducing input {} to an empty map", input);
169 return ImmutableMap.of();
173 * We retain the persistent map as long as it holds at least
176 if (input instanceof ReadWriteTrieMap<K, V> rwtm && size >= persistMinItems) {
177 return rwtm.toReadOnly();
181 * If the user opted to use singleton maps, use them. Except for the case
182 * when persistMinItems dictates we should not move off of the persistent
185 if (useSingleton && size == 1) {
186 final var e = input.entrySet().iterator().next();
187 final var ret = Collections.singletonMap(e.getKey(), e.getValue());
188 LOG.trace("Reducing input {} to singleton map {}", input, ret);
192 if (size <= copyMaxItems) {
194 * Favor access speed: use a HashMap and copy it on modification.
196 if (input instanceof HashMap) {
200 LOG.trace("Copying input {} to a HashMap ({} entries)", input, size);
201 final var ret = new HashMap<>(input);
202 LOG.trace("Read-only HashMap is {}", ret);
207 * Favor isolation speed: use a TrieMap and perform snapshots
209 * This one is a bit tricky, as the TrieMap is concurrent and does not
210 * keep an uptodate size. Updating it requires a full walk -- which is
211 * O(N) and we want to avoid that. So we wrap it in an interceptor,
212 * which will maintain the size for us.
214 LOG.trace("Copying input {} to a TrieMap ({} entries)", input, size);
215 final var map = TrieMap.<K, V>create();
217 final var ret = new ReadOnlyTrieMap<>(map, size);
218 LOG.trace("Read-only TrieMap is {}", ret);