Alex Pakka
Alex Pakka

Reputation: 9706

Java 8 lambda expression callback type when parsing strings with regex

I would like to write a function, that can match a string against regex and execute a callback with all group matches as parameters.

I came up with this and it works:

 private static void parse(String source, String regex, 
                                  Consumer<String[]> callback) {      
        //convert regex groups 1..n into integer array 0..n-1 and call 
        //consumer callback with this array
        Matcher m = Pattern.compile(regex).matcher(source);
        String[] ret = new String[m.groupCount()];
        if (m.matches()) {
            for (int i=0; i<m.groupCount(); i++) {
                ret[i] = m.group(1+i);
            }
            callback.accept(ret);
        }
    }

You can then do

parse("Add 42,43", "Add (\\d+?),(\\d+?)", p -> processData(p[0],p[1]));

What I would like to be able to do ideally is this

parse("Add 42,43", "Add (\\d+?),(\\d+?)", (x,y) -> processData(x,y));

What would be the most elegant way? The only one I can think of is to declare multiple functional interfaces with 1..n parameters and use overrides to handle it. Any better ideas, maybe with reflection?

Upvotes: 3

Views: 4393

Answers (2)

Alex Pakka
Alex Pakka

Reputation: 9706

Since I solved it for myself by now, I will post a solution I came up with here. If someone proposes a better one, maybe with method chaining or more generic, I will gladly grant an answer.

You can use the class below like this:

Sring msg = "add:42,34";
ParseUtils.parse(msg, "add:(\\d+),(\\d+)", (int x,int y) -> simulator.add(x, y));
ParseUtils.parse(msg, "add:(\\d+),(\\d+)", simulator::add); //IntBiConsumer match
ParseUtils.parse(msg, "add:(.*?),", System.out::println);

And here is the class (I omitted trivial error processing and boolean returns if no match):

public class ParseUtils {

    @FunctionalInterface
    public interface Consumer { void accept(String s); }

    @FunctionalInterface
    public interface BiConsumer { void accept(String a, String b); }

    //... you can add TriConsumer etc. if you need to ...

    @FunctionalInterface  //conveniently parses integers
    public interface IntBiConsumer { void accept(int x, int y); }

    // implementations -----
    public static void parse(String src, String regex, Consumer callback) {
        callback.accept(parse(src, regex)[0]);
    }

    public static void parse(String src, String regex, BiConsumer callback) {
        String[] p = parse(src, regex);
        callback.accept(p[0],p[1]);
    }

    public static void parse(String src, String regex, 
                                                    IntBiConsumer callback) {
        String[] p = parse(src, regex);
        callback.accept(Integer.parseInt(p[0]), Integer.parseInt(p[1]));
    }

    public static String[] parse(String source, String pattern) {
        Pattern p = Pattern.compile(pattern);
        Matcher m = p.matcher(source);
        String[] ret = new String[m.groupCount()];
        if (m.matches()) {
            for (int i=0; i<m.groupCount(); i++) {
                ret[i] = m.group(1 + i);
            }
        }
        return ret;
    }

}

Upvotes: 2

Andrey Chaschev
Andrey Chaschev

Reputation: 16516

As I understand the question is if there is a syntax sugar for tuple initialization from an array, i.e.:

 val (hour, minutes, seconds) = new String[]{"12", "05", "44"};

... except for it is hidden inside a lambda arguments declaration.

As far as I know, there is no such syntax in Java 8 and your approach seems the most convenient. There is in Scala, however: Is there a way to initialize multiple variables from array or List in Scala?.

There are similar instructions in Scala as well:

 scala> val s = "Add 42,43"
 scala> val r = "Add (\\d+?),(\\d+?)".r
 scala> val r(x,y) = s
 x: String = 42
 y: String = 43

Upvotes: 2

Related Questions