/* * 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 com.google.common.annotations.VisibleForTesting; import com.google.common.base.FinalizableReferenceQueue; import com.google.common.base.FinalizableSoftReference; import com.google.common.base.Preconditions; import com.google.common.cache.Cache; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import javax.annotation.Nonnull; import org.opendaylight.yangtools.concepts.ProductAwareBuilder; import org.opendaylight.yangtools.objcache.ObjectCache; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * 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. */ @VisibleForTesting 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 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. */ protected abstract static class SoftKey<T> extends FinalizableSoftReference<T> { private final int hashCode; public SoftKey(final T referent, final FinalizableReferenceQueue queue) { super(Preconditions.checkNotNull(referent), queue); hashCode = referent.hashCode(); } @Override public boolean equals(final Object obj) { if (obj == null) { return false; } // 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<SoftKey<?>, Object> cache; protected AbstractObjectCache(final Cache<SoftKey<?>, Object> cache, final FinalizableReferenceQueue queue) { this.queue = Preconditions.checkNotNull(queue); this.cache = Preconditions.checkNotNull(cache); } protected <T> SoftKey<T> createSoftKey(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. */ return new SoftKey<T>(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 {}", this); cache.invalidate(this); } }; } @Override public final <B extends ProductAwareBuilder<P>, P> P getProduct(@Nonnull final B builder) { throw new UnsupportedOperationException(); // 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 @SuppressWarnings("unchecked") public final <T> T getReference(final T object) { LOG.debug("Looking up reference for {}", object); if (object == null) { return null; } final SoftKey<T> key = createSoftKey(object); try { return (T) cache.get(key, new Callable<T>() { @Override public T call() { return object; } }); } catch (ExecutionException e) { throw new IllegalStateException("Failed to load value", e); } } }