From 86186a67a29efd4a97ba32284f2dd6f89cefa166 Mon Sep 17 00:00:00 2001
From: Peter Powers <pmpowers@usgs.gov>
Date: Tue, 18 Jun 2024 10:41:43 -0600
Subject: [PATCH] removed text methods; delimiter use refactoring

---
 .../java/gov/usgs/earthquake/nshmp/Text.java  |  69 ++---------
 .../earthquake/nshmp/calc/EqRateExport.java   |   9 +-
 .../earthquake/nshmp/calc/HazardExport.java   |   4 +-
 .../nshmp/fault/surface/RuptureScaling.java   |   7 +-
 .../nshmp/gmm/CoefficientContainer.java       |  85 +++++++------
 .../nshmp/gmm/GroundMotionTables.java         |  20 ++--
 .../usgs/earthquake/nshmp/gmm/NgaEast.java    |   5 +-
 .../gov/usgs/earthquake/nshmp/TextTests.java  | 113 ++++++++----------
 .../nshmp/gmm/CoefficientContainerTest.java   |  14 +--
 .../usgs/earthquake/nshmp/gmm/GmmTest.java    |  25 ++--
 10 files changed, 139 insertions(+), 212 deletions(-)

diff --git a/src/main/java/gov/usgs/earthquake/nshmp/Text.java b/src/main/java/gov/usgs/earthquake/nshmp/Text.java
index d429fd10..774107e5 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/Text.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/Text.java
@@ -1,6 +1,10 @@
 package gov.usgs.earthquake.nshmp;
 
 import static com.google.common.base.Preconditions.checkArgument;
+import static gov.usgs.earthquake.nshmp.Text.Delimiter.COMMA;
+import static gov.usgs.earthquake.nshmp.Text.Delimiter.DASH;
+import static gov.usgs.earthquake.nshmp.Text.Delimiter.SPACE;
+import static gov.usgs.earthquake.nshmp.Text.Delimiter.UNDERSCORE;
 
 import java.util.AbstractCollection;
 import java.util.Arrays;
@@ -17,7 +21,6 @@ import com.google.common.base.Splitter;
 import com.google.common.base.StandardSystemProperty;
 import com.google.common.base.Strings;
 import com.google.common.collect.FluentIterable;
-import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
 import com.google.common.primitives.Doubles;
 
@@ -66,41 +69,6 @@ public class Text {
     return name;
   }
 
