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 
15 package com.google.common.cache;
16 
17 import com.google.common.base.Function;
18 import com.google.common.base.MoreObjects;
19 import com.google.common.base.Objects;
20 import com.google.common.base.Optional;
21 import com.google.common.base.Preconditions;
22 import com.google.common.cache.LocalCache.Strength;
23 import com.google.common.collect.Iterables;
24 import com.google.common.collect.Lists;
25 import com.google.common.collect.Sets;
26 import java.util.List;
27 import java.util.Set;
28 import java.util.concurrent.TimeUnit;
29 import org.checkerframework.checker.nullness.compatqual.NullableDecl;
30 
31 /**
32  * Helper class for creating {@link CacheBuilder} instances with all combinations of several sets of
33  * parameters.
34  *
35  * @author mike nonemacher
36  */
37 class CacheBuilderFactory {
38   // Default values contain only 'null', which means don't call the CacheBuilder method (just give
39   // the CacheBuilder default).
40   private Set<Integer> concurrencyLevels = Sets.newHashSet((Integer) null);
41   private Set<Integer> initialCapacities = Sets.newHashSet((Integer) null);
42   private Set<Integer> maximumSizes = Sets.newHashSet((Integer) null);
43   private Set<DurationSpec> expireAfterWrites = Sets.newHashSet((DurationSpec) null);
44   private Set<DurationSpec> expireAfterAccesses = Sets.newHashSet((DurationSpec) null);
45   private Set<DurationSpec> refreshes = Sets.newHashSet((DurationSpec) null);
46   private Set<Strength> keyStrengths = Sets.newHashSet((Strength) null);
47   private Set<Strength> valueStrengths = Sets.newHashSet((Strength) null);
48 
withConcurrencyLevels(Set<Integer> concurrencyLevels)49   CacheBuilderFactory withConcurrencyLevels(Set<Integer> concurrencyLevels) {
50     this.concurrencyLevels = Sets.newLinkedHashSet(concurrencyLevels);
51     return this;
52   }
53 
withInitialCapacities(Set<Integer> initialCapacities)54   CacheBuilderFactory withInitialCapacities(Set<Integer> initialCapacities) {
55     this.initialCapacities = Sets.newLinkedHashSet(initialCapacities);
56     return this;
57   }
58 
withMaximumSizes(Set<Integer> maximumSizes)59   CacheBuilderFactory withMaximumSizes(Set<Integer> maximumSizes) {
60     this.maximumSizes = Sets.newLinkedHashSet(maximumSizes);
61     return this;
62   }
63 
withExpireAfterWrites(Set<DurationSpec> durations)64   CacheBuilderFactory withExpireAfterWrites(Set<DurationSpec> durations) {
65     this.expireAfterWrites = Sets.newLinkedHashSet(durations);
66     return this;
67   }
68 
withExpireAfterAccesses(Set<DurationSpec> durations)69   CacheBuilderFactory withExpireAfterAccesses(Set<DurationSpec> durations) {
70     this.expireAfterAccesses = Sets.newLinkedHashSet(durations);
71     return this;
72   }
73 
withRefreshes(Set<DurationSpec> durations)74   CacheBuilderFactory withRefreshes(Set<DurationSpec> durations) {
75     this.refreshes = Sets.newLinkedHashSet(durations);
76     return this;
77   }
78 
withKeyStrengths(Set<Strength> keyStrengths)79   CacheBuilderFactory withKeyStrengths(Set<Strength> keyStrengths) {
80     this.keyStrengths = Sets.newLinkedHashSet(keyStrengths);
81     Preconditions.checkArgument(!this.keyStrengths.contains(Strength.SOFT));
82     return this;
83   }
84 
withValueStrengths(Set<Strength> valueStrengths)85   CacheBuilderFactory withValueStrengths(Set<Strength> valueStrengths) {
86     this.valueStrengths = Sets.newLinkedHashSet(valueStrengths);
87     return this;
88   }
89 
buildAllPermutations()90   Iterable<CacheBuilder<Object, Object>> buildAllPermutations() {
91     @SuppressWarnings("unchecked")
92     Iterable<List<Object>> combinations =
93         buildCartesianProduct(
94             concurrencyLevels,
95             initialCapacities,
96             maximumSizes,
97             expireAfterWrites,
98             expireAfterAccesses,
99             refreshes,
100             keyStrengths,
101             valueStrengths);
102     return Iterables.transform(
103         combinations,
104         new Function<List<Object>, CacheBuilder<Object, Object>>() {
105           @Override
106           public CacheBuilder<Object, Object> apply(List<Object> combination) {
107             return createCacheBuilder(
108                 (Integer) combination.get(0),
109                 (Integer) combination.get(1),
110                 (Integer) combination.get(2),
111                 (DurationSpec) combination.get(3),
112                 (DurationSpec) combination.get(4),
113                 (DurationSpec) combination.get(5),
114                 (Strength) combination.get(6),
115                 (Strength) combination.get(7));
116           }
117         });
118   }
119 
120   private static final Function<Object, Optional<?>> NULLABLE_TO_OPTIONAL =
121       new Function<Object, Optional<?>>() {
122         @Override
123         public Optional<?> apply(@NullableDecl Object obj) {
124           return Optional.fromNullable(obj);
125         }
126       };
127 
128   private static final Function<Optional<?>, Object> OPTIONAL_TO_NULLABLE =
129       new Function<Optional<?>, Object>() {
130         @Override
131         public Object apply(Optional<?> optional) {
132           return optional.orNull();
133         }
134       };
135 
136   /**
137    * Sets.cartesianProduct doesn't allow sets that contain null, but we want null to mean "don't
138    * call the associated CacheBuilder method" - that is, get the default CacheBuilder behavior. This
139    * method wraps the elements in the input sets (which may contain null) as Optionals, calls
140    * Sets.cartesianProduct with those, then transforms the result to unwrap the Optionals.
141    */
142   private Iterable<List<Object>> buildCartesianProduct(Set<?>... sets) {
143     List<Set<Optional<?>>> optionalSets = Lists.newArrayListWithExpectedSize(sets.length);
144     for (Set<?> set : sets) {
145       Set<Optional<?>> optionalSet =
146           Sets.newLinkedHashSet(Iterables.transform(set, NULLABLE_TO_OPTIONAL));
147       optionalSets.add(optionalSet);
148     }
149     Set<List<Optional<?>>> cartesianProduct = Sets.cartesianProduct(optionalSets);
150     return Iterables.transform(
151         cartesianProduct,
152         new Function<List<Optional<?>>, List<Object>>() {
153           @Override
154           public List<Object> apply(List<Optional<?>> objs) {
155             return Lists.transform(objs, OPTIONAL_TO_NULLABLE);
156           }
157         });
158   }
159 
160   private CacheBuilder<Object, Object> createCacheBuilder(
161       Integer concurrencyLevel,
162       Integer initialCapacity,
163       Integer maximumSize,
164       DurationSpec expireAfterWrite,
165       DurationSpec expireAfterAccess,
166       DurationSpec refresh,
167       Strength keyStrength,
168       Strength valueStrength) {
169 
170     CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder();
171     if (concurrencyLevel != null) {
172       builder.concurrencyLevel(concurrencyLevel);
173     }
174     if (initialCapacity != null) {
175       builder.initialCapacity(initialCapacity);
176     }
177     if (maximumSize != null) {
178       builder.maximumSize(maximumSize);
179     }
180     if (expireAfterWrite != null) {
181       builder.expireAfterWrite(expireAfterWrite.duration, expireAfterWrite.unit);
182     }
183     if (expireAfterAccess != null) {
184       builder.expireAfterAccess(expireAfterAccess.duration, expireAfterAccess.unit);
185     }
186     if (refresh != null) {
187       builder.refreshAfterWrite(refresh.duration, refresh.unit);
188     }
189     if (keyStrength != null) {
190       builder.setKeyStrength(keyStrength);
191     }
192     if (valueStrength != null) {
193       builder.setValueStrength(valueStrength);
194     }
195     return builder;
196   }
197 
198   static class DurationSpec {
199     private final long duration;
200     private final TimeUnit unit;
201 
202     private DurationSpec(long duration, TimeUnit unit) {
203       this.duration = duration;
204       this.unit = unit;
205     }
206 
207     public static DurationSpec of(long duration, TimeUnit unit) {
208       return new DurationSpec(duration, unit);
209     }
210 
211     @Override
212     public int hashCode() {
213       return Objects.hashCode(duration, unit);
214     }
215 
216     @Override
217     public boolean equals(Object o) {
218       if (o instanceof DurationSpec) {
219         DurationSpec that = (DurationSpec) o;
220         return unit.toNanos(duration) == that.unit.toNanos(that.duration);
221       }
222       return false;
223     }
224 
225     @Override
226     public String toString() {
227       return MoreObjects.toStringHelper(this)
228           .add("duration", duration)
229           .add("unit", unit)
230           .toString();
231     }
232   }
233 }
234