Možná si někteří z vás ještě vzpomenou, jak jsem kdysi psal o “mikro-knihovně” Re. Čas i myšlenky trochu uzrály, a tak tu máme nástupce, který jde ještě o krok dál. Mám zároveň radost, protože je to má první knihovna, která je dostupná  i přímo z centrálního Maven repozitáře! Pojďme mrknout na to, co umí …

Úvod

Ještě než se do toho pustíme, zdrojové kódy Rojo najdete na githubu. Je vydána pod licencí GNU/GPL verze 3.0, takže návrhy či osobní snahy na zlepšení jsou vítány :-). Nažhavte tedy svá IDE, do POMu šoupněte tuhle dependency a můžeme začít.

<dependency>
    <groupId>com.svetylkovo</groupId>
    <artifactId>rojo</artifactId>
    <version>1.0.0</version>
</dependency>

Letem světem

Ať vás nezatěžuji přílišným povídáním, nechme mluvit hlavně kód :-). Začněme definicí našeho beanu:

@Regex("(\\w+):(\\d+)")
public class SimpleBean {

    @Group(1)
    private String name;
    @Group(2)
    private int count;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getCount() {
        return count;
    }

    public void setCount(int count) {
        this.count = count;
    }
}

Nad hlavičkou třídy anotace @Regex, pro určení samotného regulárního výrazu a pak @Group pro každý field třídy, aby bylo jasné, která grupa se bude mapovat na který field. Rojo zároveň umí automaticky mapovat naparsovaný String z dané grupy na cílový typ fieldu. Seznam podporovaných typů najdete v README na githubu. Dále může mít třída anotaci @Flags, pokud chcete používat flagy (ze třídy Pattern) a fieldy typu Date musí mít navíc anotaci @DateFormat, která určí, v jakém formátu datum chceme.

Nyní samotné matchování:

Optional<SimpleBean> bean = Rojo.of(SimpleBean.class)
                                .match("a:1,b:2,c:3");

List<SimpleBean> list = Rojo.of(SimpleBean.class)
                            .matchList("a:1,b:2,c:3");

Iterator<SimpleBean> iter = Rojo.of(SimpleBean.class)
                                .matchIterator("a:1,b:2,c:3");

Stream<SimpleBean> stream = Rojo.of(SimpleBean.class)
                                .matchStream("a:1,b:2,c:3");

Pokud chcete matchovat bez použití POJO objektů, pak Rojo po svém předchůdci Re převzal vše + přidal i něco navíc:

Optional<String> string = Rojo.find("[a-z]:2", "a:1,b:2,c:3");
Optional<Matcher> matcher = Rojo.findMatcher("[a-z]:2", "a:1,b:2,c:3");

List<String> list = Rojo.asList("[a-z]", "a:1,b:2,c:3");
List<Matcher> matcherList = Rojo.asMatcherList("[a-z]", "a:1,b:2,c:3");

Stream<String> stream = Rojo.asStream("[a-z]", "a:1,b:2,c:3");
Stream<Matcher> matcherStream = Rojo.asMatcherStream("[a-z]", "a:1,b:2,c:3");

Map<String, String> map = Rojo.asMap("([a-z]):(\\d)", "a:1,b:2,c:3");

String replaced = Rojo.replace("[a-z]", "a:1,b:2,c:3", String::toUpperCase);
String replaced2 = Rojo.replaceMatcher("[a-z]", "a:1,b:2,c:3", m -> m.group().toUpperCase());

Zajímavou featurou je metoda asMap(), která v regulárním výrazu očekává právě 2 grupy, které vrátí jako key-value pár v podobě mapy. Sice tohle využijete zřídkakdy, ale někdy by se to hodit mohlo :-).

Optimalizace výkonu

Jelikož každá ze statických metod volaných přímo na třídě Rojo uvnitř vždy vytváří novou instanci třídy Pattern, při mnohačetném volání by mohlo dojít ke zvýšenému využití paměti a snížení celkového výkonu. Pro tyto případy je dobré si daný matcher uložit do proměnné a pak ho znovu-použít:

RojoBeanMatcher<SimpleBean> beanMatcher = Rojo.of(SimpleBean.class);
SimpleBean bean = beanMatcher.match("a:1,b:2,c:3").get();

RojoMatcher matcher = Rojo.matcher("[a-z]");
List<String> list = matcher.asList("a:1,b:2,c:3");

Závěr

Nenapadá mě, co dodat :-). Snad jen, co byste dodali vy?