org.opendaylight.yangtools.util.MapAdaptor.java Source code

Java tutorial

Introduction

Here is the source code for org.opendaylight.yangtools.util.MapAdaptor.java

Source

/*
 * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
 * and is available at http://www.eclipse.org/legal/epl-v10.html
 */
package org.opendaylight.yangtools.util;

import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.romix.scala.collection.concurrent.TrieMap;
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;

/**
 * A simple layer on top of maps, which performs snapshot mediation and optimization of
 * what the underlying implementation is.
 */
public final class MapAdaptor {
    public static final int DEFAULT_COPY_MAX_ITEMS = 100;
    public static final String COPY_MAX_ITEMS_MAX_PROP = "org.opendaylight.yangtools.util.mapadaptor.maxcopy";

    public static final int DEFAULT_PERSIST_MIN_ITEMS = 50;
    public static final String PERSIST_MIN_ITEMS_PROP = "org.opendaylight.yangtools.util.mapadaptor.minpersist";

    private static final Logger LOG = LoggerFactory.getLogger(MapAdaptor.class);
    private static final MapAdaptor DEFAULT_INSTANCE;

    private final boolean useSingleton;
    private final int persistMinItems;
    private final int copyMaxItems;

    static {
        DEFAULT_INSTANCE = new MapAdaptor(true, getProperty(COPY_MAX_ITEMS_MAX_PROP, DEFAULT_COPY_MAX_ITEMS),
                getProperty(PERSIST_MIN_ITEMS_PROP, DEFAULT_PERSIST_MIN_ITEMS));
        LOG.debug("Configured HashMap/TrieMap cutoff at {}/{} entries", DEFAULT_INSTANCE.persistMinItems,
                DEFAULT_INSTANCE.copyMaxItems);
    }

    private static 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);
        }
        return defaultValue;
    }

    private MapAdaptor(final boolean useSingleton, final int copyMaxItems, final int persistMinItems) {
        this.useSingleton = useSingleton;
        this.copyMaxItems = copyMaxItems;
        this.persistMinItems = persistMinItems;
    }

    /**
     * Return the default-configured instance.
     *
     * @return the singleton global instance
     */
    public static MapAdaptor getDefaultInstance() {
        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");
        return new MapAdaptor(useSingleton, copyMaxItems, persistMinItems);
    }

    /**
     * Creates an initial snapshot. The backing map is selected according to
     * the expected size.
     *
     * @param expectedSize Expected map size
     * @return An empty mutable map.
     */
    public <K, V> Map<K, V> initialSnapshot(final int expectedSize) {
        Preconditions.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();
        }

        LOG.trace("Converting input {} to a HashMap", input);

        /*
         * 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);
        }

        LOG.trace("Read-write HashMap is {}", ret);
        return ret;
    }

    /**
     * Input will be thrown away, result will be retained for read-only access or
     * {@link #takeSnapshot(Map)} purposes.
     *
     * @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) {
            LOG.warn("Optimizing read-only map {}", input);
        }

        final int size = input.size();

        /*
         * No-brainer :)
         */
        if (size == 0) {
            LOG.trace("Reducing input {} to an empty map", input);
            return ImmutableMap.of();
        }

        /*
         * We retain the persistent map as long as it holds at least
         * persistMinItems
         */
        if (input instanceof ReadWriteTrieMap && size >= persistMinItems) {
            return ((ReadWriteTrieMap<K, V>) input).toReadOnly();
        }

        /*
         * If the user opted to use singleton maps, use them. Except for the case
         * when persistMinItems dictates we should not move off of the persistent
         * map.
         */
        if (useSingleton && size == 1) {
            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);
            return ret;
        }

        if (size <= copyMaxItems) {
            /*
             * Favor access speed: use a HashMap and copy it on modification.
             */
            if (input instanceof HashMap) {
                return input;
            }

            LOG.trace("Copying input {} to a HashMap ({} entries)", input, size);
            final Map<K, V> ret = new HashMap<>(input);
            LOG.trace("Read-only HashMap is {}", ret);
            return ret;
        }

        /*
         * Favor isolation speed: use a TrieMap and perform snapshots
         *
         * This one is a bit tricky, as the TrieMap is concurrent and does not
         * keep an uptodate size. Updating it requires a full walk -- which is
         * O(N) and we want to avoid that. So we wrap it in an interceptor,
         * which will maintain the size for us.
         */
        LOG.trace("Copying input {} to a TrieMap ({} entries)", input, size);
        final TrieMap<K, V> map = new TrieMap<>();
        map.putAll(input);
        final Map<K, V> ret = new ReadOnlyTrieMap<>(map, size);
        LOG.trace("Read-only TrieMap is {}", ret);
        return ret;
    }
}