Migrate to tech.pantheon.TrieMap
[yangtools.git] / common / util / src / main / java / org / opendaylight / yangtools / util / MapAdaptor.java
index d1d8b75806d1d54fcc684230680872b5a147580a..eee8287f191a5ac6a0c908609943cc46b5100786 100644 (file)
@@ -7,17 +7,18 @@
  */
 package org.opendaylight.yangtools.util;
 
+import static com.google.common.base.Preconditions.checkArgument;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Maps;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Map.Entry;
-
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-
-import com.google.common.base.Preconditions;
-import com.google.common.collect.Iterables;
-import com.romix.scala.collection.concurrent.TrieMap;
+import tech.pantheon.triemap.MutableTrieMap;
+import tech.pantheon.triemap.TrieMap;
 
 /**
  * A simple layer on top of maps, which performs snapshot mediation and optimization of
@@ -45,24 +46,13 @@ public final class MapAdaptor {
                 DEFAULT_INSTANCE.persistMinItems, DEFAULT_INSTANCE.copyMaxItems);
     }
 
-    private static final int getProperty(final String name, final int defaultValue) {
-        try {
-            final String p = System.getProperty(name);
-            if (p != null) {
-                try {
-                    int pi = Integer.valueOf(p);
-                    if (pi <= 0) {
-                        LOG.warn("Ignoring illegal value of {}: has to be a positive number", name);
-                    } else {
-                        return pi;
-                    }
-                } catch (NumberFormatException e) {
-                    LOG.warn("Ignoring non-numerical value of {}", name, e);
-                }
-            }
-        } catch (Exception e) {
-            LOG.debug("Failed to get {}", name, e);
+    private static int getProperty(final String name, final int defaultValue) {
+        final int val = Integer.getInteger(name, defaultValue).intValue();
+        if (val > 0) {
+            return val;
         }
+
+        LOG.warn("Ignoring illegal value of {}: has to be a positive number", name);
         return defaultValue;
     }
 
@@ -81,19 +71,40 @@ public final class MapAdaptor {
         return DEFAULT_INSTANCE;
     }
 
-    public static MapAdaptor getInstance(final boolean useSingleton, final int copyMaxItems, final int persistMinItems) {
-        Preconditions.checkArgument(copyMaxItems >= 0, "copyMaxItems has to be a non-negative integer");
-        Preconditions.checkArgument(persistMinItems >= 0, "persistMinItems has to be a positive integer");
-        Preconditions.checkArgument(persistMinItems <= copyMaxItems, "persistMinItems must be less than or equal to copyMaxItems");
+    public static MapAdaptor getInstance(final boolean useSingleton, final int copyMaxItems,
+            final int persistMinItems) {
+        checkArgument(copyMaxItems >= 0, "copyMaxItems has to be a non-negative integer");
+        checkArgument(persistMinItems >= 0, "persistMinItems has to be a positive integer");
+        checkArgument(persistMinItems <= copyMaxItems, "persistMinItems must be less than or equal to copyMaxItems");
         return new MapAdaptor(useSingleton, copyMaxItems, persistMinItems);
     }
 
     /**
-     * Input is treated is supposed to be left unmodified, result must be mutable.
+     * Creates an initial snapshot. The backing map is selected according to
+     * the expected size.
      *
-     * @param input
-     * @return
+     * @param expectedSize Expected map size
+     * @return An empty mutable map.
      */
+    public <K, V> Map<K, V> initialSnapshot(final int expectedSize) {
+        checkArgument(expectedSize >= 0);
+        if (expectedSize > persistMinItems) {
+            return new ReadWriteTrieMap<>();
+        }
+
+        if (expectedSize < 2) {
+            return new HashMap<>(1);
+        }
+        if (expectedSize == 2) {
+            return new HashMap<>(2);
+        }
+        return Maps.newHashMapWithExpectedSize(expectedSize);
+    }
+
+    /**
+     * Input is treated is supposed to be left unmodified, result must be mutable.
+     */
+    @SuppressWarnings("static-method")
     public <K, V> Map<K, V> takeSnapshot(final Map<K, V> input) {
         if (input instanceof ReadOnlyTrieMap) {
             return ((ReadOnlyTrieMap<K, V>)input).toReadWrite();
@@ -101,9 +112,43 @@ public final class MapAdaptor {
 
         LOG.trace("Converting input {} to a HashMap", input);
 
-        // FIXME: be a bit smart about allocation based on observed size
+        /*
+         * The default HashMap copy constructor performs a bad thing for small maps, using the default capacity of 16
+         * as the minimum sizing hint, which can lead to wasted memory. Since the HashMap grows in powers-of-two, we
+         * only kick this in if we are storing 6 entries or less, as that results in 8-entry map -- the next power is
+         * 16, which is the default.
+         */
+        final Map<K, V> ret;
+        final int size = input.size();
+        if (size <= 6) {
+            final int target;
+            switch (size) {
+                case 0:
+                case 1:
+                    target = 1;
+                    break;
+                case 2:
+                    target = 2;
+                    break;
+                case 3:
+                    target = 4;
+                    break;
+                default:
+                    target = 8;
+            }
+
+            ret = new HashMap<>(target);
+            ret.putAll(input);
+        } else if (input instanceof HashMap) {
+            // HashMap supports cloning, but we want to make sure we trim it down if entries were removed, so we do
+            // this only after having checked for small sizes.
+            @SuppressWarnings("unchecked")
+            final Map<K, V> tmp = (Map<K, V>) ((HashMap<K, V>) input).clone();
+            ret = tmp;
+        } else {
+            ret = new HashMap<>(input);
+        }
 
-        final Map<K, V> ret = new HashMap<>(input);
         LOG.trace("Read-write HashMap is {}", ret);
         return ret;
     }
@@ -112,8 +157,8 @@ public final class MapAdaptor {
      * Input will be thrown away, result will be retained for read-only access or
      * {@link #takeSnapshot(Map)} purposes.
      *
-     * @param input
-     * @return
+     * @param input non-optimized (read-write) map
+     * @return  optimized read-only map
      */
     public <K, V> Map<K, V> optimize(final Map<K, V> input) {
         if (input instanceof ReadOnlyTrieMap) {
@@ -127,7 +172,7 @@ public final class MapAdaptor {
          */
         if (size == 0) {
             LOG.trace("Reducing input {} to an empty map", input);
-            return Collections.<K, V>emptyMap();
+            return ImmutableMap.of();
         }
 
         /*
@@ -144,9 +189,9 @@ public final class MapAdaptor {
          * map.
          */
         if (useSingleton && size == 1) {
-            final Entry<K, V> e = Iterables.getOnlyElement(input.entrySet());
+            final Entry<K, V> e = input.entrySet().iterator().next();
             final Map<K, V> ret = Collections.singletonMap(e.getKey(), e.getValue());
-            LOG.trace("Reducing input () to singleton map {}", input, ret);
+            LOG.trace("Reducing input {} to singleton map {}", input, ret);
             return ret;
         }
 
@@ -173,7 +218,7 @@ public final class MapAdaptor {
          * which will maintain the size for us.
          */
         LOG.trace("Copying input {} to a TrieMap ({} entries)", input, size);
-        final TrieMap<K, V> map = TrieMap.empty();
+        final MutableTrieMap<K, V> map = TrieMap.create();
         map.putAll(input);
         final Map<K, V> ret = new ReadOnlyTrieMap<>(map, size);
         LOG.trace("Read-only TrieMap is {}", ret);