2

Given an object like this:

Matcher matcher = pattern.matcher(sql);

with usage like so:

Set<String> matches = new HashSet<>();
while (matcher.find()) {
    matches.add(matcher.group());
}

I'd like to replace this while loop by something more object-oriented like so:

new Iterator<String>() {
    @Override
    public boolean hasNext() {
        return matcher.find();
    }

    @Override
    public String next() {
        return matcher.group();
    }
}

so that I can easily e.g. make a Stream of matches, stick to using fluent APIs and such.

The thing is, I don't know and can't find a more concise way to create this Stream or Iterator. An anonymous class like above is too verbose for my taste.

I had hoped to find something like IteratorFactory.from(matcher::find, matcher::group) or StreamSupport.of(matcher::find, matcher::group) in the jdk, but so far no luck. I've no doubt libraries like apache commons or guava provide something for this, but let's say I can't use those.

Is there a convenient factory for Streams or Iterators that takes a hasNext/next method combo in the jdk?

Marnes
  • 651
  • 5
  • 19
  • 1
    is java-9 an option? – Eugene May 17 '18 at 11:37
  • Side-note: both of your method implementations are wrong. They *only* work when the caller always calls `hasNext()` exactly once followed by `next()`. No other access pattern will work. Logically the `find()` method has to be executed in the `next()` method, as that's the thing that proceeds to the next result. – Joachim Sauer May 17 '18 at 11:38
  • @JoachimSauer it's not _that_ wrong. As long as you use this iterator in the usual hasNext() -> next() way, it works fine. Calling hasNext() multiple times will screw it up, yeah, but that's not happening in my situation so it's fine. – Marnes May 17 '18 at 14:51
  • @Marnes: I'd still flag that as an issue in a code review, because just because it works in the *current* code doesn't mean that people will think to check this when the use of your code gets extended. It's simple enough to implement that conversion *once* in one place and make sure that this `MatcherIterator` is implemented correctly. But that's a personal choice, of course. – Joachim Sauer May 17 '18 at 14:53
  • @JoachimSauer yeah I get your point, in theory and if you want everything perfect, it wouldn't work, but in the context of my question... – Marnes May 17 '18 at 18:51

2 Answers2

1

In java-9 you could do it via:

Set<String> result = matcher.results()
            .map(MatchResult::group)
            .collect(Collectors.toSet());
System.out.println(result);

In java-8 you would need a back-port for this, taken from Holger's fabulous answer

EDIT

There is a single method btw tryAdvance that could incorporate find/group, something like this:

static class MyIterator extends AbstractSpliterator<String> {

    private Matcher matcher;

    public MyIterator(Matcher matcher) {
        // I can't think of a better way to estimate the size here
        // may be you can figure a better one here
        super(matcher.regionEnd() - matcher.regionStart(), 0);
        this.matcher = matcher;
    }

    @Override
    public boolean tryAdvance(Consumer<? super String> action) {
        while (matcher.find()) {
            action.accept(matcher.group());
            return true;
        }

        return false;
    }
}

And usage for example:

Pattern p = Pattern.compile("\\d");
Matcher m = p.matcher("12345");
Set<String> result = StreamSupport.stream(new MyIterator(m), false)
            .collect(Collectors.toSet());
Eugene
  • 117,005
  • 15
  • 201
  • 306
  • 1
    of course! why would I be surprised that after searching such a Spliterator is already done by Holger, even better https://stackoverflow.com/a/28150956/1059372 – Eugene May 17 '18 at 12:06
  • thanks for the effort, but neither is really a solution for me. I'm on java 8, and I'd like to avoid rolling big custom code blocks for it. If it's not available ready-made in the jdk, that's just too bad then. – Marnes May 17 '18 at 14:54
  • 1
    `matcher.regionEnd() - matcher.regionStart()` is not a bad estimate, as it denotes the maximum number of possible matches. – Holger May 17 '18 at 16:36
  • 1
    @Marnes so those under 20 lines of code are *big*? I bet you have other jars in your project that you use under 2% of the code they have, besides I bet you do have an `util` package... still, your choice really. – Eugene May 17 '18 at 19:47
  • Accepting because of the java 9 stream answer. Guess I can't have the cake I wanted to have and eat it. Thanks – Marnes May 18 '18 at 13:26
1

This class I wrote embodies what I wanted to find in the jdk. Apparently though it just doesn't exist. eugene's accepted answer offers a java 9 Stream solution though.

public static class SearchingIterator<T> implements Iterator<T> {
    private final BooleanSupplier advancer;
    private final Supplier<T> getter;

    private Optional<T> next;

    public SearchingIterator(BooleanSupplier advancer, Supplier<T> getter) {
        this.advancer = advancer;
        this.getter = getter;

        search();
    }

    private void search() {
        boolean hasNext = advancer.getAsBoolean();
        next = hasNext ? Optional.of(getter.get()) : Optional.empty();
    }

    @Override
    public boolean hasNext() {
        return next.isPresent();
    }

    @Override
    public T next() {
        T current = next.orElseThrow(IllegalStateException::new);
        search();
        return current;
    }
}

Usage:

Matcher matcher = Pattern.compile("\\d").matcher("123");
Iterator<String> it = new SearchingIterator<>(matcher::find, matcher::group);
Marnes
  • 651
  • 5
  • 19