X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=blobdiff_plain;f=common%2Futil%2Fsrc%2Fmain%2Fjava%2Forg%2Fopendaylight%2Fyangtools%2Futil%2FOffsetMapCache.java;h=b18c218f2185e1920928e0a35a905b79da9e62ea;hb=6ba98a2fab390cafe7a98e466189ea8df09cdeb9;hp=0863e286f6c37be1cc4dc9d66261a9623a38f3dc;hpb=b5baf0c5df8e298a4c50197038c2dd2a6d71abb1;p=yangtools.git diff --git a/common/util/src/main/java/org/opendaylight/yangtools/util/OffsetMapCache.java b/common/util/src/main/java/org/opendaylight/yangtools/util/OffsetMapCache.java index 0863e286f6..b18c218f21 100644 --- a/common/util/src/main/java/org/opendaylight/yangtools/util/OffsetMapCache.java +++ b/common/util/src/main/java/org/opendaylight/yangtools/util/OffsetMapCache.java @@ -7,36 +7,123 @@ */ package org.opendaylight.yangtools.util; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Verify; +import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap.Builder; +import com.google.common.collect.ImmutableSet; +import java.lang.reflect.Array; import java.util.Collection; +import java.util.Iterator; +import java.util.List; import java.util.Map; +import java.util.Set; final class OffsetMapCache { - private static final LoadingCache, Map> CACHE = - CacheBuilder.newBuilder().weakValues().build(new CacheLoader, Map>() { + /* + * Cache for offsets where order matters. The key is a List, which defines the iteration order. Since we want + * to retain this order, it is okay to use a simple LoadingCache. + */ + private static final LoadingCache, Map> ORDERED_CACHE = + CacheBuilder.newBuilder().weakValues().build(new CacheLoader, Map>() { @Override - public Map load(final Collection key) { - final Builder b = ImmutableMap.builder(); - int i = 0; - - for (Object arg : key) { - b.put(arg, i++); - } - - return b.build(); + public Map load(final List key) { + return createMap(key); } }); + /* + * Cache for offsets where order does not mapper. The key is a Set of elements. We use manual two-stage loading + * because of the nature of the objects we store as values, which is ImmutableMaps. An ImmutableMap, when queried + * for keys (as is done in ImmutableOffsetMap.keySet()), will instantiate an ImmutableSet to hold these keys. It + * would be wasteful to use one Set for lookup only to have the map have an exact copy. + * + * We perform the first look up using a Set (which may come from the user, for example via + * ImmutableOffsetMap.unorderedCopyOf()), hence potentially saving a copy operation. If we fail to find an entry, + * we construct the map and put it conditionally with Map.keySet() as the key. This will detect concurrent loading + * and also lead to the cache and the map sharing the same Set. + */ + private static final Cache, Map> UNORDERED_CACHE = + CacheBuilder.newBuilder().weakValues().build(); private OffsetMapCache() { throw new UnsupportedOperationException(); } + @VisibleForTesting + static void invalidateCache() { + ORDERED_CACHE.invalidateAll(); + UNORDERED_CACHE.invalidateAll(); + } + + @SuppressWarnings("unchecked") + static Map orderedOffsets(final Collection args) { + if (args.size() == 1) { + return unorderedOffsets(args); + } + + return (Map) ORDERED_CACHE.getUnchecked(ImmutableList.copyOf(args)); + } + + static Map unorderedOffsets(final Collection args) { + return unorderedOffsets(args instanceof Set ? (Set)args : ImmutableSet.copyOf(args)); + } + + static V[] adjustedArray(final Map offsets, final List keys, final V[] array) { + Verify.verify(offsets.size() == keys.size(), "Offsets %s do not match keys %s", offsets, keys); + + // This relies on the fact that offsets has an ascending iterator + final Iterator oi = offsets.keySet().iterator(); + final Iterator ki = keys.iterator(); + + while (oi.hasNext()) { + final K o = oi.next(); + final K k = ki.next(); + if (!k.equals(o)) { + return adjustArray(offsets, keys, array); + } + } + + return array; + } + + private static Map createMap(final Collection keys) { + final Builder b = ImmutableMap.builder(); + int i = 0; + + for (T arg : keys) { + b.put(arg, i++); + } + + return b.build(); + } + @SuppressWarnings("unchecked") - static Map offsetsFor(final Collection args) { - return (Map) CACHE.getUnchecked(args); + private static Map unorderedOffsets(final Set args) { + final Map existing = (Map) UNORDERED_CACHE.getIfPresent(args); + if (existing != null) { + return existing; + } + + final Map newMap = createMap(args); + final Map raced = UNORDERED_CACHE.asMap().putIfAbsent(newMap.keySet(), newMap); + return raced == null ? newMap : (Map)raced; + } + + private static V[] adjustArray(final Map offsets, final List keys, final V[] array) { + @SuppressWarnings("unchecked") + final V[] ret = (V[]) Array.newInstance(array.getClass().getComponentType(), array.length); + + int i = 0; + for (final K k : keys) { + final Integer o = Verify.verifyNotNull(offsets.get(k), "Key %s not present in offsets %s", k, offsets); + ret[o] = array[i++]; + } + + return ret; } }