1/*
2 * Copyright (C) 2011 The Guava Authors
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 * in compliance with the License. You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software distributed under the License
10 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 * or implied. See the License for the specific language governing permissions and limitations under
12 * the License.
13 */
14
15package com.google.common.cache;
16
17import static com.google.common.cache.LocalCache.Strength.STRONG;
18import static com.google.common.cache.TestingRemovalListeners.countingRemovalListener;
19import static com.google.common.collect.Maps.immutableEntry;
20import static org.junit.contrib.truth.Truth.ASSERT;
21
22import com.google.common.base.Function;
23import com.google.common.cache.LocalCache.Strength;
24import com.google.common.cache.TestingRemovalListeners.CountingRemovalListener;
25import com.google.common.collect.ImmutableSet;
26import com.google.common.collect.Iterables;
27
28import junit.framework.TestCase;
29
30import java.lang.ref.WeakReference;
31
32/**
33 * Tests of basic {@link LoadingCache} operations with all possible combinations of key & value
34 * strengths.
35 *
36 * @author mike nonemacher
37 */
38public class CacheReferencesTest extends TestCase {
39
40  private static final CacheLoader<Key,String> KEY_TO_STRING_LOADER =
41      new CacheLoader<Key, String>() {
42        @Override public String load(Key key) {
43          return key.toString();
44        }
45      };
46
47  private CacheBuilderFactory factoryWithAllKeyStrengths() {
48    return new CacheBuilderFactory()
49        .withKeyStrengths(ImmutableSet.of(STRONG, Strength.WEAK))
50        .withValueStrengths(ImmutableSet.of(STRONG, Strength.WEAK, Strength.SOFT));
51  }
52
53  private Iterable<LoadingCache<Key, String>> caches() {
54    CacheBuilderFactory factory = factoryWithAllKeyStrengths();
55    return Iterables.transform(factory.buildAllPermutations(),
56        new Function<CacheBuilder<Object, Object>, LoadingCache<Key, String>>() {
57          @Override public LoadingCache<Key, String> apply(CacheBuilder<Object, Object> builder) {
58            return builder.build(KEY_TO_STRING_LOADER);
59          }
60        });
61  }
62
63  public void testContainsKeyAndValue() {
64    for (LoadingCache<Key, String> cache : caches()) {
65      // maintain strong refs so these won't be collected, regardless of cache's key/value strength
66      Key key = new Key(1);
67      String value = key.toString();
68      assertSame(value, cache.getUnchecked(key));
69      assertTrue(cache.asMap().containsKey(key));
70      assertTrue(cache.asMap().containsValue(value));
71      assertEquals(1, cache.size());
72    }
73  }
74
75  public void testClear() {
76    for (LoadingCache<Key, String> cache : caches()) {
77      Key key = new Key(1);
78      String value = key.toString();
79      assertSame(value, cache.getUnchecked(key));
80      assertFalse(cache.asMap().isEmpty());
81      cache.invalidateAll();
82      assertEquals(0, cache.size());
83      assertTrue(cache.asMap().isEmpty());
84      assertFalse(cache.asMap().containsKey(key));
85      assertFalse(cache.asMap().containsValue(value));
86    }
87  }
88
89  public void testKeySetEntrySetValues() {
90    for (LoadingCache<Key, String> cache : caches()) {
91      Key key1 = new Key(1);
92      String value1 = key1.toString();
93      Key key2 = new Key(2);
94      String value2 = key2.toString();
95      assertSame(value1, cache.getUnchecked(key1));
96      assertSame(value2, cache.getUnchecked(key2));
97      assertEquals(ImmutableSet.of(key1, key2), cache.asMap().keySet());
98      ASSERT.that(cache.asMap().values()).hasContentsAnyOrder(value1, value2);
99      assertEquals(ImmutableSet.of(immutableEntry(key1, value1), immutableEntry(key2, value2)),
100          cache.asMap().entrySet());
101    }
102  }
103
104  public void testInvalidate() {
105    for (LoadingCache<Key, String> cache : caches()) {
106      Key key1 = new Key(1);
107      String value1 = key1.toString();
108      Key key2 = new Key(2);
109      String value2 = key2.toString();
110      assertSame(value1, cache.getUnchecked(key1));
111      assertSame(value2, cache.getUnchecked(key2));
112      cache.invalidate(key1);
113      assertFalse(cache.asMap().containsKey(key1));
114      assertTrue(cache.asMap().containsKey(key2));
115      assertEquals(1, cache.size());
116      assertEquals(ImmutableSet.of(key2), cache.asMap().keySet());
117      ASSERT.that(cache.asMap().values()).hasContentsAnyOrder(value2);
118      assertEquals(ImmutableSet.of(immutableEntry(key2, value2)), cache.asMap().entrySet());
119    }
120  }
121
122  public void testCleanupOnReferenceCollection() {
123    for (CacheBuilder<Object, Object> builder
124        : factoryWithAllKeyStrengths().buildAllPermutations()) {
125      if (builder.keyStrength == STRONG && builder.valueStrength == STRONG) {
126        continue;
127      }
128      CountingRemovalListener<Integer, String> removalListener = countingRemovalListener();
129      CacheLoader<Integer, String> toStringLoader =
130          new CacheLoader<Integer, String>() {
131            @Override public String load(Integer key) {
132              return key.toString();
133            }
134          };
135      LoadingCache<Integer, String> cache =
136          builder.removalListener(removalListener).build(toStringLoader);
137
138      // ints in [-128, 127] have their wrappers precomputed and cached, so they won't be GCed
139      Integer key1 = 1001;
140      Integer key2 = 1002;
141      String value1 = cache.getUnchecked(key1);
142      String value2 = cache.getUnchecked(key2);
143      // make (key1, value1) eligible for collection
144      key1 = null;
145      value1 = null;
146      assertCleanup(cache, removalListener);
147      // make sure the GC isn't going to see key2 or value2 as dead in assertCleanup
148      assertSame(value2, cache.getUnchecked(key2));
149    }
150  }
151
152  private void assertCleanup(LoadingCache<Integer, String> cache,
153      CountingRemovalListener<Integer, String> removalListener) {
154
155    // initialSize will most likely be 2, but it's possible for the GC to have already run, so we'll
156    // observe a size of 1
157    long initialSize = cache.size();
158    assertTrue(initialSize == 1 || initialSize == 2);
159
160    // wait up to 5s
161    byte[] filler = new byte[1024];
162    for (int i = 0; i < 500; i++) {
163      System.gc();
164
165      CacheTesting.drainReferenceQueues(cache);
166      if (cache.size() == 1) {
167        break;
168      }
169      try {
170        Thread.sleep(10);
171      } catch (InterruptedException e) { /* ignore */}
172      try {
173        // Fill up heap so soft references get cleared.
174        filler = new byte[Math.max(filler.length, filler.length * 2)];
175      } catch (OutOfMemoryError e) {}
176    }
177
178    CacheTesting.processPendingNotifications(cache);
179    assertEquals(1, cache.size());
180    assertEquals(1, removalListener.getCount());
181  }
182
183  // A simple type whose .toString() will return the same value each time, but without maintaining
184  // a strong reference to that value.
185  static class Key {
186    private final int value;
187    private WeakReference<String> toString;
188
189    Key(int value) {
190      this.value = value;
191    }
192
193    @Override public synchronized String toString() {
194      String s;
195      if (toString != null) {
196        s = toString.get();
197        if (s != null) {
198          return s;
199        }
200      }
201      s = Integer.toString(value);
202      toString = new WeakReference<String>(s);
203      return s;
204    }
205  }
206}
207