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