Coverage Summary for Class: CacheBuilderSpec (com.google.common.cache)
| Class | Method, % | Line, % |
|---|---|---|
| CacheBuilderSpec | 0% (0/12) | 0% (0/93) |
| CacheBuilderSpec$1 | 0% (0/1) | 0% (0/1) |
| CacheBuilderSpec$AccessDurationParser | 0% (0/2) | 0% (0/4) |
| CacheBuilderSpec$ConcurrencyLevelParser | 0% (0/2) | 0% (0/3) |
| CacheBuilderSpec$DurationParser | 0% (0/2) | 0% (0/20) |
| CacheBuilderSpec$InitialCapacityParser | 0% (0/2) | 0% (0/3) |
| CacheBuilderSpec$IntegerParser | 0% (0/2) | 0% (0/7) |
| CacheBuilderSpec$KeyStrengthParser | 0% (0/2) | 0% (0/5) |
| CacheBuilderSpec$LongParser | 0% (0/2) | 0% (0/7) |
| CacheBuilderSpec$MaximumSizeParser | 0% (0/2) | 0% (0/4) |
| CacheBuilderSpec$MaximumWeightParser | 0% (0/2) | 0% (0/4) |
| CacheBuilderSpec$RecordStatsParser | 0% (0/2) | 0% (0/4) |
| CacheBuilderSpec$RefreshDurationParser | 0% (0/2) | 0% (0/4) |
| CacheBuilderSpec$ValueStrengthParser | 0% (0/2) | 0% (0/5) |
| CacheBuilderSpec$WriteDurationParser | 0% (0/2) | 0% (0/4) |
| Total | 0% (0/39) | 0% (0/168) |
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 static com.google.common.base.Preconditions.checkArgument; 18 19 import com.google.common.annotations.GwtIncompatible; 20 import com.google.common.annotations.VisibleForTesting; 21 import com.google.common.base.MoreObjects; 22 import com.google.common.base.Objects; 23 import com.google.common.base.Splitter; 24 import com.google.common.cache.LocalCache.Strength; 25 import com.google.common.collect.ImmutableList; 26 import com.google.common.collect.ImmutableMap; 27 import java.util.List; 28 import java.util.Locale; 29 import java.util.concurrent.TimeUnit; 30 import org.checkerframework.checker.nullness.qual.Nullable; 31 32 /** 33 * A specification of a {@link CacheBuilder} configuration. 34 * 35 * <p>{@code CacheBuilderSpec} supports parsing configuration off of a string, which makes it 36 * especially useful for command-line configuration of a {@code CacheBuilder}. 37 * 38 * <p>The string syntax is a series of comma-separated keys or key-value pairs, each corresponding 39 * to a {@code CacheBuilder} method. 40 * 41 * <ul> 42 * <li>{@code concurrencyLevel=[integer]}: sets {@link CacheBuilder#concurrencyLevel}. 43 * <li>{@code initialCapacity=[integer]}: sets {@link CacheBuilder#initialCapacity}. 44 * <li>{@code maximumSize=[long]}: sets {@link CacheBuilder#maximumSize}. 45 * <li>{@code maximumWeight=[long]}: sets {@link CacheBuilder#maximumWeight}. 46 * <li>{@code expireAfterAccess=[duration]}: sets {@link CacheBuilder#expireAfterAccess}. 47 * <li>{@code expireAfterWrite=[duration]}: sets {@link CacheBuilder#expireAfterWrite}. 48 * <li>{@code refreshAfterWrite=[duration]}: sets {@link CacheBuilder#refreshAfterWrite}. 49 * <li>{@code weakKeys}: sets {@link CacheBuilder#weakKeys}. 50 * <li>{@code softValues}: sets {@link CacheBuilder#softValues}. 51 * <li>{@code weakValues}: sets {@link CacheBuilder#weakValues}. 52 * <li>{@code recordStats}: sets {@link CacheBuilder#recordStats}. 53 * </ul> 54 * 55 * <p>The set of supported keys will grow as {@code CacheBuilder} evolves, but existing keys will 56 * never be removed. 57 * 58 * <p>Durations are represented by an integer, followed by one of "d", "h", "m", or "s", 59 * representing days, hours, minutes, or seconds respectively. (There is currently no syntax to 60 * request expiration in milliseconds, microseconds, or nanoseconds.) 61 * 62 * <p>Whitespace before and after commas and equal signs is ignored. Keys may not be repeated; it is 63 * also illegal to use the following pairs of keys in a single value: 64 * 65 * <ul> 66 * <li>{@code maximumSize} and {@code maximumWeight} 67 * <li>{@code softValues} and {@code weakValues} 68 * </ul> 69 * 70 * <p>{@code CacheBuilderSpec} does not support configuring {@code CacheBuilder} methods with 71 * non-value parameters. These must be configured in code. 72 * 73 * <p>A new {@code CacheBuilder} can be instantiated from a {@code CacheBuilderSpec} using {@link 74 * CacheBuilder#from(CacheBuilderSpec)} or {@link CacheBuilder#from(String)}. 75 * 76 * @author Adam Winer 77 * @since 12.0 78 */ 79 @SuppressWarnings("GoodTime") // lots of violations (nanosecond math) 80 @GwtIncompatible 81 public final class CacheBuilderSpec { 82 /** Parses a single value. */ 83 private interface ValueParser { 84 void parse(CacheBuilderSpec spec, String key, @Nullable String value); 85 } 86 87 /** Splits each key-value pair. */ 88 private static final Splitter KEYS_SPLITTER = Splitter.on(',').trimResults(); 89 90 /** Splits the key from the value. */ 91 private static final Splitter KEY_VALUE_SPLITTER = Splitter.on('=').trimResults(); 92 93 /** Map of names to ValueParser. */ 94 private static final ImmutableMap<String, ValueParser> VALUE_PARSERS = 95 ImmutableMap.<String, ValueParser>builder() 96 .put("initialCapacity", new InitialCapacityParser()) 97 .put("maximumSize", new MaximumSizeParser()) 98 .put("maximumWeight", new MaximumWeightParser()) 99 .put("concurrencyLevel", new ConcurrencyLevelParser()) 100 .put("weakKeys", new KeyStrengthParser(Strength.WEAK)) 101 .put("softValues", new ValueStrengthParser(Strength.SOFT)) 102 .put("weakValues", new ValueStrengthParser(Strength.WEAK)) 103 .put("recordStats", new RecordStatsParser()) 104 .put("expireAfterAccess", new AccessDurationParser()) 105 .put("expireAfterWrite", new WriteDurationParser()) 106 .put("refreshAfterWrite", new RefreshDurationParser()) 107 .put("refreshInterval", new RefreshDurationParser()) 108 .build(); 109 110 @VisibleForTesting @Nullable Integer initialCapacity; 111 @VisibleForTesting @Nullable Long maximumSize; 112 @VisibleForTesting @Nullable Long maximumWeight; 113 @VisibleForTesting @Nullable Integer concurrencyLevel; 114 @VisibleForTesting @Nullable Strength keyStrength; 115 @VisibleForTesting @Nullable Strength valueStrength; 116 @VisibleForTesting @Nullable Boolean recordStats; 117 @VisibleForTesting long writeExpirationDuration; 118 @VisibleForTesting @Nullable TimeUnit writeExpirationTimeUnit; 119 @VisibleForTesting long accessExpirationDuration; 120 @VisibleForTesting @Nullable TimeUnit accessExpirationTimeUnit; 121 @VisibleForTesting long refreshDuration; 122 @VisibleForTesting @Nullable TimeUnit refreshTimeUnit; 123 /** Specification; used for toParseableString(). */ 124 private final String specification; 125 126 private CacheBuilderSpec(String specification) { 127 this.specification = specification; 128 } 129 130 /** 131 * Creates a CacheBuilderSpec from a string. 132 * 133 * @param cacheBuilderSpecification the string form 134 */ 135 public static CacheBuilderSpec parse(String cacheBuilderSpecification) { 136 CacheBuilderSpec spec = new CacheBuilderSpec(cacheBuilderSpecification); 137 if (!cacheBuilderSpecification.isEmpty()) { 138 for (String keyValuePair : KEYS_SPLITTER.split(cacheBuilderSpecification)) { 139 List<String> keyAndValue = ImmutableList.copyOf(KEY_VALUE_SPLITTER.split(keyValuePair)); 140 checkArgument(!keyAndValue.isEmpty(), "blank key-value pair"); 141 checkArgument( 142 keyAndValue.size() <= 2, 143 "key-value pair %s with more than one equals sign", 144 keyValuePair); 145 146 // Find the ValueParser for the current key. 147 String key = keyAndValue.get(0); 148 ValueParser valueParser = VALUE_PARSERS.get(key); 149 checkArgument(valueParser != null, "unknown key %s", key); 150 151 String value = keyAndValue.size() == 1 ? null : keyAndValue.get(1); 152 valueParser.parse(spec, key, value); 153 } 154 } 155 156 return spec; 157 } 158 159 /** Returns a CacheBuilderSpec that will prevent caching. */ 160 public static CacheBuilderSpec disableCaching() { 161 // Maximum size of zero is one way to block caching 162 return CacheBuilderSpec.parse("maximumSize=0"); 163 } 164 165 /** Returns a CacheBuilder configured according to this instance's specification. */ 166 CacheBuilder<Object, Object> toCacheBuilder() { 167 CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder(); 168 if (initialCapacity != null) { 169 builder.initialCapacity(initialCapacity); 170 } 171 if (maximumSize != null) { 172 builder.maximumSize(maximumSize); 173 } 174 if (maximumWeight != null) { 175 builder.maximumWeight(maximumWeight); 176 } 177 if (concurrencyLevel != null) { 178 builder.concurrencyLevel(concurrencyLevel); 179 } 180 if (keyStrength != null) { 181 switch (keyStrength) { 182 case WEAK: 183 builder.weakKeys(); 184 break; 185 default: 186 throw new AssertionError(); 187 } 188 } 189 if (valueStrength != null) { 190 switch (valueStrength) { 191 case SOFT: 192 builder.softValues(); 193 break; 194 case WEAK: 195 builder.weakValues(); 196 break; 197 default: 198 throw new AssertionError(); 199 } 200 } 201 if (recordStats != null && recordStats) { 202 builder.recordStats(); 203 } 204 if (writeExpirationTimeUnit != null) { 205 builder.expireAfterWrite(writeExpirationDuration, writeExpirationTimeUnit); 206 } 207 if (accessExpirationTimeUnit != null) { 208 builder.expireAfterAccess(accessExpirationDuration, accessExpirationTimeUnit); 209 } 210 if (refreshTimeUnit != null) { 211 builder.refreshAfterWrite(refreshDuration, refreshTimeUnit); 212 } 213 214 return builder; 215 } 216 217 /** 218 * Returns a string that can be used to parse an equivalent {@code CacheBuilderSpec}. The order 219 * and form of this representation is not guaranteed, except that reparsing its output will 220 * produce a {@code CacheBuilderSpec} equal to this instance. 221 */ 222 public String toParsableString() { 223 return specification; 224 } 225 226 /** 227 * Returns a string representation for this CacheBuilderSpec instance. The form of this 228 * representation is not guaranteed. 229 */ 230 @Override 231 public String toString() { 232 return MoreObjects.toStringHelper(this).addValue(toParsableString()).toString(); 233 } 234 235 @Override 236 public int hashCode() { 237 return Objects.hashCode( 238 initialCapacity, 239 maximumSize, 240 maximumWeight, 241 concurrencyLevel, 242 keyStrength, 243 valueStrength, 244 recordStats, 245 durationInNanos(writeExpirationDuration, writeExpirationTimeUnit), 246 durationInNanos(accessExpirationDuration, accessExpirationTimeUnit), 247 durationInNanos(refreshDuration, refreshTimeUnit)); 248 } 249 250 @Override 251 public boolean equals(@Nullable Object obj) { 252 if (this == obj) { 253 return true; 254 } 255 if (!(obj instanceof CacheBuilderSpec)) { 256 return false; 257 } 258 CacheBuilderSpec that = (CacheBuilderSpec) obj; 259 return Objects.equal(initialCapacity, that.initialCapacity) 260 && Objects.equal(maximumSize, that.maximumSize) 261 && Objects.equal(maximumWeight, that.maximumWeight) 262 && Objects.equal(concurrencyLevel, that.concurrencyLevel) 263 && Objects.equal(keyStrength, that.keyStrength) 264 && Objects.equal(valueStrength, that.valueStrength) 265 && Objects.equal(recordStats, that.recordStats) 266 && Objects.equal( 267 durationInNanos(writeExpirationDuration, writeExpirationTimeUnit), 268 durationInNanos(that.writeExpirationDuration, that.writeExpirationTimeUnit)) 269 && Objects.equal( 270 durationInNanos(accessExpirationDuration, accessExpirationTimeUnit), 271 durationInNanos(that.accessExpirationDuration, that.accessExpirationTimeUnit)) 272 && Objects.equal( 273 durationInNanos(refreshDuration, refreshTimeUnit), 274 durationInNanos(that.refreshDuration, that.refreshTimeUnit)); 275 } 276 277 /** 278 * Converts an expiration duration/unit pair into a single Long for hashing and equality. Uses 279 * nanos to match CacheBuilder implementation. 280 */ 281 private static @Nullable Long durationInNanos(long duration, @Nullable TimeUnit unit) { 282 return (unit == null) ? null : unit.toNanos(duration); 283 } 284 285 /** Base class for parsing integers. */ 286 abstract static class IntegerParser implements ValueParser { 287 protected abstract void parseInteger(CacheBuilderSpec spec, int value); 288 289 @Override 290 public void parse(CacheBuilderSpec spec, String key, String value) { 291 checkArgument(value != null && !value.isEmpty(), "value of key %s omitted", key); 292 try { 293 parseInteger(spec, Integer.parseInt(value)); 294 } catch (NumberFormatException e) { 295 throw new IllegalArgumentException( 296 format("key %s value set to %s, must be integer", key, value), e); 297 } 298 } 299 } 300 301 /** Base class for parsing integers. */ 302 abstract static class LongParser implements ValueParser { 303 protected abstract void parseLong(CacheBuilderSpec spec, long value); 304 305 @Override 306 public void parse(CacheBuilderSpec spec, String key, String value) { 307 checkArgument(value != null && !value.isEmpty(), "value of key %s omitted", key); 308 try { 309 parseLong(spec, Long.parseLong(value)); 310 } catch (NumberFormatException e) { 311 throw new IllegalArgumentException( 312 format("key %s value set to %s, must be integer", key, value), e); 313 } 314 } 315 } 316 317 /** Parse initialCapacity */ 318 static class InitialCapacityParser extends IntegerParser { 319 @Override 320 protected void parseInteger(CacheBuilderSpec spec, int value) { 321 checkArgument( 322 spec.initialCapacity == null, 323 "initial capacity was already set to ", 324 spec.initialCapacity); 325 spec.initialCapacity = value; 326 } 327 } 328 329 /** Parse maximumSize */ 330 static class MaximumSizeParser extends LongParser { 331 @Override 332 protected void parseLong(CacheBuilderSpec spec, long value) { 333 checkArgument(spec.maximumSize == null, "maximum size was already set to ", spec.maximumSize); 334 checkArgument( 335 spec.maximumWeight == null, "maximum weight was already set to ", spec.maximumWeight); 336 spec.maximumSize = value; 337 } 338 } 339 340 /** Parse maximumWeight */ 341 static class MaximumWeightParser extends LongParser { 342 @Override 343 protected void parseLong(CacheBuilderSpec spec, long value) { 344 checkArgument( 345 spec.maximumWeight == null, "maximum weight was already set to ", spec.maximumWeight); 346 checkArgument(spec.maximumSize == null, "maximum size was already set to ", spec.maximumSize); 347 spec.maximumWeight = value; 348 } 349 } 350 351 /** Parse concurrencyLevel */ 352 static class ConcurrencyLevelParser extends IntegerParser { 353 @Override 354 protected void parseInteger(CacheBuilderSpec spec, int value) { 355 checkArgument( 356 spec.concurrencyLevel == null, 357 "concurrency level was already set to ", 358 spec.concurrencyLevel); 359 spec.concurrencyLevel = value; 360 } 361 } 362 363 /** Parse weakKeys */ 364 static class KeyStrengthParser implements ValueParser { 365 private final Strength strength; 366 367 public KeyStrengthParser(Strength strength) { 368 this.strength = strength; 369 } 370 371 @Override 372 public void parse(CacheBuilderSpec spec, String key, @Nullable String value) { 373 checkArgument(value == null, "key %s does not take values", key); 374 checkArgument(spec.keyStrength == null, "%s was already set to %s", key, spec.keyStrength); 375 spec.keyStrength = strength; 376 } 377 } 378 379 /** Parse weakValues and softValues */ 380 static class ValueStrengthParser implements ValueParser { 381 private final Strength strength; 382 383 public ValueStrengthParser(Strength strength) { 384 this.strength = strength; 385 } 386 387 @Override 388 public void parse(CacheBuilderSpec spec, String key, @Nullable String value) { 389 checkArgument(value == null, "key %s does not take values", key); 390 checkArgument( 391 spec.valueStrength == null, "%s was already set to %s", key, spec.valueStrength); 392 393 spec.valueStrength = strength; 394 } 395 } 396 397 /** Parse recordStats */ 398 static class RecordStatsParser implements ValueParser { 399 400 @Override 401 public void parse(CacheBuilderSpec spec, String key, @Nullable String value) { 402 checkArgument(value == null, "recordStats does not take values"); 403 checkArgument(spec.recordStats == null, "recordStats already set"); 404 spec.recordStats = true; 405 } 406 } 407 408 /** Base class for parsing times with durations */ 409 abstract static class DurationParser implements ValueParser { 410 protected abstract void parseDuration(CacheBuilderSpec spec, long duration, TimeUnit unit); 411 412 @Override 413 public void parse(CacheBuilderSpec spec, String key, String value) { 414 checkArgument(value != null && !value.isEmpty(), "value of key %s omitted", key); 415 try { 416 char lastChar = value.charAt(value.length() - 1); 417 TimeUnit timeUnit; 418 switch (lastChar) { 419 case 'd': 420 timeUnit = TimeUnit.DAYS; 421 break; 422 case 'h': 423 timeUnit = TimeUnit.HOURS; 424 break; 425 case 'm': 426 timeUnit = TimeUnit.MINUTES; 427 break; 428 case 's': 429 timeUnit = TimeUnit.SECONDS; 430 break; 431 default: 432 throw new IllegalArgumentException( 433 format("key %s invalid unit: was %s, must end with one of [dhms]", key, value)); 434 } 435 436 long duration = Long.parseLong(value.substring(0, value.length() - 1)); 437 parseDuration(spec, duration, timeUnit); 438 } catch (NumberFormatException e) { 439 throw new IllegalArgumentException( 440 format("key %s value set to %s, must be integer", key, value)); 441 } 442 } 443 } 444 445 /** Parse expireAfterAccess */ 446 static class AccessDurationParser extends DurationParser { 447 @Override 448 protected void parseDuration(CacheBuilderSpec spec, long duration, TimeUnit unit) { 449 checkArgument(spec.accessExpirationTimeUnit == null, "expireAfterAccess already set"); 450 spec.accessExpirationDuration = duration; 451 spec.accessExpirationTimeUnit = unit; 452 } 453 } 454 455 /** Parse expireAfterWrite */ 456 static class WriteDurationParser extends DurationParser { 457 @Override 458 protected void parseDuration(CacheBuilderSpec spec, long duration, TimeUnit unit) { 459 checkArgument(spec.writeExpirationTimeUnit == null, "expireAfterWrite already set"); 460 spec.writeExpirationDuration = duration; 461 spec.writeExpirationTimeUnit = unit; 462 } 463 } 464 465 /** Parse refreshAfterWrite */ 466 static class RefreshDurationParser extends DurationParser { 467 @Override 468 protected void parseDuration(CacheBuilderSpec spec, long duration, TimeUnit unit) { 469 checkArgument(spec.refreshTimeUnit == null, "refreshAfterWrite already set"); 470 spec.refreshDuration = duration; 471 spec.refreshTimeUnit = unit; 472 } 473 } 474 475 private static String format(String format, Object... args) { 476 return String.format(Locale.ROOT, format, args); 477 } 478 }