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.Objects; 19 import com.google.common.base.Optional; 20 import com.google.common.base.Preconditions; 21 import com.google.common.cache.LocalCache.Strength; 22 import com.google.common.collect.Iterables; 23 import com.google.common.collect.Lists; 24 import com.google.common.collect.Sets; 25 26 import java.util.List; 27 import java.util.Set; 28 import java.util.concurrent.TimeUnit; 29 30 import 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 */ 38 class 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