82111c21f4312bb88610fec214111cfb741d6b37
[yangtools.git] / common / object-cache-api / src / main / java / org / opendaylight / yangtools / objcache / spi / AbstractObjectCache.java
1 /*
2  * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
3  *
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
7  */
8 package org.opendaylight.yangtools.objcache.spi;
9
10 import static java.util.Objects.requireNonNull;
11
12 import com.google.common.annotations.VisibleForTesting;
13 import com.google.common.base.FinalizableReferenceQueue;
14 import com.google.common.base.FinalizableSoftReference;
15 import com.google.common.cache.Cache;
16 import java.util.concurrent.ExecutionException;
17 import javax.annotation.Nonnull;
18 import org.opendaylight.yangtools.concepts.ProductAwareBuilder;
19 import org.opendaylight.yangtools.objcache.ObjectCache;
20 import org.slf4j.Logger;
21 import org.slf4j.LoggerFactory;
22
23 /**
24  * Abstract object cache implementation. This implementation takes care
25  * of interacting with the user and manages interaction with the Garbage
26  * Collector (via soft references). Subclasses are expected to provide
27  * a backing {@link Cache} instance and provide the
28  */
29 public abstract class AbstractObjectCache implements ObjectCache {
30     /**
31      * Key used when looking up a ProductAwareBuilder product. We assume
32      * the builder is not being modified for the duration of the lookup,
33      * anything else is the user's fault.
34      */
35     @VisibleForTesting
36     static final class BuilderKey {
37         private final ProductAwareBuilder<?> builder;
38
39         private BuilderKey(final ProductAwareBuilder<?> builder) {
40             this.builder = requireNonNull(builder);
41         }
42
43         @Override
44         public int hashCode() {
45             return builder.productHashCode();
46         }
47
48         @Override
49         public boolean equals(Object obj) {
50             /*
51              * We can tolerate null objects coming our way, but we need
52              * to be on the lookout for WeakKeys, as we cannot pass them
53              * directly to productEquals().
54              */
55             if (obj instanceof SoftKey) {
56                 obj = ((SoftKey<?>)obj).get();
57             }
58
59             return builder.productEquals(obj);
60         }
61     }
62
63     /**
64      * Key used in the underlying map. It is essentially a soft reference, with
65      * slightly special properties.
66      *
67      * <p>
68      * It acts as a proxy for the object it refers to and essentially delegates
69      * to it. There are three exceptions here:
70      *
71      * <p>
72      * 1) This key needs to have a cached hash code. The requirement is that the
73      *    key needs to be able to look itself up after the reference to the object
74      *    has been cleared (and thus we can no longer look it up from there). One
75      *    typical container where we are stored are HashMaps -- and they need it
76      *    to be constant.
77      * 2) This key does not tolerate checks to see if its equal to null. While we
78      *    could return false, we want to catch offenders who try to store nulls
79      *    in the cache.
80      * 3) This key inverts the check for equality, e.g. it calls equals() on the
81      *    object which was passed to its equals(). Instead of supplying itself,
82      *    it supplies the referent. If the soft reference is cleared, such check
83      *    will return false, which is fine as it prevents normal lookup from
84      *    seeing the cleared key. Removal is handled by the explicit identity
85      *    check.
86      */
87     protected abstract static class SoftKey<T> extends FinalizableSoftReference<T> {
88         private final int hashCode;
89
90         public SoftKey(final T referent, final FinalizableReferenceQueue queue) {
91             super(requireNonNull(referent), queue);
92             hashCode = referent.hashCode();
93         }
94
95         @Override
96         public boolean equals(final Object obj) {
97             if (obj == null) {
98                 return false;
99             }
100
101             // Order is important: we do not want to call equals() on ourselves!
102             return this == obj || obj.equals(get());
103         }
104
105         @Override
106         public int hashCode() {
107             return hashCode;
108         }
109     }
110
111     private static final Logger LOG = LoggerFactory.getLogger(AbstractObjectCache.class);
112     private final FinalizableReferenceQueue queue;
113     private final Cache<SoftKey<?>, Object> cache;
114
115     protected AbstractObjectCache(final Cache<SoftKey<?>, Object> cache, final FinalizableReferenceQueue queue) {
116         this.queue = requireNonNull(queue);
117         this.cache = requireNonNull(cache);
118     }
119
120     protected <T> SoftKey<T> createSoftKey(final T object) {
121         /*
122          * This may look like a race (having a soft reference and not have
123          * it in the cache). In fact this is protected by the fact we still
124          * have a strong reference on the object in our arguments and that
125          * reference survives past method return since we return it.
126          */
127         return new SoftKey<T>(object, queue) {
128             @Override
129             public void finalizeReferent() {
130                 /*
131                  * NOTE: while it may be tempting to add "object" into this
132                  *       trace message, do not ever do that: it would retain
133                  *       a strong reference, preventing collection.
134                  */
135                 LOG.trace("Invalidating key {}", this);
136                 cache.invalidate(this);
137             }
138         };
139     }
140
141     @Override
142     public final <B extends ProductAwareBuilder<P>, P> P getProduct(@Nonnull final B builder) {
143         throw new UnsupportedOperationException();
144 //        LOG.debug("Looking up product for {}", builder);
145 //
146 //        @SuppressWarnings("unchecked")
147 //        final P ret = (P) cache.getIfPresent(new BuilderKey(builder));
148 //        return ret == null ? put(Preconditions.checkNotNull(builder.toInstance())) : ret;
149     }
150
151     @Override
152     @SuppressWarnings("unchecked")
153     public final <T> T getReference(final T object) {
154         LOG.debug("Looking up reference for {}", object);
155         if (object == null) {
156             return null;
157         }
158
159         final SoftKey<T> key = createSoftKey(object);
160         try {
161             return (T) cache.get(key, () -> object);
162         } catch (ExecutionException e) {
163             throw new IllegalStateException("Failed to load value", e);
164         }
165     }
166 }