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.qual.Nullable; 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 Iterable<List<Object>> combinations = 92 buildCartesianProduct( 93 concurrencyLevels, 94 initialCapacities, 95 maximumSizes, 96 expireAfterWrites, 97 expireAfterAccesses, 98 refreshes, 99 keyStrengths, 100 valueStrengths); 101 return Iterables.transform( 102 combinations, 103 new Function<List<Object>, CacheBuilder<Object, Object>>() { 104 @Override 105 public CacheBuilder<Object, Object> apply(List<Object> combination) { 106 return createCacheBuilder( 107 (Integer) combination.get(0), 108 (Integer) combination.get(1), 109 (Integer) combination.get(2), 110 (DurationSpec) combination.get(3), 111 (DurationSpec) combination.get(4), 112 (DurationSpec) combination.get(5), 113 (Strength) combination.get(6), 114 (Strength) combination.get(7)); 115 } 116 }); 117 } 118 119 private static final Function<Object, Optional<?>> NULLABLE_TO_OPTIONAL = 120 new Function<Object, Optional<?>>() { 121 @Override 122 public Optional<?> apply(@Nullable Object obj) { 123 return Optional.fromNullable(obj); 124 } 125 }; 126 127 private static final Function<Optional<?>, @Nullable Object> OPTIONAL_TO_NULLABLE = 128 new Function<Optional<?>, @Nullable Object>() { 129 @Override 130 public @Nullable Object apply(Optional<?> optional) { 131 return optional.orNull(); 132 } 133 }; 134 135 /** 136 * Sets.cartesianProduct doesn't allow sets that contain null, but we want null to mean "don't 137 * call the associated CacheBuilder method" - that is, get the default CacheBuilder behavior. This 138 * method wraps the elements in the input sets (which may contain null) as Optionals, calls 139 * Sets.cartesianProduct with those, then transforms the result to unwrap the Optionals. 140 */ 141 private Iterable<List<Object>> buildCartesianProduct(Set<?>... sets) { 142 List<Set<Optional<?>>> optionalSets = Lists.newArrayListWithExpectedSize(sets.length); 143 for (Set<?> set : sets) { 144 Set<Optional<?>> optionalSet = 145 Sets.newLinkedHashSet(Iterables.transform(set, NULLABLE_TO_OPTIONAL)); 146 optionalSets.add(optionalSet); 147 } 148 Set<List<Optional<?>>> cartesianProduct = Sets.cartesianProduct(optionalSets); 149 return Iterables.transform( 150 cartesianProduct, 151 new Function<List<Optional<?>>, List<Object>>() { 152 @Override 153 public List<Object> apply(List<Optional<?>> objs) { 154 return Lists.transform(objs, OPTIONAL_TO_NULLABLE); 155 } 156 }); 157 } 158 159 private CacheBuilder<Object, Object> createCacheBuilder( 160 @Nullable Integer concurrencyLevel, 161 @Nullable Integer initialCapacity, 162 @Nullable Integer maximumSize, 163 @Nullable DurationSpec expireAfterWrite, 164 @Nullable DurationSpec expireAfterAccess, 165 @Nullable DurationSpec refresh, 166 @Nullable Strength keyStrength, 167 @Nullable Strength valueStrength) { 168 169 CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder(); 170 if (concurrencyLevel != null) { 171 builder.concurrencyLevel(concurrencyLevel); 172 } 173 if (initialCapacity != null) { 174 builder.initialCapacity(initialCapacity); 175 } 176 if (maximumSize != null) { 177 builder.maximumSize(maximumSize); 178 } 179 if (expireAfterWrite != null) { 180 builder.expireAfterWrite(expireAfterWrite.duration, expireAfterWrite.unit); 181 } 182 if (expireAfterAccess != null) { 183 builder.expireAfterAccess(expireAfterAccess.duration, expireAfterAccess.unit); 184 } 185 if (refresh != null) { 186 builder.refreshAfterWrite(refresh.duration, refresh.unit); 187 } 188 if (keyStrength != null) { 189 builder.setKeyStrength(keyStrength); 190 } 191 if (valueStrength != null) { 192 builder.setValueStrength(valueStrength); 193 } 194 return builder; 195 } 196 197 static class DurationSpec { 198 private final long duration; 199 private final TimeUnit unit; 200 201 private DurationSpec(long duration, TimeUnit unit) { 202 this.duration = duration; 203 this.unit = unit; 204 } 205 206 public static DurationSpec of(long duration, TimeUnit unit) { 207 return new DurationSpec(duration, unit); 208 } 209 210 @Override 211 public int hashCode() { 212 return Objects.hashCode(duration, unit); 213 } 214 215 @Override 216 public boolean equals(@Nullable Object o) { 217 if (o instanceof DurationSpec) { 218 DurationSpec that = (DurationSpec) o; 219 return unit.toNanos(duration) == that.unit.toNanos(that.duration); 220 } 221 return false; 222 } 223 224 @Override 225 public String toString() { 226 return MoreObjects.toStringHelper(this) 227 .add("duration", duration) 228 .add("unit", unit) 229 .toString(); 230 } 231 } 232 } 233