static String trimFieldsAndRemoveEmptyFields(String str) {
    char[] chars = str.toCharArray();
    char[] res = new char[chars.length];
    /*
     * set when reading the first non trimmable char after a separator char (or the beginning of the string)
     * unset when reading a separator
     */
    boolean inField = false;
    boolean inQuotes = false;
    int i = 0;
    int resI = 0;
    for (; i < chars.length; i++) {
      boolean isSeparator = chars[i] == ',';
      if (!inQuotes && isSeparator) {
        // exiting field (may already be unset)
        inField = false;
        if (resI > 0) {
          resI = retroTrim(res, resI);
        }
      } else {
        boolean isTrimmed = !inQuotes && istrimmable(chars[i]);
        if (isTrimmed && !inField) {
          // we haven't meet any non trimmable char since the last separator yet
          continue;
        }

        boolean isEscape = isEscapeChar(chars[i]);
        if (isEscape) {
          inQuotes = !inQuotes;
        }

        // add separator as we already had one field
        if (!inField && resI > 0) {
          res[resI] = ',';
          resI++;
        }

        // register in field (may already be set)
        inField = true;
        // copy current char
        res[resI] = chars[i];
        resI++;
      }
    }
    // inQuotes can only be true at this point if quotes are unbalanced
    if (!inQuotes) {
      // trim end of str
      resI = retroTrim(res, resI);
    }
    return new String(res, 0, resI);
  }
@Test
  public void trimFieldsAndRemoveEmptyFields_throws_NPE_if_arg_is_null() {
    assertThatThrownBy(() -> trimFieldsAndRemoveEmptyFields(null))
      .isInstanceOf(NullPointerException.class);
  }