2 * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
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
8 package org.opendaylight.protocol.bgp.rib.impl;
10 import com.google.common.base.Preconditions;
11 import com.google.common.collect.Interner;
12 import com.google.common.collect.Interners;
13 import com.google.common.collect.MapMaker;
14 import java.util.concurrent.ConcurrentMap;
15 import javax.annotation.Nonnull;
16 import javax.annotation.Nullable;
17 import javax.annotation.concurrent.NotThreadSafe;
18 import org.opendaylight.protocol.bgp.rib.impl.spi.AbstractImportPolicy;
19 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev130925.rib.tables.Attributes;
20 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
21 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
24 * A caching decorator for {@link AbstractImportPolicy}. Performs caching of effective
25 * attributes using an identity-and-hashCode-based map for fast lookup and reuse of resulting
29 final class CachingImportPolicy extends AbstractImportPolicy {
31 // A dummy ContainerNode, stored in the cache to indicate null effective attributes
32 private static final ContainerNode MASKED_NULL = ImmutableNodes.containerNode(Attributes.QNAME);
34 // We maintain a weak cache of returned effective attributes, so we end up reusing
35 // the same instance when asked. We set concurrency level to 1, as we do not expect
36 // the cache to be accessed from multiple threads. That may need to be changed
37 // if we end up sharing the cache across peers.
38 private final ConcurrentMap<ContainerNode, ContainerNode> cache =
39 new MapMaker().concurrencyLevel(1).weakKeys().weakValues().makeMap();
42 * The cache itself is weak, which means we end up with identity hash/comparisons.
43 * That is good, but we want the cache to be effective even when equivalent attributes
44 * are presented. For that purpose we maintain a weak interner, which will allow us
45 * to map attributes to a canonical object without preventing garbage collection.
47 private final Interner<ContainerNode> interner = Interners.newWeakInterner();
49 private final AbstractImportPolicy delegate;
51 CachingImportPolicy(final AbstractImportPolicy delegate) {
52 this.delegate = Preconditions.checkNotNull(delegate);
55 @Nonnull private static ContainerNode maskNull(@Nullable final ContainerNode unmasked) {
56 return unmasked == null ? MASKED_NULL : unmasked;
59 @Nullable private static ContainerNode unmaskNull(@Nonnull final ContainerNode masked) {
60 return MASKED_NULL.equals(masked) ? null : masked;
64 public ContainerNode effectiveAttributes(final ContainerNode attributes) {
65 ContainerNode ret = this.cache.get(attributes);
67 return unmaskNull(ret);
71 * The cache returned empty. The reason for that may be that the attributes
72 * passed in are not identical to the ones forming the cache's key. Intern
73 * the passed attributes, which will result in a canonical reference.
75 * If the returned reference is different, attempt to look up in the cache
76 * again. If the reference is the same, we have just populated the interner
77 * and thus are on the path to create a new cache entry.
79 final ContainerNode interned = this.interner.intern(attributes);
80 if (!interned.equals(attributes)) {
81 final ContainerNode retry = this.cache.get(interned);
83 return unmaskNull(retry);
87 final ContainerNode effective = this.delegate.effectiveAttributes(interned);
90 * Populate the cache. Note that this may have raced with another thread,
91 * in which case we want to reuse the previous entry without replacing it.
92 * Check the result of conditional put and return it unmasked if it happens
93 * to be non-null. That will throw away the attributes we just created,
94 * but that's fine, as they have not leaked to heap yet and will be GC'd
97 final ContainerNode existing = this.cache.putIfAbsent(interned, maskNull(effective));
98 return existing != null ? unmaskNull(existing) : effective;