From 787f691f44dfdbc488959627273f8cee74cc60da Mon Sep 17 00:00:00 2001 From: Luck Date: Mon, 25 Nov 2019 00:34:23 +0000 Subject: [PATCH] Improve shorthand parser --- .../luckperms/common/node/AbstractNode.java | 2 +- .../common/node/utils/ShorthandParser.java | 234 ++++++++++-------- 2 files changed, 136 insertions(+), 100 deletions(-) diff --git a/common/src/main/java/me/lucko/luckperms/common/node/AbstractNode.java b/common/src/main/java/me/lucko/luckperms/common/node/AbstractNode.java index c2e376fdb..218ddf78e 100644 --- a/common/src/main/java/me/lucko/luckperms/common/node/AbstractNode.java +++ b/common/src/main/java/me/lucko/luckperms/common/node/AbstractNode.java @@ -74,7 +74,7 @@ public abstract class AbstractNode, B extends NodeBui this.contexts = contexts; this.metadata = metadata; - this.resolvedShorthand = this instanceof PermissionNode ? ImmutableList.copyOf(ShorthandParser.parseShorthand(this.key)) : ImmutableList.of(); + this.resolvedShorthand = this instanceof PermissionNode ? ImmutableList.copyOf(ShorthandParser.expandShorthand(this.key)) : ImmutableList.of(); this.hashCode = calculateHashCode(); } diff --git a/common/src/main/java/me/lucko/luckperms/common/node/utils/ShorthandParser.java b/common/src/main/java/me/lucko/luckperms/common/node/utils/ShorthandParser.java index 58172c7ff..c2d243035 100644 --- a/common/src/main/java/me/lucko/luckperms/common/node/utils/ShorthandParser.java +++ b/common/src/main/java/me/lucko/luckperms/common/node/utils/ShorthandParser.java @@ -26,45 +26,123 @@ package me.lucko.luckperms.common.node.utils; import com.google.common.base.Splitter; -import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterators; -import java.util.ArrayList; -import java.util.Collections; import java.util.HashSet; -import java.util.List; +import java.util.Iterator; import java.util.Set; -import java.util.function.Function; -import java.util.stream.Collectors; -import java.util.stream.IntStream; -public final class ShorthandParser { - private ShorthandParser() {} +/** + * Utility to expand shorthand nodes + */ +public enum ShorthandParser { - private static final List>> PARSERS = ImmutableList.>>builder() - .add(new ListParser()) - .add(new CharacterRangeParser()) - .add(new NumericRangeParser()) - .build(); + /** + * Expands "1-4" to ["1", "2", "3", "4"] + */ + NUMERIC_RANGE { + @Override + public Iterator extract(String input) { + int index = input.indexOf(RANGE_SEPARATOR); + if (index == -1 || index == 0 || index == (input.length() - 1)) { + return null; + } - public static Set parseShorthand(String s) { - Set results = new HashSet<>(1); + return new RangeIterator(Integer.parseInt(input.substring(0, index)), Integer.parseInt(input.substring(index + 1))) { + @Override + protected String toString(int i) { + return Integer.toString(i); + } + }; + } + }, + + /** + * Expands "a-d" to ["a", "b", "c", "d"] + */ + CHARACTER_RANGE { + @Override + public Iterator extract(String input) { + if (input.length() != 3 || input.charAt(1) != RANGE_SEPARATOR) { + return null; + } + + return new RangeIterator(input.charAt(0), input.charAt(2)) { + @Override + protected String toString(int i) { + return Character.toString((char) i); + } + }; + } + }, + + /** + * Expands "aa,bb,cc" to ["aa", "bb", "cc"] + */ + LIST { + private final Splitter splitter = Splitter.on(LIST_SEPARATOR).omitEmptyStrings(); + + @Override + public Iterator extract(String input) { + if (input.indexOf(LIST_SEPARATOR) == -1) { + return Iterators.singletonIterator(input); + } + return this.splitter.split(input).iterator(); + } + }; + + /** + * Expands the given input in shorthand notation to a number of strings. + * + * @param input the input shorthand + * @return an iterator of the resultant strings, or optionally null if the input was invalid + * @throws IllegalArgumentException if the input was invalid + */ + abstract Iterator extract(String input) throws IllegalArgumentException; + + /** Character used to open a group */ + private static final char OPEN_GROUP = '{'; + /** Character used to close a group */ + private static final char CLOSE_GROUP = '}'; + /** Character used to separate items in a list */ + private static final char LIST_SEPARATOR = ','; + /** Character used to indicate a range between two values */ + private static final char RANGE_SEPARATOR = '-'; + + /** The parsers */ + private static final ShorthandParser[] PARSERS = values(); + + /** + * Parses and expands the shorthand format. + * + * @param s the string to expand + * @return the expanded result + */ + public static Set expandShorthand(String s) { + Set results = new HashSet<>(); results.add(s); + Set workSet = new HashSet<>(); while (true) { - Set working = new HashSet<>(results.size()); - int beforeSize = results.size(); + boolean work = false; for (String str : results) { - Set ret = captureResults(str); - if (ret != null) { - working.addAll(ret); + Set expanded = matchGroup(str); + if (expanded != null) { + work = true; + workSet.addAll(expanded); } else { - working.add(str); + workSet.add(str); } } - if (working.size() != beforeSize) { - results = working; + if (work) { + // set results := workSet, and init an empty workSet for the next iteration + // (we actually sneakily reuse the existing results HashSet to avoid having to create a new one) + Set temp = results; + results = workSet; + workSet = temp; + workSet.clear(); continue; } @@ -77,13 +155,13 @@ public final class ShorthandParser { return results; } - private static Set captureResults(String s) { - int openingIndex = s.indexOf('{'); + private static Set matchGroup(String s) { + int openingIndex = s.indexOf(OPEN_GROUP); if (openingIndex == -1) { return null; } - int closingIndex = s.indexOf('}'); + int closingIndex = s.indexOf(CLOSE_GROUP); if (closingIndex < openingIndex) { return null; } @@ -92,91 +170,49 @@ public final class ShorthandParser { String after = s.substring(closingIndex + 1); String between = s.substring(openingIndex + 1, closingIndex); - Set results = new HashSet<>(10); + Set results = new HashSet<>(); - for (Function> parser : PARSERS) { - Iterable res = parser.apply(between); - if (res != null) { - for (String r : res) { - results.add(before + r + after); + for (ShorthandParser parser : PARSERS) { + try { + Iterator res = parser.extract(between); + if (res != null) { + while (res.hasNext()) { + results.add(before + res.next() + after); + } + + // break after one parser has matched + break; } + } catch (IllegalArgumentException e) { + // ignore } } return results; } - private static class ListParser implements Function> { - private static final Splitter SPLITTER = Splitter.on(','); + /** + * Implements an iterator over a given range of ints. + */ + private abstract static class RangeIterator implements Iterator { + private final int max; + private int next; + + RangeIterator(int a, int b) { + this.max = Math.max(a, b); + this.next = Math.min(a, b); + } + + protected abstract String toString(int i); @Override - public Iterable apply(String s) { - if (!s.contains(",")) { - return Collections.singleton(s); - } - return SPLITTER.split(s); - } - } - - private static class NumericRangeParser implements Function> { - - private static Integer parseInt(String a) { - try { - return Integer.parseInt(a); - } catch (NumberFormatException e) { - return null; - } + public final boolean hasNext() { + return this.next <= this.max; } @Override - public Iterable apply(String s) { - int index = s.indexOf("-"); - if (index == -1) { - return null; - } - - Integer before = parseInt(s.substring(0, index)); - if (before == null) { - return null; - } - - Integer after = parseInt(s.substring(index + 1)); - if (after == null) { - return null; - } - - return IntStream.rangeClosed(before, after).mapToObj(Integer::toString).collect(Collectors.toList()); - } - } - - private static class CharacterRangeParser implements Function> { - - private static List getCharRange(char a, char b) { - List s = new ArrayList<>(); - for (char c = a; c <= b; c++) { - s.add(Character.toString(c)); - } - return s; - } - - @Override - public Iterable apply(String s) { - int index = s.indexOf("-"); - if (index == -1) { - return null; - } - - String before = s.substring(0, index); - if (before.length() != 1) { - return null; - } - - String after = s.substring(index + 1); - if (after.length() != 1) { - return null; - } - - return getCharRange(before.charAt(0), after.charAt(0)); + public final String next() { + return toString(this.next++); } }