-  /**
-   * Returns a string containing the string representation of each of
-   * {@code parts} joined with {@code delimiter}.
-   *
-   * @param parts the objects to join
-   * @param delimiter the {@link Delimiter} to join on
-   * @see Joiner
-   */
-  public static String join(Iterable<?> parts, Delimiter delimiter) {
-    return delimiter.joiner().join(parts);
-  }
-
-  /**
-   * Split a {@code sequence} into string components and make them available
-   * through a (possibly-lazy) {@code Iterator}.
-   *
-   * @param sequence the sequence of characters to split
-   * @param delimiter the {@link Delimiter} to split on
-   * @see Splitter
-   */
-  public static Iterable<String> split(CharSequence sequence, Delimiter delimiter) {
-    return delimiter.splitter().split(sequence);
-  }
-
-  /**
-   * Split a {@code sequence} into string components and make them available
-   * through an immutable {@code List}.
-   *
-   * @param sequence the sequence of characters to split
-   * @param delimiter the {@link Delimiter} to split on
-   */
-  public static List<String> splitToList(CharSequence sequence, Delimiter delimiter) {
-    return delimiter.splitter().splitToList(sequence);
-  }
-
   /**
    * Split {@code sequence} into {@code Double} components and make them
    * available through an immutable {@code List}.
@@ -110,14 +78,15 @@ public class Text {
    */
   public static List<Double> splitToDoubleList(CharSequence sequence, Delimiter delimiter) {
     return FluentIterable
-        .from(split(sequence, delimiter))
+        .from(delimiter.splitter().split(sequence))
         .transform(Doubles.stringConverter())
         .toList();
   }
 
   /**
    * Delimiter identifiers, each of which can provide a {@link Joiner} and
-   * {@link Splitter}.
+   * {@link Splitter}. A delimiter's 'joiner' skips nulls; a delimiter's
+   * 'splitter' skips empty strings and trims each element.
    */
   public enum Delimiter {
 
@@ -190,7 +159,7 @@ public class Text {
     return addBrackets(FluentIterable
         .from(iterable)
         .transform(Enums.stringConverter(enumClass).reverse())
-        .join(Delimiter.COMMA.joiner()));
+        .join(COMMA.joiner()));
   }
 
   /**
@@ -202,7 +171,7 @@ public class Text {
    *        false if letters should all be lowercase
    */
   public static String enumLabelWithSpaces(Enum<? extends Enum<?>> e, boolean capitalize) {
-    return join(splitEnum(e, capitalize), Delimiter.SPACE);
+    return SPACE.joiner().join(splitEnum(e, capitalize));
   }
 
   /**
@@ -214,12 +183,12 @@ public class Text {
    *        false if letters should all be lowercase
    */
   public static String enumLabelWithDashes(Enum<? extends Enum<?>> e, boolean capitalize) {
-    return join(splitEnum(e, capitalize), Delimiter.DASH);
+    return DASH.joiner().join(splitEnum(e, capitalize));
   }
 
   /* Splits and possibly capitalizes an enum.name() */
   private static List<String> splitEnum(Enum<? extends Enum<?>> e, boolean capitalize) {
-    Iterable<String> sources = split(e.name(), Delimiter.UNDERSCORE);
+    Iterable<String> sources = UNDERSCORE.splitter().split(e.name());
     List<String> results = Lists.newArrayList();
     for (String s : sources) {
       results.add(capitalize ? capitalize(s) : s.toLowerCase());
@@ -259,10 +228,6 @@ public class Text {
     String base = values.stream()
         .map(Text.formatDoubleFunction(format))
         .collect(Collectors.joining(delimiter));
-    // String base = Joiner.on(delimiter).join(
-    // Iterables.transform(
-    // values,
-    // Text.formatDoubleFunction(format)));
     return brackets ? addBrackets(base) : base;
     // TODO replace with prefix suffix
   }
@@ -319,16 +284,4 @@ public class Text {
     return d -> (d == 0.0) ? "0.0" : String.format(format, d);
   }
 
-  /**
-   * Read and return the {@code double} at {@code position} in a space-delimited
-   * string.
-   *
-   * @param s the string to read from
-   * @param position the position to read
-   */
-  @Deprecated
-  public static double readDouble(String s, int position) {
-    return Double.valueOf(Iterables.get(split(s, Delimiter.SPACE), position));
-  }
-
 }
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/calc/EqRateExport.java b/src/main/java/gov/usgs/earthquake/nshmp/calc/EqRateExport.java
index 08886959..4ee830ad 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/calc/EqRateExport.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/calc/EqRateExport.java
@@ -1,5 +1,6 @@
 package gov.usgs.earthquake.nshmp.calc;
 
+import static gov.usgs.earthquake.nshmp.Text.Delimiter.COMMA;
 import static java.nio.file.StandardOpenOption.APPEND;
 
 import java.io.IOException;
@@ -16,7 +17,6 @@ import com.google.common.collect.Lists;
 import com.google.common.primitives.Doubles;
 
 import gov.usgs.earthquake.nshmp.Text;
-import gov.usgs.earthquake.nshmp.Text.Delimiter;
 import gov.usgs.earthquake.nshmp.data.XySequence;
 import gov.usgs.earthquake.nshmp.geo.Location;
 import gov.usgs.earthquake.nshmp.model.HazardModel;
@@ -129,7 +129,7 @@ public final class EqRateExport {
     Iterable<?> headerElements = Iterables.concat(
         Lists.newArrayList(namedSites ? "name" : null, "lon", "lat"),
         demo.totalMfd.xValues().boxed().collect(Collectors.toList()));
-    String header = Text.join(headerElements, Delimiter.COMMA);
+    String header = COMMA.joiner().join(headerElements);
     Path totalFile = out.resolve(file);
     HazardExport.writeLine(totalFile, header);
 
@@ -150,12 +150,11 @@ public final class EqRateExport {
       Iterable<Double> values,
       Function<Double, String> formatter) {
 
-    return Text.join(
+    return COMMA.joiner().join(
         FluentIterable.from(location)
             .append(Iterables.transform(
                 values,
-                formatter::apply)),
-        Delimiter.COMMA);
+                formatter::apply)));
   }
 
   private static String toLine(
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/calc/HazardExport.java b/src/main/java/gov/usgs/earthquake/nshmp/calc/HazardExport.java
index 34123af4..2ede8645 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/calc/HazardExport.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/calc/HazardExport.java
@@ -1,5 +1,6 @@
 package gov.usgs.earthquake.nshmp.calc;
 
+import static gov.usgs.earthquake.nshmp.Text.Delimiter.COMMA;
 import static gov.usgs.earthquake.nshmp.calc.ValueFormat.POISSON_PROBABILITY;
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static java.nio.file.StandardOpenOption.APPEND;
@@ -28,7 +29,6 @@ import com.google.common.collect.Maps;
 
 import gov.usgs.earthquake.nshmp.Maths;
 import gov.usgs.earthquake.nshmp.Text;
-import gov.usgs.earthquake.nshmp.Text.Delimiter;
 import gov.usgs.earthquake.nshmp.data.MutableXySequence;
 import gov.usgs.earthquake.nshmp.data.XySequence;
 import gov.usgs.earthquake.nshmp.geo.Location;
@@ -119,7 +119,7 @@ public final class HazardExport {
       XySequence modelCurve = config.hazard.modelCurves().get(imt);
       headerValues.addAll(modelCurve.xValues().boxed().collect(toList()));
 
-      String header = Text.join(headerValues, Delimiter.COMMA);
+      String header = COMMA.joiner().join(headerValues);
       writeLine(totalFile, header);
       writeLine(totalFileTruncated, header);
 
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/fault/surface/RuptureScaling.java b/src/main/java/gov/usgs/earthquake/nshmp/fault/surface/RuptureScaling.java
index 2ddcd26d..0be210c6 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/fault/surface/RuptureScaling.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/fault/surface/RuptureScaling.java
@@ -2,6 +2,7 @@ package gov.usgs.earthquake.nshmp.fault.surface;
 
 import static com.google.common.io.Resources.getResource;
 import static com.google.common.io.Resources.readLines;
+import static gov.usgs.earthquake.nshmp.Text.Delimiter.SPACE;
 import static java.lang.Math.floor;
 import static java.lang.Math.log10;
 import static java.lang.Math.max;
@@ -13,12 +14,12 @@ import static java.nio.charset.StandardCharsets.UTF_8;
 
 import java.io.IOException;
 import java.net.URL;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 
-import gov.usgs.earthquake.nshmp.Text;
 import gov.usgs.earthquake.nshmp.data.DoubleData;
 import gov.usgs.earthquake.nshmp.data.Interpolator;
 import gov.usgs.earthquake.nshmp.data.Sequences;
@@ -311,7 +312,7 @@ public enum RuptureScaling {
   static double[][] readRjb(String resource) {
     double[][] rjbs = new double[RJB_M_SIZE][RJB_R_SIZE];
     URL url = getResource(RJB_DAT_DIR + resource);
-    List<String> lines = null;
+    List<String> lines = new ArrayList<>();
     try {
       lines = readLines(url, UTF_8);
     } catch (IOException ioe) {
@@ -331,7 +332,7 @@ public enum RuptureScaling {
       if (line.startsWith(COMMENT_ID)) {
         continue;
       }
-      rjbs[magIndex][rIndex++] = Text.readDouble(line, 1);
+      rjbs[magIndex][rIndex++] = Double.parseDouble(SPACE.splitter().splitToList(line).get(1));
     }
     return rjbs;
   }
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/gmm/CoefficientContainer.java b/src/main/java/gov/usgs/earthquake/nshmp/gmm/CoefficientContainer.java
index c9bc9bee..f4fd3691 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/gmm/CoefficientContainer.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/gmm/CoefficientContainer.java
@@ -1,27 +1,22 @@
 package gov.usgs.earthquake.nshmp.gmm;
 
 import static com.google.common.base.Preconditions.checkNotNull;
+import static gov.usgs.earthquake.nshmp.Text.Delimiter.COMMA;
 import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.util.stream.Collectors.toList;
+import static java.util.stream.Collectors.toMap;
 
 import java.io.IOException;
 import java.net.URL;
+import java.util.EnumSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.function.Predicate;
 
 import com.google.common.collect.ArrayTable;
-import com.google.common.collect.FluentIterable;
 import com.google.common.collect.ImmutableTable;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
 import com.google.common.collect.Table;
 import com.google.common.io.Resources;
-import com.google.common.primitives.Doubles;
-
-import gov.usgs.earthquake.nshmp.Text;
-import gov.usgs.earthquake.nshmp.Text.Delimiter;
 
 /**
  * Class loads and manages {@code GroundMotionModel} coefficients.
@@ -75,43 +70,37 @@ final class CoefficientContainer {
 
   /**
    * Returns the {@code Set} of intensity measure types (IMTs) for which
-   * coefficients are supplied.
+   * coefficients are supplied. The returned Set is a always a new mutable
+   * EnumSet.
+   *
    * @return the {@code Set} of supported IMTs
    */
   Set<Imt> imts() {
-    return Sets.immutableEnumSet(table.rowKeySet());
+    return EnumSet.copyOf(table.rowKeySet());
   }
 
   private static Table<Imt, String, Double> load(String resource) throws IOException {
     URL url = Resources.getResource(C_DIR + resource);
     List<String> lines = Resources.readLines(url, UTF_8);
-    // build coeff name list
-    Iterable<String> names = FluentIterable
-        .from(Text.split(lines.get(0), Delimiter.COMMA))
-        .skip(1);
-    // build Imt-value map
-    Map<Imt, Double[]> valueMap = Maps.newHashMap();
 
-    Iterable<String> imtLines = FluentIterable.from(lines)
+    // build coeff name list from first line
+    List<String> names = COMMA.splitter().splitToStream(lines.get(0))
         .skip(1)
-        .filter(new Predicate<String>() {
-          @Override
-          public boolean test(String s) {
-            return !s.startsWith("#");
-          }
-        }::test);
-    for (String line : imtLines) {
-      Iterable<String> entries = Text.split(line, Delimiter.COMMA);
-      String imtStr = Iterables.get(entries, 0);
-      Imt imt = parseImt(imtStr);
-      Iterable<String> valStrs = Iterables.skip(entries, 1);
-      Iterable<Double> values = Iterables.transform(valStrs, Doubles.stringConverter());
-      valueMap.put(imt, Iterables.toArray(values, Double.class));
-    }
+        .collect(toList());
+
+    // build Imt value arrays from remaining lines
+    Map<Imt, double[]> valueMap = lines.stream()
+        .skip(1)
+        .filter(line -> !line.startsWith("#"))
+        .map(COMMA.splitter()::splitToList)
+        .collect(toMap(
+            elements -> parseImt(elements),
+            elements -> parseValues(elements)));
+
     // create and load table
     Table<Imt, String, Double> table = ArrayTable.create(valueMap.keySet(), names);
     for (Imt imt : valueMap.keySet()) {
-      Double[] values = valueMap.get(imt);
+      double[] values = valueMap.get(imt);
       int i = 0;
       for (String name : names) {
         table.put(imt, name, values[i++]);
@@ -121,19 +110,27 @@ final class CoefficientContainer {
   }
 
   /*
-   * Parses IMT strings from coefficient files. Method expects Imt.name() for
-   * specifically named intensity measure types, e.g. "PGA", and double value
-   * strings for spectral periods, e.g. "0.2". This method is NOT the same as
-   * {@link Imt#valueOf(String)}. Method will throw a NumberFormatException or
-   * IllegalArgumentException if the supplied string is not parseable into a
-   * known IMT.
+   * Parses IMT strings from from a line in a coefficient file. Method expects
+   * Imt.name() for specifically named intensity measure types, e.g. "PGA", and
+   * double value strings for spectral periods, e.g. "0.2". This method is NOT
+   * the same as {@link Imt#valueOf(String)}. Method will throw a
+   * NumberFormatException or IllegalArgumentException if the supplied string is
+   * not parseable into a known IMT.
    */
-  static Imt parseImt(String s) {
-    s = s.trim().toUpperCase();
-    if (s.equals("PGA") || s.equals("PGV") || s.equals("PGD")) {
-      return Imt.valueOf(s);
+  private static Imt parseImt(List<String> elements) {
+    String imtStr = elements.get(0);
+    if (imtStr.equals("PGA") || imtStr.equals("PGV") || imtStr.equals("PGD")) {
+      return Imt.valueOf(imtStr);
     }
-    double period = Double.parseDouble(s);
+    double period = Double.parseDouble(imtStr);
     return Imt.fromPeriod(period);
   }
+
+  /* Parses coefficient values from a line in a coefficient file. */
+  private static double[] parseValues(List<String> elements) {
+    return elements.stream()
+        .skip(1)
+        .mapToDouble(Double::parseDouble)
+        .toArray();
+  }
 }
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/gmm/GroundMotionTables.java b/src/main/java/gov/usgs/earthquake/nshmp/gmm/GroundMotionTables.java
index 8ab7f3f4..cbbacd80 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/gmm/GroundMotionTables.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/gmm/GroundMotionTables.java
@@ -4,7 +4,8 @@ import static com.google.common.io.Resources.getResource;
 import static com.google.common.io.Resources.readLines;
 import static gov.usgs.earthquake.nshmp.Text.NEWLINE;
 import static gov.usgs.earthquake.nshmp.Text.splitToDoubleList;
-import static gov.usgs.earthquake.nshmp.Text.splitToList;
+import static gov.usgs.earthquake.nshmp.Text.Delimiter.COMMA;
+import static gov.usgs.earthquake.nshmp.Text.Delimiter.SPACE;
 import static gov.usgs.earthquake.nshmp.gmm.Imt.PGA;
 import static gov.usgs.earthquake.nshmp.gmm.Imt.PGV;
 import static gov.usgs.earthquake.nshmp.gmm.Imt.SA0P03;
@@ -37,7 +38,6 @@ import com.google.common.io.LineProcessor;
 import com.google.common.primitives.Doubles;
 
 import gov.usgs.earthquake.nshmp.Text;
-import gov.usgs.earthquake.nshmp.Text.Delimiter;
 import gov.usgs.earthquake.nshmp.data.DoubleData;
 import gov.usgs.earthquake.nshmp.gmm.GmmUtils.CeusSiteClass;
 import gov.usgs.earthquake.nshmp.gmm.GroundMotionTables.GroundMotionTable.Position;
@@ -294,7 +294,7 @@ final class GroundMotionTables {
     try {
       List<String> lines = readLines(url, UTF_8);
       List<Imt> imts = FluentIterable
-          .from(splitToList(lines.get(0), Delimiter.COMMA))
+          .from(COMMA.splitter().splitToList(lines.get(0)))
           .skip(1)
           .transform(Enums.stringConverter(Imt.class))
           .toList();
@@ -302,7 +302,7 @@ final class GroundMotionTables {
         map.put(imt, new double[NGA_EAST_MODEL_COUNT]);
       }
       for (int i = 0; i < NGA_EAST_MODEL_COUNT; i++) {
-        List<Double> weights = splitToDoubleList(lines.get(i + 1), Delimiter.COMMA);
+        List<Double> weights = splitToDoubleList(lines.get(i + 1), COMMA);
         for (int j = 1; j < weights.size(); j++) {
           map.get(imts.get(j - 1))[i] = weights.get(j);
         }
@@ -603,7 +603,7 @@ final class GroundMotionTables {
         firstLine = false;
         return true;
       }
-      List<Double> values = splitToDoubleList(line, Delimiter.SPACE);
+      List<Double> values = splitToDoubleList(line, SPACE);
       data.add(values.subList(1, values.size()));
       return true;
     }
@@ -660,7 +660,7 @@ final class GroundMotionTables {
         imtHeaders = imtHeaders.substring(imtHeaders.indexOf("FS") + 3);
 
         List<Double> imtVals =
-            splitToDoubleList(imtHeaders, Delimiter.COMMA);
+            splitToDoubleList(imtHeaders, COMMA);
         for (Double t : imtVals) {
           Imt imt = (t == -1.) ? Imt.PGV : (t == 0.) ? Imt.PGA : Imt.fromPeriod(t);
           imtList.add(imt);
@@ -670,7 +670,7 @@ final class GroundMotionTables {
       }
 
       /* parse data */
-      List<Double> values = splitToDoubleList(line, Delimiter.COMMA);
+      List<Double> values = splitToDoubleList(line, COMMA);
       Double r = values.get(rIdx);
       Double m = values.get(mIdx);
       List<Double> sigmas = values.subList(dataIdx, values.size());
@@ -725,7 +725,7 @@ final class GroundMotionTables {
         return true;
       }
 
-      List<Double> values = splitToDoubleList(line, Delimiter.COMMA);
+      List<Double> values = splitToDoubleList(line, COMMA);
       List<Double> lnValues = DoubleData.ln(new ArrayList<>(values.subList(1, values.size())));
       dataLists.add(lnValues);
 
@@ -769,7 +769,7 @@ final class GroundMotionTables {
 
       if (lineIndex == 2) {
         List<Imt> imtList = FluentIterable
-            .from(Text.split(line, Delimiter.SPACE))
+            .from(SPACE.splitter().split(line))
             .transform(Doubles.stringConverter())
             .transform(new FrequencyToIMT()::apply)
             .toList();
@@ -786,7 +786,7 @@ final class GroundMotionTables {
         return true;
       }
 
-      List<Double> values = Text.splitToDoubleList(line, Delimiter.SPACE);
+      List<Double> values = Text.splitToDoubleList(line, SPACE);
 
       if (values.size() == 1) {
         // reset rIndex for every single mag line encountered
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/gmm/NgaEast.java b/src/main/java/gov/usgs/earthquake/nshmp/gmm/NgaEast.java
index 0873f55f..53b778dc 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/gmm/NgaEast.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/gmm/NgaEast.java
@@ -2,6 +2,7 @@ package gov.usgs.earthquake.nshmp.gmm;
 
 import static com.google.common.io.Resources.getResource;
 import static com.google.common.io.Resources.readLines;
+import static gov.usgs.earthquake.nshmp.Text.Delimiter.COMMA;
 import static gov.usgs.earthquake.nshmp.data.DoubleData.checkWeights;
 import static gov.usgs.earthquake.nshmp.gmm.GmmInput.Field.MW;
 import static gov.usgs.earthquake.nshmp.gmm.GmmInput.Field.RJB;
@@ -30,8 +31,6 @@ import java.util.stream.IntStream;
 import com.google.common.collect.Range;
 
 import gov.usgs.earthquake.nshmp.Maths;
-import gov.usgs.earthquake.nshmp.Text;
-import gov.usgs.earthquake.nshmp.Text.Delimiter;
 import gov.usgs.earthquake.nshmp.data.Interpolator;
 import gov.usgs.earthquake.nshmp.gmm.GmmInput.Constraints;
 import gov.usgs.earthquake.nshmp.gmm.GroundMotionTables.GroundMotionTable;
@@ -196,7 +195,7 @@ public abstract class NgaEast implements GroundMotionModel {
       Map<String, Double> wtMap = readLines(wtsUrl, StandardCharsets.UTF_8)
           .stream()
           .skip(1)
-          .map(line -> Text.splitToList(line, Delimiter.COMMA))
+          .map(line -> COMMA.splitter().splitToList(line))
           .collect(toUnmodifiableMap(
               e -> e.get(0),
               e -> Double.valueOf(e.get(1))));
diff --git a/src/test/java/gov/usgs/earthquake/nshmp/TextTests.java b/src/test/java/gov/usgs/earthquake/nshmp/TextTests.java
index 42999cf5..c7ffb449 100644
--- a/src/test/java/gov/usgs/earthquake/nshmp/TextTests.java
+++ b/src/test/java/gov/usgs/earthquake/nshmp/TextTests.java
@@ -1,5 +1,12 @@
 package gov.usgs.earthquake.nshmp;
 
+import static gov.usgs.earthquake.nshmp.Text.Delimiter.COLON;
+import static gov.usgs.earthquake.nshmp.Text.Delimiter.COMMA;
+import static gov.usgs.earthquake.nshmp.Text.Delimiter.DASH;
+import static gov.usgs.earthquake.nshmp.Text.Delimiter.PERIOD;
+import static gov.usgs.earthquake.nshmp.Text.Delimiter.SLASH;
+import static gov.usgs.earthquake.nshmp.Text.Delimiter.SPACE;
+import static gov.usgs.earthquake.nshmp.Text.Delimiter.UNDERSCORE;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotEquals;
 import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -60,99 +67,84 @@ class TextTests {
 
   @Test
   final void testSplitsAndJoin() {
+
     String str = "Test string";
-    assertEquals(str,
-        Text.join(Text.split(str, Text.Delimiter.SPACE), Text.Delimiter.SPACE));
+    Iterable<String> split = SPACE.splitter().split(str);
+    assertEquals(str, SPACE.joiner().join(split));
 
     str = "test";
-    assertEquals(str,
-        Text.join(Text.split(str, Text.Delimiter.SPACE), Text.Delimiter.SPACE));
+    split = SPACE.splitter().split(str);
+    assertEquals(str, SPACE.joiner().join(split));
 
     // duplicate delimiters do not produce an empty field
     str = "test_double__underscore_delimited";
-    assertNotEquals(str,
-        Text.join(Text.split(str, Text.Delimiter.UNDERSCORE),
-            Text.Delimiter.UNDERSCORE));
+    split = UNDERSCORE.splitter().split(str);
+    assertNotEquals(str, UNDERSCORE.joiner().join(split));
+
     str = "Test string 1 with   extra spaces";
-    assertNotEquals(str,
-        Text.join(Text.split(str, Text.Delimiter.SPACE), Text.Delimiter.SPACE));
+    split = SPACE.splitter().split(str);
+    assertNotEquals(str, SPACE.joiner().join(split));
 
     str = "test string with μ β θν° unicode";
-    assertEquals(str,
-        Text.join(Text.split(str, Text.Delimiter.SPACE), Text.Delimiter.SPACE));
+    split = SPACE.splitter().split(str);
+    assertEquals(str, SPACE.joiner().join(split));
 
     str = "test:colon:delimited";
-    assertEquals(str,
-        Text.join(Text.split(str, Text.Delimiter.COLON), Text.Delimiter.COLON));
+    split = COLON.splitter().split(str);
+    assertEquals(str, COLON.joiner().join(split));
 
     str = "test,comma,delimited";
-    assertEquals(str,
-        Text.join(Text.split(str, Text.Delimiter.COMMA), Text.Delimiter.COMMA));
+    split = COMMA.splitter().split(str);
+    assertEquals(str, COMMA.joiner().join(split));
 
     str = "test-dash-delimited";
-    assertEquals(str,
-        Text.join(Text.split(str, Text.Delimiter.DASH), Text.Delimiter.DASH));
+    split = DASH.splitter().split(str);
+    assertEquals(str, DASH.joiner().join(split));
 
     str = "test.period.delimited";
-    assertEquals(str,
-        Text.join(Text.split(str, Text.Delimiter.PERIOD), Text.Delimiter.PERIOD));
+    split = PERIOD.splitter().split(str);
+    assertEquals(str, PERIOD.joiner().join(split));
 
     str = "test/slash/delimited";
-    assertEquals(str,
-        Text.join(Text.split(str, Text.Delimiter.SLASH), Text.Delimiter.SLASH));
+    split = SLASH.splitter().split(str);
+    assertEquals(str, SLASH.joiner().join(split));
 
     str = "test_underscore_delimited";
-    assertEquals(str,
-        Text.join(Text.split(str, Text.Delimiter.UNDERSCORE),
-            Text.Delimiter.UNDERSCORE));
+    split = UNDERSCORE.splitter().split(str);
+    assertEquals(str, UNDERSCORE.joiner().join(split));
 
     str = "1 2 3 4";
-    assertEquals(str,
-        Text.join(Text.split(str, Text.Delimiter.SPACE), Text.Delimiter.SPACE));
-    assertEquals(str,
-        Text.join(Text.splitToList(str, Text.Delimiter.SPACE), Text.Delimiter.SPACE));
-    assertNotEquals(str,
-        Text.join(Text.splitToDoubleList(str, Text.Delimiter.SPACE),
-            Text.Delimiter.SPACE));
+    split = SPACE.splitter().split(str);
+    assertEquals(str, SPACE.joiner().join(split));
+    assertNotEquals(str, SPACE.joiner().join(Text.splitToDoubleList(str, SPACE)));
 
     str = "1.0 2.0 3.0 4.0";
-    assertEquals(str,
-        Text.join(Text.splitToDoubleList(str, Text.Delimiter.SPACE),
-            Text.Delimiter.SPACE));
+    List<Double> splitDouble = Text.splitToDoubleList(str, SPACE);
+    assertEquals(str, SPACE.joiner().join(splitDouble));
     str = "1.1 2.02 3.3 4.0";
-    assertEquals(str,
-        Text.join(Text.splitToDoubleList(str, Text.Delimiter.SPACE),
-            Text.Delimiter.SPACE));
+    splitDouble = Text.splitToDoubleList(str, SPACE);
+    assertEquals(str, SPACE.joiner().join(splitDouble));
     // trailing zeros are not reproduced
     str = "1.10 2.02 3.3 4.0";
-    assertNotEquals(str,
-        Text.join(Text.splitToDoubleList(str, Text.Delimiter.SPACE),
-            Text.Delimiter.SPACE));
+    splitDouble = Text.splitToDoubleList(str, SPACE);
+    assertNotEquals(str, SPACE.joiner().join(splitDouble));
 
     // doubles below 1e-3 are returned in scientific notation, with capital E
     str = "1.0E-4 2.02 3.3 4.0";
-    assertEquals(str,
-        Text.join(Text.splitToDoubleList(str, Text.Delimiter.SPACE),
-            Text.Delimiter.SPACE));
+    splitDouble = Text.splitToDoubleList(str, SPACE);
+    assertEquals(str, SPACE.joiner().join(splitDouble));
     str = "1.0E-3 2.02 3.3 4.0";
-    assertNotEquals(str,
-        Text.join(Text.splitToDoubleList(str, Text.Delimiter.SPACE),
-            Text.Delimiter.SPACE));
+    splitDouble = Text.splitToDoubleList(str, SPACE);
+    assertNotEquals(str, SPACE.joiner().join(splitDouble));
 
     List<Double> listDoubles = List.of(1e-4, 2.02, 3.3);
-    assertEquals(listDoubles, Text.splitToDoubleList(
-        Text.join(listDoubles, Text.Delimiter.COLON), Text.Delimiter.COLON));
+    String joined = COLON.joiner().join(listDoubles);
+    splitDouble = Text.splitToDoubleList(joined, COLON);
+    assertEquals(listDoubles, splitDouble);
   }
 
   @Test
   final void testEnumsToString() {
-    // System.out.println("Print enum as default: " + TestEnum.ABC);
-    // System.out.println("Pring enum as name(): " + TestEnum.ABC.name());
-    // System.out.println("Print enum toString(): " + TestEnum.ABC.toString());
-    // System.out.println();
-
-    // This should be more robust, create expected string with alternative
-    // method?
     List<TestEnum> v = List.of(TestEnum.values());
     String enumString = "[ABC,DEF,G_HI_J]";
     assertEquals(enumString, Text.enumsToString(v, TestEnum.class));
@@ -201,17 +193,6 @@ class TextTests {
     assertEquals("0.000", fmtFunc.apply(0.000000001));
   }
 
-  @Test
-  final void testReadDouble() {
-    String string = "0.0   1.0  2.0 3.0  4   5.0\n";
-    for (double i = 0.0; i <= 5.0; i++) {
-      assertEquals(i, Text.readDouble(string, (int) i));
-    }
-    assertThrows(IndexOutOfBoundsException.class, () -> {
-      Text.readDouble(string, 10);
-    });
-  }
-
   @Test
   final void testCleanCoordinates() {
     assertEquals(cleanCoords, Text.cleanCoordinates(dirtyCoords));
diff --git a/src/test/java/gov/usgs/earthquake/nshmp/gmm/CoefficientContainerTest.java b/src/test/java/gov/usgs/earthquake/nshmp/gmm/CoefficientContainerTest.java
index 6df85d6e..3f4c469f 100644
--- a/src/test/java/gov/usgs/earthquake/nshmp/gmm/CoefficientContainerTest.java
+++ b/src/test/java/gov/usgs/earthquake/nshmp/gmm/CoefficientContainerTest.java
@@ -6,15 +6,15 @@ import org.junit.jupiter.api.Test;
 
 class CoefficientContainerTest {
 
+  /*
+   * Coefficient container is largely tested via GMM tests. Need only to check
+   * bad coefficients file for full coverage.
+   */
+
   @Test
   void testParseImt() {
-    assertThrows(NumberFormatException.class, () -> {
-      System.out.println(CoefficientContainer.parseImt("garbage"));
-    });
-
-    assertThrows(IllegalArgumentException.class, () -> {
-      System.out.println(CoefficientContainer.parseImt("0.1234"));
+    assertThrows(RuntimeException.class, () -> {
+      new CoefficientContainer("bad coeffs file");
     });
   }
-
 }
diff --git a/src/test/java/gov/usgs/earthquake/nshmp/gmm/GmmTest.java b/src/test/java/gov/usgs/earthquake/nshmp/gmm/GmmTest.java
index 4c4a0b44..f441fc9f 100644
--- a/src/test/java/gov/usgs/earthquake/nshmp/gmm/GmmTest.java
+++ b/src/test/java/gov/usgs/earthquake/nshmp/gmm/GmmTest.java
@@ -1,5 +1,7 @@
 package gov.usgs.earthquake.nshmp.gmm;
 
+import static gov.usgs.earthquake.nshmp.Text.Delimiter.COMMA;
+import static gov.usgs.earthquake.nshmp.Text.Delimiter.DASH;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 
 import java.io.IOException;
@@ -10,7 +12,6 @@ import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
 import java.util.stream.Collectors;
@@ -21,9 +22,6 @@ import org.junit.jupiter.params.provider.Arguments;
 import com.google.common.collect.Lists;
 import com.google.common.io.Resources;
 
-import gov.usgs.earthquake.nshmp.Text;
-import gov.usgs.earthquake.nshmp.Text.Delimiter;
-
 class GmmTest {
 
   private static final double TOL = 1e-10;
@@ -66,11 +64,10 @@ class GmmTest {
         String id = gmm.name() + "-" + imt.name();
         for (GmmInput input : inputs) {
           GroundMotion sgm = GroundMotions.combine(gmModel.calc(input));
-          String result = Text.join(
+          String result = COMMA.joiner().join(
               Lists.newArrayList(modelIndex++ + "-" + id,
                   String.format("%.10f", Math.exp(sgm.mean())),
-                  String.format("%.10f", sgm.sigma())),
-              Delimiter.COMMA);
+                  String.format("%.10f", sgm.sigma())));
           lines.add(result);
         }
       }
@@ -116,14 +113,14 @@ class GmmTest {
   }
 
   private static Object[] resultsToObject(String line, String inputs) {
-    Iterator<String> lineIt = Text.split(line, Delimiter.COMMA).iterator();
-    Iterator<String> idIt = Text.split(lineIt.next(), Delimiter.DASH).iterator();
+    List<String> elements = COMMA.splitter().splitToList(line);
+    List<String> id = DASH.splitter().splitToList(elements.get(0));
     return new Object[] {
-        Integer.valueOf(idIt.next()), // inputs index
-        Gmm.valueOf(idIt.next()), // Gmm
-        Imt.valueOf(idIt.next()), // Imt
-        Double.valueOf(lineIt.next()), // median
-        Double.valueOf(lineIt.next()), // sigma
+        Integer.valueOf(id.get(0)), // inputs index
+        Gmm.valueOf(id.get(1)), // Gmm
+        Imt.valueOf(id.get(2)), // Imt
+        Double.valueOf(elements.get(1)), // median
+        Double.valueOf(elements.get(2)), // sigma
         inputs
     };
   }
-- 
GitLab