NumberSequence { public static Either<NumberSequence, String> parse(String value) { List<String> parts = StringExt.splitByComma(value); List<NumberSequence> subsequences = new ArrayList<>(); for (String part : parts) { Matcher matcher = PART_RE.matcher(part); if (!matcher.matches()) { return Either.ofError(String.format("Syntax error in sequence fragment '%s'", part)); } if (matcher.group(2) == null) { subsequences.add(new ValuesSequence(Integer.parseInt(part))); } else if (matcher.group(3) == null) { subsequences.add(new RangeSequence(Long.parseLong(matcher.group(1)), Long.MAX_VALUE, 1)); } else { long from = Long.parseLong(matcher.group(1)); long to = Long.parseLong(matcher.group(3)); if (from >= to) { return Either.ofError(String.format("Invalid range: %s (from) >= %s (to)", from, to)); } subsequences.add(new RangeSequence(from, to, 1)); } } return Either.ofValue(subsequences.isEmpty() ? EMPTY_SEQUENCE : new CompositeSequence(subsequences)); }  abstract long getCount(); abstract Iterator<Long> getIterator(); abstract boolean matches(long number); abstract NumberSequence skip(long skipCount); abstract NumberSequence step(long step); static NumberSequence from(long from); static Either<NumberSequence, String> parse(String value);  }
@Test public void testParse() { checkSequence(tryParse(""), Collections.emptyList(), false); checkSequence(tryParse("1,2,3"), asList(1L, 2L, 3L), false); checkSequence(tryParse("1..5"), asList(1L, 2L, 3L, 4L), false); checkSequence(tryParse("1.."), asList(1L, 2L, 3L, 4L, 5L, 6L), true); checkSequence(tryParse("1, 2, 3, 4..6, 10"), asList(1L, 2L, 3L, 4L, 5L, 10L), false); }