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