public static @NotNull JavaVersion parse(@NotNull String versionString) throws IllegalArgumentException {
    // trimming
    String str = versionString.trim();
    Map<String, String> trimmingMap = new HashMap<>(); // "substring to detect" to "substring from which to trim"
    trimmingMap.put("Runtime Environment", "(build ");
    trimmingMap.put("OpenJ9", "version ");
    trimmingMap.put("GraalVM", "Java ");
    for (String keyToDetect : trimmingMap.keySet()) {
      if (str.contains(keyToDetect)) {
        int p = str.indexOf(trimmingMap.get(keyToDetect));
        if (p > 0) str = str.substring(p);
      }
    }

    // partitioning
    List<String> numbers = new ArrayList<>(), separators = new ArrayList<>();
    int length = str.length(), p = 0;
    boolean number = false;
    while (p < length) {
      int start = p;
      while (p < length && Character.isDigit(str.charAt(p)) == number) p++;
      String part = str.substring(start, p);
      (number ? numbers : separators).add(part);
      number = !number;
    }

    // parsing
    if (!numbers.isEmpty() && !separators.isEmpty()) {
      try {
        int feature = Integer.parseInt(numbers.get(0)), minor = 0, update = 0, build = 0;
        boolean ea = false;

        if (feature >= 5 && feature < MAX_ACCEPTED_VERSION) {
          // Java 9+; Java 5+ (short format)
          p = 1;
          while (p < separators.size() && ".".equals(separators.get(p))) p++;
          if (p > 1 && numbers.size() > 2) {
            minor = Integer.parseInt(numbers.get(1));
            update = Integer.parseInt(numbers.get(2));
          }
          if (p < separators.size()) {
            String s = separators.get(p);
            if (s != null && !s.isEmpty() && s.charAt(0) == '-') {
              ea = startsWithWord(s, "-ea") || startsWithWord(s, "-internal");
              if (p < numbers.size() && s.charAt(s.length() - 1) == '+') {
                build = Integer.parseInt(numbers.get(p));
              }
              p++;
            }
            if (build == 0 && p < separators.size() && p < numbers.size() && "+".equals(separators.get(p))) {
              build = Integer.parseInt(numbers.get(p));
            }
          }
          return new JavaVersion(feature, minor, update, build, ea);
        }
        else if (feature == 1 && numbers.size() > 1 && separators.size() > 1 && ".".equals(separators.get(1))) {
          // Java 1.0 .. 1.4; Java 5+ (prefixed format)
          feature = Integer.parseInt(numbers.get(1));
          if (feature <= MAX_ACCEPTED_VERSION) {
            if (numbers.size() > 2 && separators.size() > 2 && ".".equals(separators.get(2))) {
              minor = Integer.parseInt(numbers.get(2));
              if (numbers.size() > 3 && separators.size() > 3 && "_".equals(separators.get(3))) {
                update = Integer.parseInt(numbers.get(3));
                if (separators.size() > 4) {
                  String s = separators.get(4);
                  if (s != null && !s.isEmpty() && s.charAt(0) == '-') {
                    ea = startsWithWord(s, "-ea") || startsWithWord(s, "-internal");
                  }
                  p = 4;
                  while (p < separators.size() && !separators.get(p).endsWith("-b")) p++;
                  if (p < numbers.size()) {
                    build = Integer.parseInt(numbers.get(p));
                  }
                }
              }
            }
            return new JavaVersion(feature, minor, update, build, ea);
          }
        }
      }
      catch (NumberFormatException ignored) { }
    }

    throw new IllegalArgumentException(versionString);
  }
public static @NotNull JavaVersion parse(@NotNull String versionString)