Merge "BUG-1276: fixed generated union constructor"
[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 java.util.Collections;
11 import java.util.HashMap;
12 import java.util.Map;
13 import java.util.Map.Entry;
14
15 import org.slf4j.Logger;
16 import org.slf4j.LoggerFactory;
17
18 import com.google.common.base.Preconditions;
19 import com.google.common.collect.Iterables;
20 import com.romix.scala.collection.concurrent.TrieMap;
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 final int getProperty(final String name, final int defaultValue) {
49         try {
50             final String p = System.getProperty(name);
51             if (p != null) {
52                 try {
53                     int pi = Integer.valueOf(p);
54                     if (pi <= 0) {
55                         LOG.warn("Ignoring illegal value of {}: has to be a positive number", name);
56                     } else {
57                         return pi;
58                     }
59                 } catch (NumberFormatException e) {
60                     LOG.warn("Ignoring non-numerical value of {}", name, e);
61                 }
62             }
63         } catch (Exception e) {
64             LOG.debug("Failed to get {}", name, e);
65         }
66         return defaultValue;
67     }
68
69     private MapAdaptor(final boolean useSingleton, final int copyMaxItems, final int persistMinItems) {
70         this.useSingleton = useSingleton;
71         this.copyMaxItems = copyMaxItems;
72         this.persistMinItems = persistMinItems;
73     }
74
75     /**
76      * Return the default-configured instance.
77      *
78      * @return the singleton global instance
79      */
80     public static MapAdaptor getDefaultInstance() {
81         return DEFAULT_INSTANCE;
82     }
83
84     public static MapAdaptor getInstance(final boolean useSingleton, final int copyMaxItems, 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, "persistMinItems must be less than or equal to copyMaxItems");
88         return new MapAdaptor(useSingleton, copyMaxItems, persistMinItems);
89     }
90
91     /**
92      * Input is treated is supposed to be left unmodified, result must be mutable.
93      *
94      * @param input
95      * @return
96      */
97     public <K, V> Map<K, V> takeSnapshot(final Map<K, V> input) {
98         if (input instanceof ReadOnlyTrieMap) {
99             return ((ReadOnlyTrieMap<K, V>)input).toReadWrite();
100         }
101
102         LOG.trace("Converting input {} to a HashMap", input);
103
104         // FIXME: be a bit smart about allocation based on observed size
105
106         final Map<K, V> ret = new HashMap<>(input);
107         LOG.trace("Read-write HashMap is {}", ret);
108         return ret;
109     }
110
111     /**
112      * Input will be thrown away, result will be retained for read-only access or
113      * {@link #takeSnapshot(Map)} purposes.
114      *
115      * @param input
116      * @return
117      */
118     public <K, V> Map<K, V> optimize(final Map<K, V> input) {
119         if (input instanceof ReadOnlyTrieMap) {
120             LOG.warn("Optimizing read-only map {}", input);
121         }
122
123         final int size = input.size();
124
125         /*
126          * No-brainer :)
127          */
128         if (size == 0) {
129             LOG.trace("Reducing input {} to an empty map", input);
130             return Collections.<K, V>emptyMap();
131         }
132
133         /*
134          * We retain the persistent map as long as it holds at least
135          * persistMinItems
136          */
137         if (input instanceof ReadWriteTrieMap && size >= persistMinItems) {
138             return ((ReadWriteTrieMap<K, V>)input).toReadOnly();
139         }
140
141         /*
142          * If the user opted to use singleton maps, use them. Except for the case
143          * when persistMinItems dictates we should not move off of the persistent
144          * map.
145          */
146         if (useSingleton && size == 1) {
147             final Entry<K, V> e = Iterables.getOnlyElement(input.entrySet());
148             final Map<K, V> ret = Collections.singletonMap(e.getKey(), e.getValue());
149             LOG.trace("Reducing input () to singleton map {}", input, ret);
150             return ret;
151         }
152
153         if (size <= copyMaxItems) {
154             /*
155              * Favor access speed: use a HashMap and copy it on modification.
156              */
157             if (input instanceof HashMap) {
158                 return input;
159             }
160
161             LOG.trace("Copying input {} to a HashMap ({} entries)", input, size);
162             final Map<K, V> ret = new HashMap<>(input);
163             LOG.trace("Read-only HashMap is {}", ret);
164             return ret;
165         }
166
167         /*
168          * Favor isolation speed: use a TrieMap and perform snapshots
169          *
170          * This one is a bit tricky, as the TrieMap is concurrent and does not
171          * keep an uptodate size. Updating it requires a full walk -- which is
172          * O(N) and we want to avoid that. So we wrap it in an interceptor,
173          * which will maintain the size for us.
174          */
175         LOG.trace("Copying input {} to a TrieMap ({} entries)", input, size);
176         final TrieMap<K, V> map = TrieMap.empty();
177         map.putAll(input);
178         final Map<K, V> ret = new ReadOnlyTrieMap<>(map, size);
179         LOG.trace("Read-only TrieMap is {}", ret);
180         return ret;
181     }
182 }