6ca8710b185827002033224bd930a8450c4bd39c
[yangtools.git] / common / util / src / main / java / org / opendaylight / yangtools / util / MapAdaptor.java
1 /*
2  * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
3  *
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
7  */
8 package org.opendaylight.yangtools.util;
9
10 import com.google.common.base.Preconditions;
11 import com.google.common.collect.ImmutableMap;
12 import com.google.common.collect.Maps;
13 import java.util.Collections;
14 import java.util.HashMap;
15 import java.util.Map;
16 import java.util.Map.Entry;
17 import org.opendaylight.yangtools.triemap.MutableTrieMap;
18 import org.opendaylight.yangtools.triemap.TrieMap;
19 import org.slf4j.Logger;
20 import org.slf4j.LoggerFactory;
21
22 /**
23  * A simple layer on top of maps, which performs snapshot mediation and optimization of
24  * what the underlying implementation is.
25  */
26 public final class MapAdaptor {
27     public static final int DEFAULT_COPY_MAX_ITEMS = 100;
28     public static final String COPY_MAX_ITEMS_MAX_PROP = "org.opendaylight.yangtools.util.mapadaptor.maxcopy";
29
30     public static final int DEFAULT_PERSIST_MIN_ITEMS = 50;
31     public static final String PERSIST_MIN_ITEMS_PROP = "org.opendaylight.yangtools.util.mapadaptor.minpersist";
32
33     private static final Logger LOG = LoggerFactory.getLogger(MapAdaptor.class);
34     private static final MapAdaptor DEFAULT_INSTANCE;
35
36     private final boolean useSingleton;
37     private final int persistMinItems;
38     private final int copyMaxItems;
39
40     static {
41         DEFAULT_INSTANCE = new MapAdaptor(true,
42                 getProperty(COPY_MAX_ITEMS_MAX_PROP, DEFAULT_COPY_MAX_ITEMS),
43                 getProperty(PERSIST_MIN_ITEMS_PROP, DEFAULT_PERSIST_MIN_ITEMS));
44         LOG.debug("Configured HashMap/TrieMap cutoff at {}/{} entries",
45                 DEFAULT_INSTANCE.persistMinItems, DEFAULT_INSTANCE.copyMaxItems);
46     }
47
48     private static int getProperty(final String name, final int defaultValue) {
49         final int val = Integer.getInteger(name, defaultValue).intValue();
50         if (val > 0) {
51             return val;
52         }
53
54         LOG.warn("Ignoring illegal value of {}: has to be a positive number", name);
55         return defaultValue;
56     }
57
58     private MapAdaptor(final boolean useSingleton, final int copyMaxItems, final int persistMinItems) {
59         this.useSingleton = useSingleton;
60         this.copyMaxItems = copyMaxItems;
61         this.persistMinItems = persistMinItems;
62     }
63
64     /**
65      * Return the default-configured instance.
66      *
67      * @return the singleton global instance
68      */
69     public static MapAdaptor getDefaultInstance() {
70         return DEFAULT_INSTANCE;
71     }
72
73     public static MapAdaptor getInstance(final boolean useSingleton, final int copyMaxItems,
74             final int persistMinItems) {
75         Preconditions.checkArgument(copyMaxItems >= 0, "copyMaxItems has to be a non-negative integer");
76         Preconditions.checkArgument(persistMinItems >= 0, "persistMinItems has to be a positive integer");
77         Preconditions.checkArgument(persistMinItems <= copyMaxItems,
78                 "persistMinItems must be less than or equal to copyMaxItems");
79         return new MapAdaptor(useSingleton, copyMaxItems, persistMinItems);
80     }
81
82     /**
83      * Creates an initial snapshot. The backing map is selected according to
84      * the expected size.
85      *
86      * @param expectedSize Expected map size
87      * @return An empty mutable map.
88      */
89     public <K, V> Map<K, V> initialSnapshot(final int expectedSize) {
90         Preconditions.checkArgument(expectedSize >= 0);
91         if (expectedSize > persistMinItems) {
92             return new ReadWriteTrieMap<>();
93         }
94
95         if (expectedSize < 2) {
96             return new HashMap<>(1);
97         }
98         if (expectedSize == 2) {
99             return new HashMap<>(2);
100         }
101         return Maps.newHashMapWithExpectedSize(expectedSize);
102     }
103
104     /**
105      * Input is treated is supposed to be left unmodified, result must be mutable.
106      */
107     @SuppressWarnings("static-method")
108     public <K, V> Map<K, V> takeSnapshot(final Map<K, V> input) {
109         if (input instanceof ReadOnlyTrieMap) {
110             return ((ReadOnlyTrieMap<K, V>)input).toReadWrite();
111         }
112
113         LOG.trace("Converting input {} to a HashMap", input);
114
115         /*
116          * The default HashMap copy constructor performs a bad thing for small maps, using the default capacity of 16
117          * as the minimum sizing hint, which can lead to wasted memory. Since the HashMap grows in powers-of-two, we
118          * only kick this in if we are storing 6 entries or less, as that results in 8-entry map -- the next power is
119          * 16, which is the default.
120          */
121         final Map<K, V> ret;
122         final int size = input.size();
123         if (size <= 6) {
124             final int target;
125             switch (size) {
126                 case 0:
127                 case 1:
128                     target = 1;
129                     break;
130                 case 2:
131                     target = 2;
132                     break;
133                 case 3:
134                     target = 4;
135                     break;
136                 default:
137                     target = 8;
138             }
139
140             ret = new HashMap<>(target);
141             ret.putAll(input);
142         } else if (input instanceof HashMap) {
143             // HashMap supports cloning, but we want to make sure we trim it down if entries were removed, so we do
144             // this only after having checked for small sizes.
145             @SuppressWarnings("unchecked")
146             final Map<K, V> tmp = (Map<K, V>) ((HashMap<K, V>) input).clone();
147             ret = tmp;
148         } else {
149             ret = new HashMap<>(input);
150         }
151
152         LOG.trace("Read-write HashMap is {}", ret);
153         return ret;
154     }
155
156     /**
157      * Input will be thrown away, result will be retained for read-only access or
158      * {@link #takeSnapshot(Map)} purposes.
159      *
160      * @param input non-optimized (read-write) map
161      * @return  optimized read-only map
162      */
163     public <K, V> Map<K, V> optimize(final Map<K, V> input) {
164         if (input instanceof ReadOnlyTrieMap) {
165             LOG.warn("Optimizing read-only map {}", input);
166         }
167
168         final int size = input.size();
169
170         /*
171          * No-brainer :)
172          */
173         if (size == 0) {
174             LOG.trace("Reducing input {} to an empty map", input);
175             return ImmutableMap.of();
176         }
177
178         /*
179          * We retain the persistent map as long as it holds at least
180          * persistMinItems
181          */
182         if (input instanceof ReadWriteTrieMap && size >= persistMinItems) {
183             return ((ReadWriteTrieMap<K, V>)input).toReadOnly();
184         }
185
186         /*
187          * If the user opted to use singleton maps, use them. Except for the case
188          * when persistMinItems dictates we should not move off of the persistent
189          * map.
190          */
191         if (useSingleton && size == 1) {
192             final Entry<K, V> e = input.entrySet().iterator().next();
193             final Map<K, V> ret = Collections.singletonMap(e.getKey(), e.getValue());
194             LOG.trace("Reducing input {} to singleton map {}", input, ret);
195             return ret;
196         }
197
198         if (size <= copyMaxItems) {
199             /*
200              * Favor access speed: use a HashMap and copy it on modification.
201              */
202             if (input instanceof HashMap) {
203                 return input;
204             }
205
206             LOG.trace("Copying input {} to a HashMap ({} entries)", input, size);
207             final Map<K, V> ret = new HashMap<>(input);
208             LOG.trace("Read-only HashMap is {}", ret);
209             return ret;
210         }
211
212         /*
213          * Favor isolation speed: use a TrieMap and perform snapshots
214          *
215          * This one is a bit tricky, as the TrieMap is concurrent and does not
216          * keep an uptodate size. Updating it requires a full walk -- which is
217          * O(N) and we want to avoid that. So we wrap it in an interceptor,
218          * which will maintain the size for us.
219          */
220         LOG.trace("Copying input {} to a TrieMap ({} entries)", input, size);
221         final MutableTrieMap<K, V> map = TrieMap.create();
222         map.putAll(input);
223         final Map<K, V> ret = new ReadOnlyTrieMap<>(map, size);
224         LOG.trace("Read-only TrieMap is {}", ret);
225         return ret;
226     }
227 }