ConfigUtil: Extract parseDuration from getDuration

Change-Id: I06118572ae0670e1d3b7e5229d0c52b8a0b0d0f0
diff --git a/gitiles-servlet/src/main/java/com/google/gitiles/ConfigUtil.java b/gitiles-servlet/src/main/java/com/google/gitiles/ConfigUtil.java
index e4dc44d..e167f94 100644
--- a/gitiles-servlet/src/main/java/com/google/gitiles/ConfigUtil.java
+++ b/gitiles-servlet/src/main/java/com/google/gitiles/ConfigUtil.java
@@ -33,7 +33,7 @@
   /**
    * Read a duration value from the configuration.
    * <p>
-   * Durations can be written as expressions, for example {@code "1 s"} or
+   * Durations can be written with unit suffixes, for example {@code "1 s"} or
    * {@code "5 days"}. If units are not specified, milliseconds are assumed.
    *
    * @param config JGit config object.
@@ -49,17 +49,40 @@
     if (valStr == null) {
         return defaultValue;
     }
-
     valStr = valStr.trim();
-    if (valStr.length() == 0) {
+    if (valStr.isEmpty()) {
         return defaultValue;
     }
-
-    Matcher m = matcher("^([1-9][0-9]*(?:\\.[0-9]*)?)\\s*(.*)$", valStr);
-    if (!m.matches()) {
+    Duration val = parseDuration(valStr);
+    if (val == null) {
       String key = section + (subsection != null ? "." + subsection : "") + "." + name;
       throw new IllegalStateException("Not time unit: " + key + " = " + valStr);
     }
+    return val;
+  }
+
+  /**
+   * Parse a duration value from a string.
+   * <p>
+   * Durations can be written with unit suffixes, for example {@code "1 s"} or
+   * {@code "5 days"}. If units are not specified, milliseconds are assumed.
+   *
+   * @param valStr the value to parse.
+   * @return a standard duration representing the time parsed, or null if not a
+   *     valid duration.
+   */
+  public static Duration parseDuration(String valStr) {
+    if (valStr == null) {
+      return null;
+    }
+    valStr = valStr.trim();
+    if (valStr.isEmpty()) {
+      return null;
+    }
+    Matcher m = matcher("^([1-9][0-9]*(?:\\.[0-9]*)?)\\s*(.*)$", valStr);
+    if (!m.matches()) {
+      return null;
+    }
 
     String digits = m.group(1);
     String unitName = m.group(2).trim();
@@ -78,8 +101,7 @@
     } else if (anyOf(unitName, "d", "day", "days")) {
       unit = TimeUnit.DAYS;
     } else {
-      String key = section + (subsection != null ? "." + subsection : "") + "." + name;
-      throw new IllegalStateException("Not time unit: " + key + " = " + valStr);
+      return null;
     }
 
     try {
@@ -91,8 +113,7 @@
         return new Duration((long) (val * TimeUnit.MILLISECONDS.convert(1, unit)));
       }
     } catch (NumberFormatException nfe) {
-      String key = section + (subsection != null ? "." + subsection : "") + "." + name;
-      throw new IllegalStateException("Not time unit: " + key + " = " + valStr, nfe);
+      return null;
     }
   }
 
diff --git a/gitiles-servlet/src/test/java/com/google/gitiles/ConfigUtilTest.java b/gitiles-servlet/src/test/java/com/google/gitiles/ConfigUtilTest.java
index a22cc72..e28b9fa 100644
--- a/gitiles-servlet/src/test/java/com/google/gitiles/ConfigUtilTest.java
+++ b/gitiles-servlet/src/test/java/com/google/gitiles/ConfigUtilTest.java
@@ -14,8 +14,10 @@
 
 package com.google.gitiles;
 
+import static com.google.common.base.Preconditions.checkNotNull;
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.gitiles.ConfigUtil.getDuration;
+import static com.google.gitiles.ConfigUtil.parseDuration;
 
 import org.eclipse.jgit.lib.Config;
 import org.joda.time.Duration;
@@ -44,4 +46,26 @@
     t = getDuration(config, "core", "dht", "timeout", def);
     assertThat(t.getMillis()).isEqualTo(60000);
   }
+
+  @Test
+  public void parseDurationReturnsDuration() throws Exception {
+    assertDoesNotParse(null);
+    assertDoesNotParse("");
+    assertDoesNotParse(" ");
+    assertParses(500, "500 ms");
+    assertParses(500, "500ms");
+    assertParses(500, " 500 ms ");
+    assertParses(5200, "5.2 sec");
+    assertParses(60000, "1 min");
+  }
+
+  private static void assertDoesNotParse(String val) {
+    assertThat(parseDuration(val)).named(String.valueOf(val)).isNull();
+  }
+
+  private static void assertParses(long expectedMillis, String val) {
+    Duration actual = parseDuration(checkNotNull(val));
+    assertThat(actual).named(val).isNotNull();
+    assertThat(actual.getMillis()).named(val).isEqualTo(expectedMillis);
+  }
 }