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 }