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