class ValueProperties implements Serializable, Comparable<ValueProperties> {

  public static ValueProperties parse(String s) {
    // REVIEW jonathan 2011-05-11 -- this is bordering on being complex enough to write a grammar and auto-generate the
    // lexer, but it works and ValueProperties is unlikely to change.
    // REVIEW andrew 2011-08-08 -- even as a big fan of crazy-mad home brew state machines I'd use an auto-generated lexer
    if (StringUtils.isBlank(s) || EMPTY.toString().equals(s)) {
      return EMPTY;
    }
    s = s.trim();
    if (INFINITE.toString().equals(s)) {
      return INFINITE;
    }
    boolean nearlyInfinite = false;
    Builder builder = null;
    ValueProperties nearlyInfiniteResult = null;
    if (s.startsWith("INFINITE-")) {
      nearlyInfinite = true;
      s = s.substring(9, s.length());
      nearlyInfiniteResult = INFINITE;
    } else {
      builder = builder();
    }
    if (s.charAt(0) == '{' && s.charAt(s.length() - 1) == '}') {
      // Strip away any curly brace wrappers
      s = s.substring(1, s.length() - 1);
    }
    int pos = 0;
    boolean isOptional = false;
    StringBuilder substring = new StringBuilder();
    String name = null;
    Set<String> values = new HashSet<String>();
    boolean bracketedValue = false;
    boolean inValue = false;
    int space = -1;
    while (pos <= s.length()) {
      char next = pos < s.length() ? s.charAt(pos) : 0;
      if (next == '\\') { // Begin escape sequence
        pos++;
        if (pos < s.length()) {
          if (space > 0) {
            for (int i = 0; i < space; i++) {
              substring.append(' ');
            }
          }
          space = 0;
          char escapedCharacter = s.charAt(pos);
          if (escapedCharacter == '\\' || escapedCharacter == ',' || escapedCharacter == '=' || escapedCharacter == '[' || escapedCharacter == ']' || escapedCharacter == '?' ||
              escapedCharacter == ' ') {
            substring.append(escapedCharacter);
          } else {
            throw new IllegalArgumentException("Unrecognised escape sequence: \\" + escapedCharacter);
          }
        } else {
          throw new IllegalArgumentException("Unexpected end of ValueProperties string: " + s);
        }
      } else if (next == '=') { // Separator between name and values
        if (inValue) {
          throw new IllegalArgumentException("Unexpected '=' at position " + pos);
        }
        space = -1;
        name = substring.toString();
        substring = new StringBuilder();
        inValue = true;
        while (pos + 1 < s.length()) {
          if (s.charAt(pos + 1) == ' ') {
            pos++;
            continue;
          }
          if (s.charAt(pos + 1) == '[') {
            bracketedValue = true;
            pos++;
          }
          break;
        }
      } else if (next == ']') { // End of values
        space = -1;
        inValue = false;
        while (pos + 1 < s.length()) {
          if (s.charAt(pos + 1) == ' ') {
            pos++;
            continue;
          }
          if (s.charAt(pos + 1) == '?') {
            isOptional = true;
            pos++;
          }
          break;
        }
      } else if (next == ',' || next == 0) { // Separator between values in a group or between properties
        space = -1;
        if (substring.length() > 0) {
          if (name == null) {
            name = substring.toString();
          } else {
            values.add(substring.toString());
          }
          substring = new StringBuilder();
        }
        if (!inValue || !bracketedValue) {
          // End of a property
          if (values.isEmpty()) {
            if (nearlyInfinite) {
              nearlyInfiniteResult = nearlyInfiniteResult.withoutAny(name);
            } else {
              builder.withAny(name);
            }
          } else {
            if (nearlyInfinite) {
              throw new IllegalArgumentException("Property values not supported in nearly infinite ValueProperties. Found: " + values);
            }
            builder.with(name, values);
          }
          if (isOptional) {
            builder.withOptional(name);
          }
          name = null;
          values = new HashSet<String>();
          isOptional = false;
          inValue = false;
          bracketedValue = false;
        }
      } else if (next != ' ') {
        if (space > 0) {
          for (int i = 0; i < space; i++) {
            substring.append(' ');
          }
        }
        space = 0;
        substring.append(next);
      } else {
        if (space >= 0) {
          space++;
        }
      }
      pos++;
    }
    if (name != null || substring.length() > 0) {
      throw new IllegalArgumentException("Unexpected end of ValueProperties string: " + s);
    }
    return nearlyInfinite ? nearlyInfiniteResult : builder.get();
  }

    ValueProperties();

}