/**
   * Add Cartesian product of prefixes and suffixes to a string, each give as a D4M String.
   *
   * @see #padD4mString_Single(String, String, String)
   */
  public static String padD4mString(String prefixes, String suffixes, String str) {
    if (prefixes == null || prefixes.isEmpty()) prefixes = ",";
    if (suffixes == null || suffixes.isEmpty()) suffixes = ",";
    if (prefixes.length() <= 1 && suffixes.length() <= 1) return str;

    if (d4mStringContainsRange(str)) {
      //      if (suffixes.length()>1)
      //        throw new UnsupportedOperationException("No way to append the suffixes "+suffixes+
      //            " to a D4M String containing a Range: "+str);
      // add prefix to v0 Ranges. Goto full Range Objects because ':' is complicated.
      SortedSet<Range> tmp = GraphuloUtil.d4mRowToRanges(str), tmp2 = new TreeSet<>();
      for (String startPre : GraphuloUtil.splitD4mString(prefixes))
        for (Range range : tmp) tmp2.add(GraphuloUtil.prependPrefixToRange(startPre, range));
      str = GraphuloUtil.rangesToD4MString(tmp2, str.charAt(str.length() - 1));
      prefixes = ",";
    }

    String s = "";
    for (String pre : GraphuloUtil.splitD4mString(prefixes)) {
      for (String suf : GraphuloUtil.splitD4mString(suffixes)) {
        s += padD4mString_Single(pre, suf, str);
      }
    }
    return s;
  }
 /**
  * Add prefix and/or suffix to every part of a D4M string.
  * prependStartPrefix("pre|","X","a,b,:,v,:,") ==> "pre|aX,pre|bX,:,pre|vX,:,"
  */
 public static String padD4mString_Single(String prefix, String suffix, String str) {
   if (prefix == null) prefix = "";
   if (suffix == null) suffix = "";
   if (prefix.isEmpty() && suffix.isEmpty()) return str;
   char sep = str.charAt(str.length() - 1);
   StringBuilder sb = new StringBuilder();
   for (String part : GraphuloUtil.splitD4mString(str)) {
     if (part.equals(":")) sb.append(part).append(sep);
     else sb.append(prefix).append(part).append(suffix).append(sep);
   }
   return sb.toString();
 }
  /**
   * Makes each input term into a prefix range.
   *
   * <pre>
   *  "v1,v5," => "v1|,:,v1},v5|,:,v5},"
   *  "v1,:,v3,v5," => "v1,:,v3,v5|,:,v5},"
   * </pre>
   */
  public static String singletonsAsPrefix(String str) {
    Preconditions.checkNotNull(str);
    Preconditions.checkArgument(!str.isEmpty());
    //    Preconditions.checkArgument(str.indexOf(':') != -1, "Cannot have the ':' character:
    // "+str);
    char sep = str.charAt(str.length() - 1);
    if (d4mStringContainsEmptyString(str)) // empty prefix is full range.
    return ":" + sep;

    if (!d4mStringContainsRange(str)) {
      StringBuilder sb = new StringBuilder();
      for (String vktext : GraphuloUtil.splitD4mString(str)) {
        sb.append(vktext)
            .append(sep)
            .append(':')
            .append(sep)
            .append(prevRow(Range.followingPrefix(new Text(vktext)).toString()))
            .append(sep);
      }
      return sb.toString();
    }

    //    Collection<Range> origRngs = d4mRowToRanges(str);
    //    for (Range rng : origRngs) {
    //      // if a singleton row, then make into a prefix row
    //
    //    }

    //    String[] strSplit = str.substring(0, str.length() - 1)
    //        .split(String.valueOf(sep));
    //    List<String> strList = Arrays.asList(strSplit);
    //    PeekingIterator3<String> pi = new PeekingIterator3<>(strList.iterator());
    //    SortedSet<Range> rngset = new TreeSet<>();
    //
    //    if (pi.peekFirst().equals(":")) { // (-Inf,
    //      if (pi.peekSecond() == null) {
    //        return str; // (-Inf,+Inf)
    //      } else {
    //        if (pi.peekSecond().equals(":") || (pi.peekThird() != null &&
    // pi.peekThird().equals(":")))
    //          throw new IllegalArgumentException("Bad D4M rowStr: " + str);
    ////        sb.append(':').append(sep).append(pi.peekSecond()).append(sep); // (-Inf,2]
    //        rngset.add(new Range(null, false, pi.peekSecond(), true)); // (-Inf,2]
    //        pi.next();
    //        pi.next();
    //      }
    //    }
    //
    //    while (pi.hasNext()) {
    //      if (pi.peekSecond() == null) { // last singleton row [1,1~)
    ////        sb.append(pi.peekFirst()).append(sep)
    ////            .append(':').append(sep)
    ////            .append(Range.followingPrefix(new Text(pi.peekFirst())).toString()).append(sep);
    //        rngset.add(Range.prefix(pi.peekFirst()));
    //
    //      } else if (pi.peekSecond().equals(":")) {
    //        if (pi.peekThird() == null) { // [1,+Inf)
    ////          sb.append(pi.peekFirst()).append(sep).append(':').append(sep);
    //          rngset.add(new Range(pi.peekFirst(), true, null, false));
    //
    //        } else { String s = GraphuloUtil.singletonsAsPrefix(vktexts, sep);// [1,3]
    //          if (pi.peekThird().equals(":"))
    //            throw new IllegalArgumentException("Bad D4M rowStr: " + str);
    ////          sb.append(pi.peekFirst()).append(sep)
    ////              .append(':').append(sep)
    ////              .append(pi.peekThird()).append(sep);
    //          rngset.add(new Range(pi.peekFirst(), true, pi.peekThird(), true));
    //          pi.next();
    //          pi.next();
    //          pi.next();
    //        }
    //      } else { // [1,1~)
    ////        sb.append(pi.peekFirst()).append(sep)
    ////            .append(':').append(sep)
    ////            .append(Range.followingPrefix(new Text(pi.peekFirst())).toString()).append(sep);
    //        rngset.add(Range.prefix(pi.peekFirst()));
    //        pi.next();
    //      }
    //    }

    Collection<Range> prefixRngs = d4mRowToRanges(str, true);
    //    log.info(prefixRngs);
    return rangesToD4MString(prefixRngs, sep);
  }