/* dCache - http://www.dcache.org/ * * Copyright (C) 2015 Deutsches Elektronen-Synchrotron * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.dcache.util; import java.util.ArrayList; import java.util.List; import java.util.StringTokenizer; import static com.google.common.collect.Iterables.concat; import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; /** * Simple parser that expands alternation lists in globs recursively. * * Recognizes the following LL(1) grammar: * * S ::= E T * T ::= "," S | "" * E ::= STR F | F * F ::= "{" S "}" E | "" * STR ::= [^,{}]+ * * * The grammar is implemented as a recursive decent parser that unfolds the * alternations on the fly. The semantics are defined by the following pseudo * code ([] constructs a list, U is union and x is the cartesian product): * * expand(S) = expand(E) U expand(T) * expand(T) = expand(S) | [] * expand(E) = [ STR ] x expand(F) | expand(F) * expand(F) = expand(S) x expand(E) | [""] */ class GlobBraceParser { /** * Simple lexicographical analyzer with a look ahead of 1. */ private static class Scanner { private final StringTokenizer tokenizer; private String current; public Scanner(String s) { tokenizer = new StringTokenizer(s, ",{}", true); current = tokenizer.hasMoreTokens() ? tokenizer.nextToken() : ""; } public String peek() { return current; } public String next() { String current = this.current; this.current = tokenizer.hasMoreTokens() ? tokenizer.nextToken() : ""; return current; } } private final Scanner scanner; GlobBraceParser(String s) { scanner = new Scanner(s); } Iterable<String> expandGlob() { Iterable<String> result = expandE(); checkEndOfInput(); return result; } Iterable<String> expandList() { Iterable<String> result = expandS(); checkEndOfInput(); return result; } private void checkEndOfInput() { if (!scanner.peek().isEmpty()) { throw new IllegalArgumentException("Unexpected token " + scanner.peek()); } } private Iterable<String> expandS() { return concat(expandE(), expandT()); } private Iterable<String> expandT() { switch (scanner.peek()) { case ",": scanner.next(); return expandS(); default: return emptyList(); } } private Iterable<String> expandE() { switch (scanner.peek()) { case "{": case "}": case ",": return expandF(); default: return cartesianProduct(singletonList(scanner.next()), expandF()); } } private Iterable<String> expandF() { if (scanner.peek().equals("{")) { scanner.next(); Iterable<String> left = expandS(); String token = scanner.next(); if (!token.equals("}")) { throw new IllegalArgumentException("Expected '}' instead of '" + token + '\''); } Iterable<String> right = expandE(); return cartesianProduct(left, right); } else { return singletonList(""); } } private Iterable<String> cartesianProduct(Iterable<String> left, Iterable<String> right) { List<String> result = new ArrayList<>(); for (String s1 : left) { for (String s2 : right) { result.add(s1 + s2); } } return result; } }