Compare commits

...
Author SHA1 Message Date
Luck 3006e58209 Highest-n/lowest-n meta stack elements 2022-01-02 23:59:53 +00:00
4 changed files with 253 additions and 40 deletions

No files matched your search

@@ -26,14 +26,13 @@
package me.lucko.luckperms.common.cacheddata.type;
import me.lucko.luckperms.common.cacheddata.result.StringResult;
import me.lucko.luckperms.common.metastacking.ElementAccumulator;
import net.luckperms.api.metastacking.MetaStackDefinition;
import net.luckperms.api.metastacking.MetaStackElement;
import net.luckperms.api.node.ChatMetaType;
import net.luckperms.api.node.types.ChatMetaNode;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
@@ -43,7 +42,7 @@ import java.util.stream.Collectors;
public class MetaStackAccumulator<N extends ChatMetaNode<N, ?>> {
private final MetaStackDefinition definition;
private final List<Entry<N>> entries;
private final List<ElementAccumulator<N>> entries;
public MetaStackAccumulator(MetaStackDefinition definition, ChatMetaType targetType) {
this.definition = definition;
@@ -51,27 +50,27 @@ public class MetaStackAccumulator<N extends ChatMetaNode<N, ?>> {
List<MetaStackElement> elements = definition.getElements();
this.entries = new ArrayList<>(elements.size());
for (MetaStackElement element : elements) {
this.entries.add(new Entry<>(element, targetType));
this.entries.add(ElementAccumulator.create(element, targetType));
}
}
public void offer(N node) {
for (Entry<N> entry : this.entries) {
for (ElementAccumulator<N> entry : this.entries) {
entry.offer(node);
}
}
public List<N> getElements() {
return this.entries.stream()
.map(Entry::getNode)
.map(ElementAccumulator::result)
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
public String toFormattedString() {
List<String> elements = new LinkedList<>();
for (Entry<N> entry : this.entries) {
N node = entry.getNode();
for (ElementAccumulator<N> entry : this.entries) {
N node = entry.result();
if (node != null) {
elements.add(node.getMetaValue());
}
@@ -134,27 +133,4 @@ public class MetaStackAccumulator<N extends ChatMetaNode<N, ?>> {
}
}
private static final class Entry<N extends ChatMetaNode<?, ?>> {
private final MetaStackElement element;
private final ChatMetaType type;
private @Nullable N current = null;
Entry(MetaStackElement element, ChatMetaType type) {
this.element = element;
this.type = type;
}
public N getNode() {
return this.current;
}
public boolean offer(N node) {
if (this.element.shouldAccumulate(this.type, node, this.current)) {
this.current = node;
return true;
}
return false;
}
}
}
@@ -0,0 +1,56 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* 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.metastacking;
import net.luckperms.api.metastacking.MetaStackElement;
import net.luckperms.api.node.ChatMetaType;
import net.luckperms.api.node.types.ChatMetaNode;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* Special extension of {@link MetaStackElement} that provides its own {@link ElementAccumulator}
* instead of using the {@link #shouldAccumulate(ChatMetaType, ChatMetaNode, ChatMetaNode)} method.
*/
public interface DynamicMetaStackElement extends MetaStackElement {
@Deprecated
@Override
default boolean shouldAccumulate(@NonNull ChatMetaType type, @NonNull ChatMetaNode<?, ?> node, @Nullable ChatMetaNode<?, ?> current) {
throw new UnsupportedOperationException("DynamicMetaStackElements do not support this method.");
}
/**
* Creates a new {@link ElementAccumulator} for this element.
*
* @param type the type
* @param <N> the node type
* @return a new element accumulator
*/
<N extends ChatMetaNode<?, ?>> ElementAccumulator<N> newAccumulator(ChatMetaType type);
}
@@ -0,0 +1,99 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* 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.metastacking;
import net.luckperms.api.metastacking.MetaStackElement;
import net.luckperms.api.node.ChatMetaType;
import net.luckperms.api.node.types.ChatMetaNode;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* Accumulates {@link ChatMetaNode}s for a {@link MetaStackElement}
* to provide the stack resolution.
*
* @param <N> the node type
*/
public interface ElementAccumulator<N extends ChatMetaNode<?, ?>> {
/**
* Creates a {@link ElementAccumulator} for the given {@link MetaStackElement}.
*
* @param element the element
* @param type the type
* @param <N> the node type
* @return the new accumulator
*/
static <N extends ChatMetaNode<?, ?>> ElementAccumulator<N> create(MetaStackElement element, ChatMetaType type) {
// if the element is dynamic, use its provided impl
if (element instanceof DynamicMetaStackElement) {
return ((DynamicMetaStackElement) element).newAccumulator(type);
}
// otherwise, just use the standard accumulator
return new Standard<>(element, type);
}
/**
* Offers a node to the accumulator.
*
* @param node the node
*/
void offer(@NonNull N node);
/**
* Exports the final result from the accumulator.
*
* @return the result node
*/
@Nullable N result();
final class Standard<N extends ChatMetaNode<?, ?>> implements ElementAccumulator<N> {
private final MetaStackElement element;
private final ChatMetaType type;
private @Nullable N current = null;
Standard(MetaStackElement element, ChatMetaType type) {
this.element = element;
this.type = type;
}
@Override
public void offer(@NonNull N node) {
if (this.element.shouldAccumulate(this.type, node, this.current)) {
this.current = node;
}
}
@Override
public @Nullable N result() {
return this.current;
}
}
}
@@ -38,9 +38,13 @@ import net.luckperms.api.node.types.ChatMetaNode;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Contains the standard {@link MetaStackElement}s provided by LuckPerms.
@@ -48,6 +52,9 @@ import java.util.Objects;
public final class StandardStackElements {
private StandardStackElements() {}
private static final Pattern HIGHEST_N_PATTERN = Pattern.compile("highest\\[([1-9]\\d*)]");
private static final Pattern LOWEST_N_PATTERN = Pattern.compile("lowest\\[([1-9]\\d*)]");
public static MetaStackElement parseFromString(LuckPermsPlugin plugin, String s) {
s = s.toLowerCase(Locale.ROOT);
@@ -61,25 +68,35 @@ public final class StandardStackElements {
// dynamic
String p;
if ((p = parseParam(s, "highest_on_track_")) != null) return highestFromGroupOnTrack(plugin, p);
if ((p = parseParam(s, "lowest_on_track_")) != null) return lowestFromGroupOnTrack(plugin, p);
if ((p = parseParam(s, "highest_not_on_track_")) != null) return highestNotFromGroupOnTrack(plugin, p);
if ((p = parseParam(s, "lowest_not_on_track_")) != null) return lowestNotFromGroupOnTrack(plugin, p);
if ((p = parseParam(s, "highest_from_group_")) != null) return highestFromGroup(p);
if ((p = parseParam(s, "lowest_from_group_")) != null) return lowestFromGroup(p);
if ((p = parseParam(s, "highest_not_from_group_")) != null) return highestNotFromGroup(p);
if ((p = parseParam(s, "lowest_not_from_group_")) != null) return lowestNotFromGroup(p);
if ((p = parseSuffixParam(s, "highest_on_track_")) != null) return highestFromGroupOnTrack(plugin, p);
if ((p = parseSuffixParam(s, "lowest_on_track_")) != null) return lowestFromGroupOnTrack(plugin, p);
if ((p = parseSuffixParam(s, "highest_not_on_track_")) != null) return highestNotFromGroupOnTrack(plugin, p);
if ((p = parseSuffixParam(s, "lowest_not_on_track_")) != null) return lowestNotFromGroupOnTrack(plugin, p);
if ((p = parseSuffixParam(s, "highest_from_group_")) != null) return highestFromGroup(p);
if ((p = parseSuffixParam(s, "lowest_from_group_")) != null) return lowestFromGroup(p);
if ((p = parseSuffixParam(s, "highest_not_from_group_")) != null) return highestNotFromGroup(p);
if ((p = parseSuffixParam(s, "lowest_not_from_group_")) != null) return lowestNotFromGroup(p);
if ((p = parseRegexParam(s, HIGHEST_N_PATTERN)) != null) return highestN(p);
if ((p = parseRegexParam(s, LOWEST_N_PATTERN)) != null) return lowestN(p);
return null;
}
private static String parseParam(String s, String prefix) {
private static String parseSuffixParam(String s, String prefix) {
if (s.startsWith(prefix) && s.length() > prefix.length()) {
return s.substring(prefix.length());
}
return null;
}
private static String parseRegexParam(String s, Pattern pattern) {
Matcher matcher = pattern.matcher(s);
if (!matcher.matches()) {
return null;
}
return matcher.group(1);
}
public static List<MetaStackElement> parseList(LuckPermsPlugin plugin, List<String> strings) {
return strings.stream()
.map(s -> {
@@ -157,6 +174,15 @@ public final class StandardStackElements {
.build();
}
public static MetaStackElement highestN(String nString) {
int n = Integer.parseInt(nString);
return FluentMetaStackElement.builder("HighestN")
.param("n", Integer.toString(n))
.with(TYPE_CHECK)
.with(CompareTakeNCheck.highest(n))
.build();
}
public static final MetaStackElement LOWEST = FluentMetaStackElement.builder("LowestPriority")
.with(TYPE_CHECK)
.with(LOWEST_CHECK)
@@ -210,6 +236,62 @@ public final class StandardStackElements {
.build();
}
public static MetaStackElement lowestN(String nString) {
int n = Integer.parseInt(nString);
return FluentMetaStackElement.builder("LowestN")
.param("n", Integer.toString(n))
.with(TYPE_CHECK)
.with(CompareTakeNCheck.lowest(n))
.build();
}
private static final class CompareTakeNCheck implements DynamicMetaStackElement {
private static final Comparator<? super ChatMetaNode<?, ?>> LOWEST = Comparator.comparingInt(ChatMetaNode::getPriority);
private static final Comparator<? super ChatMetaNode<?, ?>> HIGHEST = LOWEST.reversed();
static CompareTakeNCheck lowest(int n) {
return new CompareTakeNCheck(n, LOWEST);
}
static CompareTakeNCheck highest(int n) {
return new CompareTakeNCheck(n, HIGHEST);
}
private final int n;
private final Comparator<? super ChatMetaNode<?, ?>> comparator;
CompareTakeNCheck(int n, Comparator<? super ChatMetaNode<?, ?>> comparator) {
this.n = n - 1; // n is 1-indexed, convert it to zero-indexed
this.comparator = comparator;
}
@Override
public <N extends ChatMetaNode<?, ?>> ElementAccumulator<N> newAccumulator(ChatMetaType type) {
return new Accumulator<>();
}
private final class Accumulator<N extends ChatMetaNode<?, ?>> implements ElementAccumulator<N> {
private final List<N> nodes = new ArrayList<>();
@Override
public void offer(@NonNull N node) {
this.nodes.add(node);
}
@Override
public @Nullable N result() {
// sort by priority
this.nodes.sort(CompareTakeNCheck.this.comparator);
// return nodes[n]
if (this.nodes.size() <= CompareTakeNCheck.this.n) {
return null;
}
return this.nodes.get(CompareTakeNCheck.this.n);
}
}
}
private static final class FromGroupOnTrackCheck implements MetaStackElement {
private final LuckPermsPlugin plugin;
private final String trackName;