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