diff --git a/common/src/main/java/me/lucko/luckperms/common/core/Node.java b/common/src/main/java/me/lucko/luckperms/common/core/Node.java index a530798ca..bb3ad0649 100644 --- a/common/src/main/java/me/lucko/luckperms/common/core/Node.java +++ b/common/src/main/java/me/lucko/luckperms/common/core/Node.java @@ -33,11 +33,11 @@ import me.lucko.luckperms.api.MetaUtils; import me.lucko.luckperms.api.Tristate; import me.lucko.luckperms.api.context.ContextSet; import me.lucko.luckperms.common.constants.Patterns; +import me.lucko.luckperms.common.utils.ShorthandParser; import java.util.*; import java.util.regex.Pattern; import java.util.stream.Collectors; -import java.util.stream.IntStream; /** * An immutable permission node @@ -150,7 +150,7 @@ public class Node implements me.lucko.luckperms.api.Node { suffix = Maps.immutableEntry(i, MetaUtils.unescapeCharacters(suffixPart.get(1))); } - resolvedShorthand = calculateShorthand(); + resolvedShorthand = ImmutableList.copyOf(ShorthandParser.parseShorthand(getPermission())); serializedNode = calculateSerializedNode(); } @@ -413,60 +413,6 @@ public class Node implements me.lucko.luckperms.api.Node { return serializedNode; } - private List calculateShorthand() { - if (!Patterns.SHORTHAND_NODE.matcher(getPermission()).find()) { - return ImmutableList.of(); - } - - if (!getPermission().contains(".")) { - return ImmutableList.of(); - } - - Iterable parts = Splitter.on('.').split(getPermission()); - List> nodeParts = new ArrayList<>(); - - for (String s : parts) { - if ((!s.startsWith("(") || !s.endsWith(")")) || (!s.contains("|") && !s.contains("-"))) { - nodeParts.add(Collections.singleton(s)); - continue; - } - - final String bits = s.substring(1, s.length() - 1); - if (s.contains("|")) { - nodeParts.add(new HashSet<>(Splitter.on('|').splitToList(bits))); - } else { - List range = Splitter.on('-').limit(2).splitToList(bits); - if (isChar(range.get(0), range.get(1))) { - nodeParts.add(getCharRange(range.get(0).charAt(0), range.get(1).charAt(0))); - } else if (isInt(range.get(0), range.get(1))) { - nodeParts.add(IntStream.rangeClosed(Integer.parseInt(range.get(0)), Integer.parseInt(range.get(1))).boxed() - .map(i -> "" + i) - .collect(Collectors.toSet()) - ); - } else { - // Fallback - nodeParts.add(Collections.singleton(s)); - } - } - } - - Set nodes = new HashSet<>(); - for (Set set : nodeParts) { - final Set newNodes = new HashSet<>(); - if (nodes.isEmpty()) { - newNodes.addAll(set); - } else { - nodes.forEach(str -> newNodes.addAll(set.stream() - .map(add -> str + "." + add) - .collect(Collectors.toList())) - ); - } - nodes = newNodes; - } - - return ImmutableList.copyOf(nodes); - } - private String calculateSerializedNode() { StringBuilder builder = new StringBuilder(); diff --git a/common/src/main/java/me/lucko/luckperms/common/utils/ShorthandParser.java b/common/src/main/java/me/lucko/luckperms/common/utils/ShorthandParser.java new file mode 100644 index 000000000..b35d38ed6 --- /dev/null +++ b/common/src/main/java/me/lucko/luckperms/common/utils/ShorthandParser.java @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2016 Lucko (Luck) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.lucko.luckperms.common.utils; + +import com.google.common.base.Splitter; +import com.google.common.collect.ImmutableList; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +public class ShorthandParser { + private static final List>> PARSERS = ImmutableList.>>builder() + .add(new ListParser()) + .add(new CharacterRangeParser()) + .add(new NumericRangeParser()) + .build(); + + public static Set parseShorthand(String s) { + Set results = new HashSet<>(); + results.add(s); + + while (true) { + Set working = new HashSet<>(); + int beforeSize = results.size(); + + for (String str : results) { + Set ret = captureResults(str); + if (ret != null) { + working.addAll(ret); + } else { + working.add(str); + } + } + + if (working.size() != beforeSize) { + results = working; + continue; + } + + break; + } + + results.remove(s); + return results; + } + + private static Set captureResults(String s) { + s = s.replace('(', '{').replace(')', '}'); + + int openingIndex = s.indexOf('{'); + if (openingIndex == -1) { + return null; + } + + int closingIndex = s.indexOf('}'); + if (closingIndex < openingIndex) { + return null; + } + + String before = s.substring(0, openingIndex); + String after = s.substring(closingIndex + 1); + String between = s.substring(openingIndex + 1, closingIndex); + + Set results = new HashSet<>(); + + for (Function> parser : PARSERS) { + List res = parser.apply(between); + if (res != null) { + for (String r : res) { + results.add(before + r + after); + } + } + } + + return results; + } + + private static class ListParser implements Function> { + + @Override + public List apply(String s) { + if (s.contains("|")) { + s = s.replace('|', ','); + } + + if (!s.contains(",")) { + return null; + } + + return Splitter.on(',').splitToList(s); + } + } + + private static class NumericRangeParser implements Function> { + + @Override + public List apply(String s) { + int index = s.indexOf("-"); + if (index == -1) { + return null; + } + + String before = s.substring(0, index); + String after = s.substring(index + 1); + + if (isInt(before) && isInt(after)) { + return IntStream.rangeClosed(Integer.parseInt(before), Integer.parseInt(after)).boxed().map(i -> "" + i).collect(Collectors.toList()); + } + + return null; + } + + private static boolean isInt(String a) { + try { + Integer.parseInt(a); + return true; + } catch (NumberFormatException e) { + return false; + } + } + } + + private static class CharacterRangeParser implements Function> { + + @Override + public List apply(String s) { + int index = s.indexOf("-"); + if (index == -1) { + return null; + } + + String before = s.substring(0, s.indexOf("-")); + String after = s.substring(s.indexOf("-") + 1); + + if (before.length() == 1 && after.length() == 1) { + return getCharRange(before.charAt(0), after.charAt(0)); + } + + return null; + } + + 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; + } + } + +}