X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=blobdiff_plain;f=common%2Fobject-cache-api%2Fsrc%2Fmain%2Fjava%2Forg%2Fopendaylight%2Fyangtools%2Fobjcache%2Fspi%2FAbstractObjectCache.java;fp=common%2Fobject-cache-api%2Fsrc%2Fmain%2Fjava%2Forg%2Fopendaylight%2Fyangtools%2Fobjcache%2Fspi%2FAbstractObjectCache.java;h=963030cf8b10bc3bcb3695b17f15766a00831680;hb=e5b2e5891368d3c99a484e397997dc9cf138da83;hp=0000000000000000000000000000000000000000;hpb=a791edb78424e32a3b02c92491d941e092248c3f;p=yangtools.git diff --git a/common/object-cache-api/src/main/java/org/opendaylight/yangtools/objcache/spi/AbstractObjectCache.java b/common/object-cache-api/src/main/java/org/opendaylight/yangtools/objcache/spi/AbstractObjectCache.java new file mode 100644 index 0000000000..963030cf8b --- /dev/null +++ b/common/object-cache-api/src/main/java/org/opendaylight/yangtools/objcache/spi/AbstractObjectCache.java @@ -0,0 +1,156 @@ +/* + * 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.objcache.spi; + +import org.opendaylight.yangtools.concepts.ProductAwareBuilder; +import org.opendaylight.yangtools.objcache.ObjectCache; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.base.FinalizableReferenceQueue; +import com.google.common.base.FinalizableSoftReference; +import com.google.common.base.Preconditions; +import com.google.common.cache.Cache; + +/** + * Abstract object cache implementation. This implementation takes care + * of interacting with the user and manages interaction with the Garbage + * Collector (via soft references). Subclasses are expected to provide + * a backing {@link Cache} instance and provide the + */ +public abstract class AbstractObjectCache implements ObjectCache { + /** + * Key used when looking up a ProductAwareBuilder product. We assume + * the builder is not being modified for the duration of the lookup, + * anything else is the user's fault. + */ + private static final class BuilderKey { + private final ProductAwareBuilder builder; + + private BuilderKey(final ProductAwareBuilder builder) { + this.builder = Preconditions.checkNotNull(builder); + } + + @Override + public int hashCode() { + return builder.productHashCode(); + } + + @Override + public boolean equals(Object obj) { + /* + * We can tolerate null objects coming our way, but we need + * to be on the lookout for WeakKeys, as we cannot pass them + * directly to productEquals(). + */ + if (obj != null && obj instanceof SoftKey) { + obj = ((SoftKey)obj).get(); + } + + return builder.productEquals(obj); + } + } + + /** + * Key used in the underlying map. It is essentially a soft reference, with + * slightly special properties. + * + * It acts as a proxy for the object it refers to and essentially delegates + * to it. There are three exceptions here: + * + * 1) This key needs to have a cached hash code. The requirement is that the + * key needs to be able to look itself up after the reference to the object + * has been cleared (and thus we can no longer look it up from there). One + * typical container where we are stored are HashMaps -- and they need it + * to be constant. + * 2) This key does not tolerate checks to see if its equal to null. While we + * could return false, we want to catch offenders who try to store nulls + * in the cache. + * 3) This key inverts the check for equality, e.g. it calls equals() on the + * object which was passed to its equals(). Instead of supplying itself, + * it supplies the referent. If the soft reference is cleared, such check + * will return false, which is fine as it prevents normal lookup from + * seeing the cleared key. Removal is handled by the explicit identity + * check. + */ + private static abstract class SoftKey extends FinalizableSoftReference { + private final int hashCode; + + public SoftKey(final Object referent, final FinalizableReferenceQueue q) { + super(Preconditions.checkNotNull(referent), q); + hashCode = referent.hashCode(); + } + + @Override + public boolean equals(final Object obj) { + Preconditions.checkState(obj != null); + + // Order is important: we do not want to call equals() on ourselves! + return this == obj || obj.equals(get()); + } + + @Override + public int hashCode() { + return hashCode; + } + } + + private static final Logger LOG = LoggerFactory.getLogger(AbstractObjectCache.class); + private final FinalizableReferenceQueue queue; + private final Cache cache; + + protected AbstractObjectCache(final Cache cache, final FinalizableReferenceQueue queue) { + this.queue = Preconditions.checkNotNull(queue); + this.cache = Preconditions.checkNotNull(cache); + } + + private T put(final T object) { + /* + * This may look like a race (having a soft reference and not have + * it in the cache). In fact this is protected by the fact we still + * have a strong reference on the object in our arguments and that + * reference survives past method return since we return it. + */ + final Object key = new SoftKey(object, queue) { + @Override + public void finalizeReferent() { + /* + * NOTE: while it may be tempting to add "object" into this + * trace message, do not ever do that: it would retain + * a strong reference, preventing collection. + */ + LOG.trace("Invalidating key {} for object {}", this); + cache.invalidate(this); + } + }; + cache.put(key, object); + LOG.debug("Cached key {} to object {}", key, object); + return object; + } + + @Override + public final , P> P getProduct(final B builder) { + LOG.debug("Looking up product for {}", builder); + + @SuppressWarnings("unchecked") + final P ret = (P) cache.getIfPresent(new BuilderKey(builder)); + return ret == null ? put(Preconditions.checkNotNull(builder.toInstance())) : ret; + } + + @Override + public final T getReference(final T object) { + LOG.debug("Looking up reference for {}", object); + if (object == null) { + return null; + } + + @SuppressWarnings("unchecked") + final T ret = (T) cache.getIfPresent(object); + return ret == null ? put(object) : ret; + } +}