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.collect.Maps.immutableEntry;
19import static com.google.common.truth.Truth.assertThat;
20
21import com.google.common.base.Function;
22import com.google.common.cache.LocalCache.Strength;
23import com.google.common.cache.TestingRemovalListeners.CountingRemovalListener;
24import com.google.common.collect.ImmutableSet;
25import com.google.common.collect.Iterables;
26
27import junit.framework.TestCase;
28
29import java.lang.ref.WeakReference;
30
31/**
32 * Tests of basic {@link LoadingCache} operations with all possible combinations of key & value
33 * strengths.
34 *
35 * @author mike nonemacher
36 */
37public class CacheReferencesTest extends TestCase {
38
39  private static final CacheLoader<Key,String> KEY_TO_STRING_LOADER =
40      new CacheLoader<Key, String>() {
41        @Override public String load(Key key) {
42          return key.toString();
43        }
44      };
45
46  private CacheBuilderFactory factoryWithAllKeyStrengths() {
47    return new CacheBuilderFactory()
48        .withKeyStrengths(ImmutableSet.of(STRONG, Strength.WEAK))
49        .withValueStrengths(ImmutableSet.of(STRONG, Strength.WEAK, Strength.SOFT));
50  }
51
52  private Iterable<LoadingCache<Key, String>> caches() {
53    CacheBuilderFactory factory = factoryWithAllKeyStrengths();
54    return Iterables.transform(factory.buildAllPermutations(),
55        new Function<CacheBuilder<Object, Object>, LoadingCache<Key, String>>() {
56          @Override public LoadingCache<Key, String> apply(CacheBuilder<Object, Object> builder) {
57            return builder.build(KEY_TO_STRING_LOADER);
58          }
59        });
60  }
61
62  public void testContainsKeyAndValue() {
63    for (LoadingCache<Key, String> cache : caches()) {
64      // maintain strong refs so these won't be collected, regardless of cache's key/value strength
65      Key key = new Key(1);
66      String value = key.toString();
67      assertSame(value, cache.getUnchecked(key));
68      assertTrue(cache.asMap().containsKey(key));
69      assertTrue(cache.asMap().containsValue(value));
70      assertEquals(1, cache.size());
71    }
72  }
73
74  public void testClear() {
75    for (LoadingCache<Key, String> cache : caches()) {
76      Key key = new Key(1);
77      String value = key.toString();
78      assertSame(value, cache.getUnchecked(key));
79      assertFalse(cache.asMap().isEmpty());
80      cache.invalidateAll();
81      assertEquals(0, cache.size());
82      assertTrue(cache.asMap().isEmpty());
83      assertFalse(cache.asMap().containsKey(key));
84      assertFalse(cache.asMap().containsValue(value));
85    }
86  }
87
88  public void testKeySetEntrySetValues() {
89    for (LoadingCache<Key, String> cache : caches()) {
90      Key key1 = new Key(1);
91      String value1 = key1.toString();
92      Key key2 = new Key(2);
93      String value2 = key2.toString();
94      assertSame(value1, cache.getUnchecked(key1));
95      assertSame(value2, cache.getUnchecked(key2));
96      assertEquals(ImmutableSet.of(key1, key2), cache.asMap().keySet());
97      assertThat(cache.asMap().values()).has().exactly(value1, value2);
98      assertEquals(ImmutableSet.of(immutableEntry(key1, value1), immutableEntry(key2, value2)),
99          cache.asMap().entrySet());
100    }
101  }
102
103  public void testInvalidate() {
104    for (LoadingCache<Key, String> cache : caches()) {
105      Key key1 = new Key(1);
106      String value1 = key1.toString();
107      Key key2 = new Key(2);
108      String value2 = key2.toString();
109      assertSame(value1, cache.getUnchecked(key1));
110      assertSame(value2, cache.getUnchecked(key2));
111      cache.invalidate(key1);
112      assertFalse(cache.asMap().containsKey(key1));
113      assertTrue(cache.asMap().containsKey(key2));
114      assertEquals(1, cache.size());
115      assertEquals(ImmutableSet.of(key2), cache.asMap().keySet());
116      assertThat(cache.asMap().values()).has().item(value2);
117      assertEquals(ImmutableSet.of(immutableEntry(key2, value2)), cache.asMap().entrySet());
118    }
119  }
120
121  // fails in Maven with 64-bit JDK: http://code.google.com/p/guava-libraries/issues/detail?id=1568
122
123  private void assertCleanup(LoadingCache<Integer, String> cache,
124      CountingRemovalListener<Integer, String> removalListener) {
125
126    // initialSize will most likely be 2, but it's possible for the GC to have already run, so we'll
127    // observe a size of 1
128    long initialSize = cache.size();
129    assertTrue(initialSize == 1 || initialSize == 2);
130
131    // wait up to 5s
132    byte[] filler = new byte[1024];
133    for (int i = 0; i < 500; i++) {
134      System.gc();
135
136      CacheTesting.drainReferenceQueues(cache);
137      if (cache.size() == 1) {
138        break;
139      }
140      try {
141        Thread.sleep(10);
142      } catch (InterruptedException e) { /* ignore */}
143      try {
144        // Fill up heap so soft references get cleared.
145        filler = new byte[Math.max(filler.length, filler.length * 2)];
146      } catch (OutOfMemoryError e) {}
147    }
148
149    CacheTesting.processPendingNotifications(cache);
150    assertEquals(1, cache.size());
151    assertEquals(1, removalListener.getCount());
152  }
153
154  // A simple type whose .toString() will return the same value each time, but without maintaining
155  // a strong reference to that value.
156  static class Key {
157    private final int value;
158    private WeakReference<String> toString;
159
160    Key(int value) {
161      this.value = value;
162    }
163
164    @Override public synchronized String toString() {
165      String s;
166      if (toString != null) {
167        s = toString.get();
168        if (s != null) {
169          return s;
170        }
171      }
172      s = Integer.toString(value);
173      toString = new WeakReference<String>(s);
174      return s;
175    }
176  }
177}
178