diff --git a/api/src/main/java/me/lucko/luckperms/api/DataMutateResult.java b/api/src/main/java/me/lucko/luckperms/api/DataMutateResult.java index 0d9a3aa3..8f738d72 100644 --- a/api/src/main/java/me/lucko/luckperms/api/DataMutateResult.java +++ b/api/src/main/java/me/lucko/luckperms/api/DataMutateResult.java @@ -26,9 +26,11 @@ package me.lucko.luckperms.api; /** - * Represents the result of a mutation call. + * Represents the result of a data mutation call on a LuckPerms object. + * + *

Usually as the result to a call on a {@link PermissionHolder} or {@link Track}.

*/ -public enum DataMutateResult { +public enum DataMutateResult implements MutateResult { /** * Indicates the mutation was a success @@ -50,39 +52,14 @@ public enum DataMutateResult { */ FAIL(false); - private final boolean value; + private final boolean success; - DataMutateResult(boolean value) { - this.value = value; + DataMutateResult(boolean success) { + this.success = success; } - /** - * Gets a boolean representation of the result. - * - * @return a boolean representation - */ - public boolean asBoolean() { - return this.value; - } - - /** - * Gets if the result indicates a success - * - * @return if the result indicates a success - * @since 3.4 - */ + @Override public boolean wasSuccess() { - return this.value; + return this.success; } - - /** - * Gets if the result indicates a failure - * - * @return if the result indicates a failure - * @since 3.4 - */ - public boolean wasFailure() { - return !this.value; - } - } diff --git a/api/src/main/java/me/lucko/luckperms/api/DemotionResult.java b/api/src/main/java/me/lucko/luckperms/api/DemotionResult.java new file mode 100644 index 00000000..02052925 --- /dev/null +++ b/api/src/main/java/me/lucko/luckperms/api/DemotionResult.java @@ -0,0 +1,130 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * 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.api; + +import java.util.Optional; + +import javax.annotation.Nonnull; + +/** + * Encapsulates the result of {@link User}s demotion along a {@link Track}. + * + * @since 4.2 + */ +public interface DemotionResult extends MutateResult { + + /** + * Gets the status of the result. + * + * @return the status + */ + @Nonnull + Status getStatus(); + + @Override + default boolean wasSuccess() { + return getStatus().wasSuccess(); + } + + /** + * Gets the name of the group the user was demoted from, if applicable. + * + *

Will only be present for results with a {@link #getStatus() status} of + * {@link Status#SUCCESS} or {@link Status#REMOVED_FROM_FIRST_GROUP}.

+ * + *

The value will also be set for results with the {@link Status#MALFORMED_TRACK} status, + * with this value marking the group which no longer exists.

+ * + * @return the group the user was demoted from. + */ + @Nonnull + Optional getGroupFrom(); + + /** + * Gets the name of the group the user was demoted from, if applicable. + * + *

Will only be present for results with a {@link #getStatus() status} of + * {@link Status#SUCCESS}.

+ * + * @return the group the user was demoted to. + */ + @Nonnull + Optional getGroupTo(); + + /** + * The result status + */ + enum Status implements MutateResult { + + /** + * Indicates that the user was demoted normally. + */ + SUCCESS(true), + + /** + * Indicates that the user was removed from the first group in the track. + * + *

This usually occurs when the user is currently on the first group, and was demoted + * "over the start" of the track.

+ */ + REMOVED_FROM_FIRST_GROUP(true), + + /** + * Indicates that the previous group in the track no longer exists. + */ + MALFORMED_TRACK(false), + + /** + * Indicates that the user isn't a member of any of the groups on this track. + */ + NOT_ON_TRACK(false), + + /** + * Indicates that the implementation was unable to determine the users current position on + * this track. + * + *

This usually occurs when the user is on more than one group on the track.

+ */ + AMBIGUOUS_CALL(false), + + /** + * An undefined failure occurred. + */ + UNDEFINED_FAILURE(false); + + private final boolean success; + + Status(boolean success) { + this.success = success; + } + + @Override + public boolean wasSuccess() { + return this.success; + } + } + +} diff --git a/api/src/main/java/me/lucko/luckperms/api/MutateResult.java b/api/src/main/java/me/lucko/luckperms/api/MutateResult.java new file mode 100644 index 00000000..e2ca32e1 --- /dev/null +++ b/api/src/main/java/me/lucko/luckperms/api/MutateResult.java @@ -0,0 +1,74 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * 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.api; + +/** + * Represents the result to a "mutation" on an object. + * + * @since 4.2 + */ +public interface MutateResult { + + /** + * Instance of {@link MutateResult} which always reports success. + */ + MutateResult GENERIC_SUCCESS = () -> true; + + /** + * Instance of {@link MutateResult} which always reports failure. + */ + MutateResult GENERIC_FAILURE = () -> false; + + /** + * Gets if the operation which produced this result completed successfully. + * + * @return if the result indicates a success + */ + boolean wasSuccess(); + + /** + * Gets if the operation which produced this result failed. + * + * @return if the result indicates a failure + */ + default boolean wasFailure() { + return !wasSuccess(); + } + + /** + * Gets a boolean representation of the result. + * + *

A value of true marks that the operation {@link #wasSuccess() was a success} + * and a value of false marks that the operation + * {@link #wasFailure() was a failure}.

+ * + * @return a boolean representation + */ + default boolean asBoolean() { + return wasSuccess(); + } + +} diff --git a/api/src/main/java/me/lucko/luckperms/api/PromotionResult.java b/api/src/main/java/me/lucko/luckperms/api/PromotionResult.java new file mode 100644 index 00000000..b1bcf819 --- /dev/null +++ b/api/src/main/java/me/lucko/luckperms/api/PromotionResult.java @@ -0,0 +1,130 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * 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.api; + +import java.util.Optional; + +import javax.annotation.Nonnull; + +/** + * Encapsulates the result of {@link User}s promotion along a {@link Track}. + * + * @since 4.2 + */ +public interface PromotionResult extends MutateResult { + + /** + * Gets the status of the result. + * + * @return the status + */ + @Nonnull + Status getStatus(); + + @Override + default boolean wasSuccess() { + return getStatus().wasSuccess(); + } + + /** + * Gets the name of the group the user was promoted from, if applicable. + * + *

Will only be present for results with a {@link #getStatus() status} of + * {@link Status#SUCCESS}.

+ * + * @return the group the user was promoted from. + */ + @Nonnull + Optional getGroupFrom(); + + /** + * Gets the name of the group the user was promoted from, if applicable. + * + *

Will only be present for results with a {@link #getStatus() status} of + * {@link Status#SUCCESS} or {@link Status#ADDED_TO_FIRST_GROUP}.

+ * + *

The value will also be set for results with the {@link Status#MALFORMED_TRACK} status, + * with this value marking the group which no longer exists.

+ * + * @return the group the user was promoted to. + */ + @Nonnull + Optional getGroupTo(); + + /** + * The result status + */ + enum Status implements MutateResult { + + /** + * Indicates that the user was promoted normally. + */ + SUCCESS(true), + + /** + * Indicates that the user was added to the first group in the track. + * + *

This usually occurs when the user isn't already on any of the groups in the track.

+ */ + ADDED_TO_FIRST_GROUP(true), + + /** + * Indicates that the next group in the track no longer exists. + */ + MALFORMED_TRACK(false), + + /** + * Indicates that the user is already a member of the group at the end of the track, + * and as such cannot be promoted any further. + */ + END_OF_TRACK(false), + + /** + * Indicates that the implementation was unable to determine the users current position on + * this track. + * + *

This usually occurs when the user is on more than one group on the track.

+ */ + AMBIGUOUS_CALL(false), + + /** + * An undefined failure occurred. + */ + UNDEFINED_FAILURE(false); + + private final boolean success; + + Status(boolean success) { + this.success = success; + } + + @Override + public boolean wasSuccess() { + return this.success; + } + } + +} diff --git a/api/src/main/java/me/lucko/luckperms/api/Track.java b/api/src/main/java/me/lucko/luckperms/api/Track.java index 62f50643..be92aafc 100644 --- a/api/src/main/java/me/lucko/luckperms/api/Track.java +++ b/api/src/main/java/me/lucko/luckperms/api/Track.java @@ -25,6 +25,8 @@ package me.lucko.luckperms.api; +import me.lucko.luckperms.api.context.ContextSet; + import java.util.List; import javax.annotation.Nonnull; @@ -88,6 +90,28 @@ public interface Track { @Nullable String getPrevious(@Nonnull Group current); + /** + * Promotes the given user along this track. + * + * @param user the user to promote + * @param contextSet the contexts to promote the user in + * @return the result of the action + * @since 4.2 + */ + @Nonnull + PromotionResult promote(@Nonnull User user, @Nonnull ContextSet contextSet); + + /** + * Demotes the given user along this track. + * + * @param user the user to demote + * @param contextSet the contexts to demote the user in + * @return the result of the action + * @since 4.2 + */ + @Nonnull + DemotionResult demote(@Nonnull User user, @Nonnull ContextSet contextSet); + /** * Appends a group to the end of this track * diff --git a/common/src/main/java/me/lucko/luckperms/common/api/DemotionResults.java b/common/src/main/java/me/lucko/luckperms/common/api/DemotionResults.java new file mode 100644 index 00000000..95420718 --- /dev/null +++ b/common/src/main/java/me/lucko/luckperms/common/api/DemotionResults.java @@ -0,0 +1,124 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * 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.api; + +import me.lucko.luckperms.api.DemotionResult; + +import java.util.Objects; +import java.util.Optional; + +import javax.annotation.Nonnull; + +/** + * Utility class for creating instances of {@link DemotionResult}. + */ +public final class DemotionResults { + + public static DemotionResult success(String groupFrom, String groupTo) { + return new Impl(DemotionResult.Status.SUCCESS, groupFrom, groupTo); + } + + public static DemotionResult removedFromFirst(String groupFrom) { + return new Impl(DemotionResult.Status.REMOVED_FROM_FIRST_GROUP, groupFrom, null); + } + + public static DemotionResult malformedTrack(String groupTo) { + return new Impl(DemotionResult.Status.MALFORMED_TRACK, null, groupTo); + } + + public static DemotionResult notOnTrack() { + return new Impl(DemotionResult.Status.NOT_ON_TRACK); + } + + public static DemotionResult ambiguousCall() { + return new Impl(DemotionResult.Status.AMBIGUOUS_CALL); + } + + public static DemotionResult undefinedFailure() { + return new Impl(DemotionResult.Status.UNDEFINED_FAILURE); + } + + private static final class Impl implements DemotionResult { + private final Status status; + private final String groupFrom; + private final String groupTo; + + private Impl(Status status, String groupFrom, String groupTo) { + this.status = status; + this.groupFrom = groupFrom; + this.groupTo = groupTo; + } + + private Impl(Status status) { + this(status, null, null); + } + + @Nonnull + @Override + public Status getStatus() { + return this.status; + } + + @Nonnull + @Override + public Optional getGroupFrom() { + return Optional.ofNullable(this.groupFrom); + } + + @Nonnull + @Override + public Optional getGroupTo() { + return Optional.ofNullable(this.groupTo); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Impl that = (Impl) o; + return this.status == that.status && + Objects.equals(this.groupFrom, that.groupFrom) && + Objects.equals(this.groupTo, that.groupTo); + } + + @Override + public int hashCode() { + return Objects.hash(this.status, this.groupFrom, this.groupTo); + } + + @Override + public String toString() { + return "DemotionResult(" + + "status=" + this.status + ", " + + "groupFrom='" + this.groupFrom + "', " + + "groupTo='" + this.groupTo + "')"; + } + } + + + private DemotionResults() {} + +} diff --git a/common/src/main/java/me/lucko/luckperms/common/api/PromotionResults.java b/common/src/main/java/me/lucko/luckperms/common/api/PromotionResults.java new file mode 100644 index 00000000..39474dda --- /dev/null +++ b/common/src/main/java/me/lucko/luckperms/common/api/PromotionResults.java @@ -0,0 +1,124 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * 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.api; + +import me.lucko.luckperms.api.PromotionResult; + +import java.util.Objects; +import java.util.Optional; + +import javax.annotation.Nonnull; + +/** + * Utility class for creating instances of {@link PromotionResult}. + */ +public final class PromotionResults { + + public static PromotionResult success(String groupFrom, String groupTo) { + return new Impl(PromotionResult.Status.SUCCESS, groupFrom, groupTo); + } + + public static PromotionResult addedToFirst(String groupTo) { + return new Impl(PromotionResult.Status.ADDED_TO_FIRST_GROUP, null, groupTo); + } + + public static PromotionResult malformedTrack(String groupTo) { + return new Impl(PromotionResult.Status.MALFORMED_TRACK, null, groupTo); + } + + public static PromotionResult endOfTrack() { + return new Impl(PromotionResult.Status.END_OF_TRACK); + } + + public static PromotionResult ambiguousCall() { + return new Impl(PromotionResult.Status.AMBIGUOUS_CALL); + } + + public static PromotionResult undefinedFailure() { + return new Impl(PromotionResult.Status.UNDEFINED_FAILURE); + } + + private static final class Impl implements PromotionResult { + private final Status status; + private final String groupFrom; + private final String groupTo; + + private Impl(Status status, String groupFrom, String groupTo) { + this.status = status; + this.groupFrom = groupFrom; + this.groupTo = groupTo; + } + + private Impl(Status status) { + this(status, null, null); + } + + @Nonnull + @Override + public Status getStatus() { + return this.status; + } + + @Nonnull + @Override + public Optional getGroupFrom() { + return Optional.ofNullable(this.groupFrom); + } + + @Nonnull + @Override + public Optional getGroupTo() { + return Optional.ofNullable(this.groupTo); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Impl that = (Impl) o; + return this.status == that.status && + Objects.equals(this.groupFrom, that.groupFrom) && + Objects.equals(this.groupTo, that.groupTo); + } + + @Override + public int hashCode() { + return Objects.hash(this.status, this.groupFrom, this.groupTo); + } + + @Override + public String toString() { + return "PromotionResult(" + + "status=" + this.status + ", " + + "groupFrom='" + this.groupFrom + "', " + + "groupTo='" + this.groupTo + "')"; + } + } + + + private PromotionResults() {} + +} diff --git a/common/src/main/java/me/lucko/luckperms/common/api/delegates/model/ApiTrack.java b/common/src/main/java/me/lucko/luckperms/common/api/delegates/model/ApiTrack.java index 7d238e20..0e49a17c 100644 --- a/common/src/main/java/me/lucko/luckperms/common/api/delegates/model/ApiTrack.java +++ b/common/src/main/java/me/lucko/luckperms/common/api/delegates/model/ApiTrack.java @@ -28,8 +28,13 @@ package me.lucko.luckperms.common.api.delegates.model; import com.google.common.base.Preconditions; import me.lucko.luckperms.api.DataMutateResult; +import me.lucko.luckperms.api.DemotionResult; import me.lucko.luckperms.api.Group; +import me.lucko.luckperms.api.PromotionResult; +import me.lucko.luckperms.api.User; +import me.lucko.luckperms.api.context.ContextSet; import me.lucko.luckperms.common.model.Track; +import me.lucko.luckperms.common.utils.Predicates; import java.util.List; import java.util.Objects; @@ -90,6 +95,18 @@ public final class ApiTrack implements me.lucko.luckperms.api.Track { } } + @Nonnull + @Override + public PromotionResult promote(@Nonnull User user, @Nonnull ContextSet contextSet) { + return this.handle.promote(ApiUser.cast(user), contextSet, Predicates.alwaysTrue(), null); + } + + @Nonnull + @Override + public DemotionResult demote(@Nonnull User user, @Nonnull ContextSet contextSet) { + return this.handle.demote(ApiUser.cast(user), contextSet, Predicates.alwaysTrue(), null); + } + @Override public DataMutateResult appendGroup(@Nonnull Group group) { Objects.requireNonNull(group, "group"); diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/user/UserDemote.java b/common/src/main/java/me/lucko/luckperms/common/commands/user/UserDemote.java index f4c00b31..159ca876 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/user/UserDemote.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/user/UserDemote.java @@ -25,9 +25,7 @@ package me.lucko.luckperms.common.commands.user; -import com.google.common.collect.Iterables; - -import me.lucko.luckperms.api.Node; +import me.lucko.luckperms.api.DemotionResult; import me.lucko.luckperms.api.context.MutableContextSet; import me.lucko.luckperms.common.actionlog.ExtendedLogEntry; import me.lucko.luckperms.common.command.CommandResult; @@ -42,18 +40,14 @@ import me.lucko.luckperms.common.command.utils.TabCompletions; import me.lucko.luckperms.common.locale.LocaleManager; import me.lucko.luckperms.common.locale.command.CommandSpec; import me.lucko.luckperms.common.locale.message.Message; -import me.lucko.luckperms.common.model.Group; import me.lucko.luckperms.common.model.Track; import me.lucko.luckperms.common.model.User; -import me.lucko.luckperms.common.node.NodeFactory; import me.lucko.luckperms.common.plugin.LuckPermsPlugin; import me.lucko.luckperms.common.sender.Sender; import me.lucko.luckperms.common.storage.DataConstraints; import me.lucko.luckperms.common.utils.Predicates; import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; public class UserDemote extends SubCommand { public UserDemote(LocaleManager locale) { @@ -91,78 +85,52 @@ public class UserDemote extends SubCommand { return CommandResult.NO_PERMISSION; } - // Load applicable groups - Set nodes = user.getEnduringNodes().values().stream() - .filter(Node::isGroupNode) - .filter(Node::getValuePrimitive) - .filter(node -> node.getFullContexts().makeImmutable().equals(context.makeImmutable())) - .collect(Collectors.toSet()); + DemotionResult result = track.demote(user, context, s -> !ArgumentPermissions.checkArguments(plugin, sender, getPermission().get(), track.getName(), s), sender); + switch (result.getStatus()) { + case NOT_ON_TRACK: + Message.USER_TRACK_ERROR_NOT_CONTAIN_GROUP.send(sender, user.getFriendlyName(), track.getName()); + return CommandResult.FAILURE; + case AMBIGUOUS_CALL: + Message.TRACK_AMBIGUOUS_CALL.send(sender, user.getFriendlyName()); + return CommandResult.FAILURE; + case UNDEFINED_FAILURE: + Message.COMMAND_NO_PERMISSION.send(sender); + return CommandResult.NO_PERMISSION; + case MALFORMED_TRACK: + Message.USER_DEMOTE_ERROR_MALFORMED.send(sender, result.getGroupTo().get()); + return CommandResult.LOADING_ERROR; - nodes.removeIf(g -> !track.containsGroup(g.getGroupName())); + case REMOVED_FROM_FIRST_GROUP: { + Message.USER_DEMOTE_ENDOFTRACK.send(sender, track.getName(), user.getFriendlyName(), result.getGroupFrom().get()); - if (nodes.isEmpty()) { - Message.USER_TRACK_ERROR_NOT_CONTAIN_GROUP.send(sender, user.getFriendlyName(), track.getName()); - return CommandResult.FAILURE; + ExtendedLogEntry.build().actor(sender).acted(user) + .action("demote", track.getName(), context) + .build().submit(plugin, sender); + + StorageAssistant.save(user, sender, plugin); + return CommandResult.SUCCESS; + } + + case SUCCESS: { + String groupFrom = result.getGroupFrom().get(); + String groupTo = result.getGroupTo().get(); + + Message.USER_DEMOTE_SUCCESS.send(sender, user.getFriendlyName(), track.getName(), groupFrom, groupTo, MessageUtils.contextSetToString(context)); + if (!silent) { + Message.EMPTY.send(sender, MessageUtils.listToArrowSep(track.getGroups(), groupTo, groupFrom, true)); + } + + ExtendedLogEntry.build().actor(sender).acted(user) + .action("demote", track.getName(), context) + .build().submit(plugin, sender); + + StorageAssistant.save(user, sender, plugin); + return CommandResult.SUCCESS; + } + + default: + throw new AssertionError("Unknown status: " + result.getStatus()); } - - if (nodes.size() != 1) { - Message.TRACK_AMBIGUOUS_CALL.send(sender, user.getFriendlyName()); - return CommandResult.FAILURE; - } - - final Node oldNode = Iterables.getFirst(nodes, null); - final String old = oldNode.getGroupName(); - final String previous; - try { - previous = track.getPrevious(old); - } catch (IllegalArgumentException e) { - Message.TRACK_DOES_NOT_CONTAIN.send(sender, track.getName(), old); - return CommandResult.STATE_ERROR; - } - - if (ArgumentPermissions.checkArguments(plugin, sender, getPermission().get(), track.getName(), oldNode.getGroupName())) { - Message.COMMAND_NO_PERMISSION.send(sender); - return CommandResult.NO_PERMISSION; - } - - if (previous == null) { - user.unsetPermission(oldNode); - Message.USER_DEMOTE_ENDOFTRACK.send(sender, track.getName(), user.getFriendlyName(), old); - - ExtendedLogEntry.build().actor(sender).acted(user) - .action("demote", track.getName(), context) - .build().submit(plugin, sender); - - StorageAssistant.save(user, sender, plugin); - plugin.getEventFactory().handleUserDemote(user, track, old, null, sender); - return CommandResult.SUCCESS; - } - - Group previousGroup = plugin.getStorage().loadGroup(previous).join().orElse(null); - if (previousGroup == null) { - Message.USER_DEMOTE_ERROR_MALFORMED.send(sender, previous); - return CommandResult.LOADING_ERROR; - } - - user.unsetPermission(oldNode); - user.setPermission(NodeFactory.buildGroupNode(previousGroup.getName()).withExtraContext(context).build()); - - if (context.isEmpty() && user.getPrimaryGroup().getStoredValue().orElse(NodeFactory.DEFAULT_GROUP_NAME).equalsIgnoreCase(old)) { - user.getPrimaryGroup().setStoredValue(previousGroup.getName()); - } - - Message.USER_DEMOTE_SUCCESS.send(sender, user.getFriendlyName(), track.getName(), old, previousGroup.getFriendlyName(), MessageUtils.contextSetToString(context)); - if (!silent) { - Message.EMPTY.send(sender, MessageUtils.listToArrowSep(track.getGroups(), previousGroup.getName(), old, true)); - } - - ExtendedLogEntry.build().actor(sender).acted(user) - .action("demote", track.getName(), context) - .build().submit(plugin, sender); - - StorageAssistant.save(user, sender, plugin); - plugin.getEventFactory().handleUserDemote(user, track, old, previousGroup.getName(), sender); - return CommandResult.SUCCESS; } @Override diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/user/UserPromote.java b/common/src/main/java/me/lucko/luckperms/common/commands/user/UserPromote.java index 541c9e77..76a86297 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/user/UserPromote.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/user/UserPromote.java @@ -25,7 +25,7 @@ package me.lucko.luckperms.common.commands.user; -import me.lucko.luckperms.api.Node; +import me.lucko.luckperms.api.PromotionResult; import me.lucko.luckperms.api.context.MutableContextSet; import me.lucko.luckperms.common.actionlog.ExtendedLogEntry; import me.lucko.luckperms.common.command.CommandResult; @@ -40,18 +40,14 @@ import me.lucko.luckperms.common.command.utils.TabCompletions; import me.lucko.luckperms.common.locale.LocaleManager; import me.lucko.luckperms.common.locale.command.CommandSpec; import me.lucko.luckperms.common.locale.message.Message; -import me.lucko.luckperms.common.model.Group; import me.lucko.luckperms.common.model.Track; import me.lucko.luckperms.common.model.User; -import me.lucko.luckperms.common.node.NodeFactory; import me.lucko.luckperms.common.plugin.LuckPermsPlugin; import me.lucko.luckperms.common.sender.Sender; import me.lucko.luckperms.common.storage.DataConstraints; import me.lucko.luckperms.common.utils.Predicates; import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; public class UserPromote extends SubCommand { public UserPromote(LocaleManager locale) { @@ -89,97 +85,52 @@ public class UserPromote extends SubCommand { return CommandResult.NO_PERMISSION; } - // Load applicable groups - Set nodes = user.getEnduringNodes().values().stream() - .filter(Node::isGroupNode) - .filter(Node::getValuePrimitive) - .filter(node -> node.getFullContexts().makeImmutable().equals(context.makeImmutable())) - .collect(Collectors.toSet()); - - nodes.removeIf(g -> !track.containsGroup(g.getGroupName())); - - if (nodes.isEmpty()) { - String first = track.getGroups().get(0); - - Group nextGroup = plugin.getGroupManager().getIfLoaded(first); - if (nextGroup == null) { - Message.USER_PROMOTE_ERROR_MALFORMED.send(sender, first); + PromotionResult result = track.promote(user, context, s -> !ArgumentPermissions.checkArguments(plugin, sender, getPermission().get(), track.getName(), s), sender); + switch (result.getStatus()) { + case MALFORMED_TRACK: + Message.USER_PROMOTE_ERROR_MALFORMED.send(sender, result.getGroupTo().get()); return CommandResult.LOADING_ERROR; - } - - if (ArgumentPermissions.checkArguments(plugin, sender, getPermission().get(), track.getName(), nextGroup.getName())) { + case UNDEFINED_FAILURE: Message.COMMAND_NO_PERMISSION.send(sender); return CommandResult.NO_PERMISSION; + case AMBIGUOUS_CALL: + Message.TRACK_AMBIGUOUS_CALL.send(sender, user.getFriendlyName()); + return CommandResult.FAILURE; + case END_OF_TRACK: + Message.USER_PROMOTE_ERROR_ENDOFTRACK.send(sender, track.getName(), user.getFriendlyName()); + return CommandResult.STATE_ERROR; + + case ADDED_TO_FIRST_GROUP: { + Message.USER_TRACK_ADDED_TO_FIRST.send(sender, user.getFriendlyName(), result.getGroupTo().get(), MessageUtils.contextSetToString(context)); + + ExtendedLogEntry.build().actor(sender).acted(user) + .action("promote", track.getName(), context) + .build().submit(plugin, sender); + + StorageAssistant.save(user, sender, plugin); + return CommandResult.SUCCESS; } - user.setPermission(NodeFactory.buildGroupNode(nextGroup.getId()).withExtraContext(context).build()); + case SUCCESS: { + String groupFrom = result.getGroupFrom().get(); + String groupTo = result.getGroupTo().get(); - Message.USER_TRACK_ADDED_TO_FIRST.send(sender, user.getFriendlyName(), nextGroup.getFriendlyName(), MessageUtils.contextSetToString(context)); + Message.USER_PROMOTE_SUCCESS.send(sender, user.getFriendlyName(), track.getName(), groupFrom, groupTo, MessageUtils.contextSetToString(context)); + if (!silent) { + Message.EMPTY.send(sender, MessageUtils.listToArrowSep(track.getGroups(), groupFrom, groupTo, false)); + } - ExtendedLogEntry.build().actor(sender).acted(user) - .action("promote", track.getName(), context) - .build().submit(plugin, sender); + ExtendedLogEntry.build().actor(sender).acted(user) + .action("promote", track.getName(), context) + .build().submit(plugin, sender); - StorageAssistant.save(user, sender, plugin); - plugin.getEventFactory().handleUserPromote(user, track, null, first, sender); - return CommandResult.SUCCESS; + StorageAssistant.save(user, sender, plugin); + return CommandResult.SUCCESS; + } + + default: + throw new AssertionError("Unknown status: " + result.getStatus()); } - - if (nodes.size() != 1) { - Message.TRACK_AMBIGUOUS_CALL.send(sender, user.getFriendlyName()); - return CommandResult.FAILURE; - } - - final Node oldNode = nodes.stream().findAny().get(); - final String old = oldNode.getGroupName(); - final String next; - try { - next = track.getNext(old); - } catch (IllegalArgumentException e) { - Message.TRACK_DOES_NOT_CONTAIN.send(sender, track.getName(), old); - return CommandResult.STATE_ERROR; - } - - if (next == null) { - Message.USER_PROMOTE_ERROR_ENDOFTRACK.send(sender, track.getName(), user.getFriendlyName()); - return CommandResult.STATE_ERROR; - } - - if (!plugin.getStorage().loadGroup(next).join().isPresent()) { - Message.USER_PROMOTE_ERROR_MALFORMED.send(sender, next); - return CommandResult.STATE_ERROR; - } - - Group nextGroup = plugin.getGroupManager().getIfLoaded(next); - if (nextGroup == null) { - Message.USER_PROMOTE_ERROR_MALFORMED.send(sender, next); - return CommandResult.LOADING_ERROR; - } - - if (ArgumentPermissions.checkArguments(plugin, sender, getPermission().get(), track.getName(), nextGroup.getName())) { - Message.COMMAND_NO_PERMISSION.send(sender); - return CommandResult.NO_PERMISSION; - } - - user.unsetPermission(oldNode); - user.setPermission(NodeFactory.buildGroupNode(nextGroup.getName()).withExtraContext(context).build()); - - if (context.isEmpty() && user.getPrimaryGroup().getStoredValue().orElse(NodeFactory.DEFAULT_GROUP_NAME).equalsIgnoreCase(old)) { - user.getPrimaryGroup().setStoredValue(nextGroup.getName()); - } - - Message.USER_PROMOTE_SUCCESS.send(sender, user.getFriendlyName(), track.getName(), old, nextGroup.getFriendlyName(), MessageUtils.contextSetToString(context)); - if (!silent) { - Message.EMPTY.send(sender, MessageUtils.listToArrowSep(track.getGroups(), old, nextGroup.getName(), false)); - } - - ExtendedLogEntry.build().actor(sender).acted(user) - .action("promote", track.getName(), context) - .build().submit(plugin, sender); - - StorageAssistant.save(user, sender, plugin); - plugin.getEventFactory().handleUserPromote(user, track, old, nextGroup.getName(), sender); - return CommandResult.SUCCESS; } @Override diff --git a/common/src/main/java/me/lucko/luckperms/common/event/EventFactory.java b/common/src/main/java/me/lucko/luckperms/common/event/EventFactory.java index a76cb481..539dd95b 100644 --- a/common/src/main/java/me/lucko/luckperms/common/event/EventFactory.java +++ b/common/src/main/java/me/lucko/luckperms/common/event/EventFactory.java @@ -37,6 +37,7 @@ import me.lucko.luckperms.api.event.cause.CreationCause; import me.lucko.luckperms.api.event.cause.DeletionCause; import me.lucko.luckperms.api.event.log.LogBroadcastEvent; import me.lucko.luckperms.api.event.log.LogNotifyEvent; +import me.lucko.luckperms.api.event.source.Source; import me.lucko.luckperms.common.api.delegates.model.ApiPermissionHolder; import me.lucko.luckperms.common.api.delegates.model.ApiUser; import me.lucko.luckperms.common.event.impl.EventConfigReload; @@ -73,6 +74,7 @@ import me.lucko.luckperms.common.event.impl.EventUserLoginProcess; import me.lucko.luckperms.common.event.impl.EventUserPromote; import me.lucko.luckperms.common.event.model.EntitySourceImpl; import me.lucko.luckperms.common.event.model.SenderEntity; +import me.lucko.luckperms.common.event.model.UnknownSource; import me.lucko.luckperms.common.model.Group; import me.lucko.luckperms.common.model.PermissionHolder; import me.lucko.luckperms.common.model.Track; @@ -84,6 +86,8 @@ import java.util.List; import java.util.UUID; import java.util.concurrent.atomic.AtomicBoolean; +import javax.annotation.Nullable; + public final class EventFactory { private final AbstractEventBus eventBus; @@ -265,13 +269,15 @@ public final class EventFactory { fireEvent(event); } - public void handleUserDemote(User user, Track track, String from, String to, Sender source) { - EventUserDemote event = new EventUserDemote(track.getApiDelegate(), new ApiUser(user), from, to, new EntitySourceImpl(new SenderEntity(source))); + public void handleUserDemote(User user, Track track, String from, String to, @Nullable Sender source) { + Source s = source == null ? UnknownSource.INSTANCE : new EntitySourceImpl(new SenderEntity(source)); + EventUserDemote event = new EventUserDemote(track.getApiDelegate(), new ApiUser(user), from, to, s); fireEventAsync(event); } - public void handleUserPromote(User user, Track track, String from, String to, Sender source) { - EventUserPromote event = new EventUserPromote(track.getApiDelegate(), new ApiUser(user), from, to, new EntitySourceImpl(new SenderEntity(source))); + public void handleUserPromote(User user, Track track, String from, String to, @Nullable Sender source) { + Source s = source == null ? UnknownSource.INSTANCE : new EntitySourceImpl(new SenderEntity(source)); + EventUserPromote event = new EventUserPromote(track.getApiDelegate(), new ApiUser(user), from, to, s); fireEventAsync(event); } diff --git a/common/src/main/java/me/lucko/luckperms/common/event/model/UnknownSource.java b/common/src/main/java/me/lucko/luckperms/common/event/model/UnknownSource.java index d5440f7e..e6d84bfa 100644 --- a/common/src/main/java/me/lucko/luckperms/common/event/model/UnknownSource.java +++ b/common/src/main/java/me/lucko/luckperms/common/event/model/UnknownSource.java @@ -30,7 +30,7 @@ import me.lucko.luckperms.api.event.source.Source; import javax.annotation.Nonnull; public final class UnknownSource implements Source { - private static final Source INSTANCE = new UnknownSource(); + public static final Source INSTANCE = new UnknownSource(); private UnknownSource() { diff --git a/common/src/main/java/me/lucko/luckperms/common/model/Track.java b/common/src/main/java/me/lucko/luckperms/common/model/Track.java index 395cc5f2..3c93ab71 100644 --- a/common/src/main/java/me/lucko/luckperms/common/model/Track.java +++ b/common/src/main/java/me/lucko/luckperms/common/model/Track.java @@ -28,15 +28,27 @@ package me.lucko.luckperms.common.model; import com.google.common.collect.ImmutableList; import me.lucko.luckperms.api.DataMutateResult; +import me.lucko.luckperms.api.DemotionResult; +import me.lucko.luckperms.api.Node; +import me.lucko.luckperms.api.PromotionResult; +import me.lucko.luckperms.api.context.ContextSet; +import me.lucko.luckperms.common.api.DemotionResults; +import me.lucko.luckperms.common.api.PromotionResults; import me.lucko.luckperms.common.api.delegates.model.ApiTrack; +import me.lucko.luckperms.common.node.NodeFactory; import me.lucko.luckperms.common.plugin.LuckPermsPlugin; import me.lucko.luckperms.common.references.Identifiable; +import me.lucko.luckperms.common.sender.Sender; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import javax.annotation.Nullable; public final class Track implements Identifiable { @@ -259,6 +271,119 @@ public final class Track implements Identifiable { this.plugin.getEventFactory().handleTrackClear(this, before); } + public PromotionResult promote(User user, ContextSet context, Predicate nextGroupPermissionChecker, @Nullable Sender sender) { + if (getSize() <= 1) { + throw new IllegalStateException("Track contains one or fewer groups, unable to promote"); + } + + // find all groups that are inherited by the user in the exact contexts given and applicable to this track + List nodes = user.getEnduringNodes().get(context.makeImmutable()).stream() + .filter(Node::isGroupNode) + .filter(Node::getValuePrimitive) + .filter(node -> containsGroup(node.getGroupName())) + .distinct() + .collect(Collectors.toList()); + + if (nodes.isEmpty()) { + String first = getGroups().get(0); + + Group nextGroup = this.plugin.getGroupManager().getIfLoaded(first); + if (nextGroup == null) { + return PromotionResults.malformedTrack(first); + } + + if (!nextGroupPermissionChecker.test(nextGroup.getName())) { + return PromotionResults.undefinedFailure(); + } + + user.setPermission(NodeFactory.buildGroupNode(nextGroup.getId()).withExtraContext(context).build()); + this.plugin.getEventFactory().handleUserPromote(user, this, null, first, sender); + return PromotionResults.addedToFirst(first); + } + + if (nodes.size() != 1) { + return PromotionResults.ambiguousCall(); + } + + Node oldNode = nodes.get(0); + String old = oldNode.getGroupName(); + String next = getNext(old); + + if (next == null) { + return PromotionResults.endOfTrack(); + } + + Group nextGroup = this.plugin.getGroupManager().getIfLoaded(next); + if (nextGroup == null) { + return PromotionResults.malformedTrack(next); + } + + if (!nextGroupPermissionChecker.test(nextGroup.getName())) { + return PromotionResults.undefinedFailure(); + } + + user.unsetPermission(oldNode); + user.setPermission(NodeFactory.buildGroupNode(nextGroup.getName()).withExtraContext(context).build()); + + if (context.isEmpty() && user.getPrimaryGroup().getStoredValue().orElse(NodeFactory.DEFAULT_GROUP_NAME).equalsIgnoreCase(old)) { + user.getPrimaryGroup().setStoredValue(nextGroup.getName()); + } + + this.plugin.getEventFactory().handleUserPromote(user, this, old, nextGroup.getName(), sender); + return PromotionResults.success(old, nextGroup.getName()); + } + + public DemotionResult demote(User user, ContextSet context, Predicate previousGroupPermissionChecker, @Nullable Sender sender) { + if (getSize() <= 1) { + throw new IllegalStateException("Track contains one or fewer groups, unable to demote"); + } + + // find all groups that are inherited by the user in the exact contexts given and applicable to this track + List nodes = user.getEnduringNodes().get(context.makeImmutable()).stream() + .filter(Node::isGroupNode) + .filter(Node::getValuePrimitive) + .filter(node -> containsGroup(node.getGroupName())) + .distinct() + .collect(Collectors.toList()); + + if (nodes.isEmpty()) { + return DemotionResults.notOnTrack(); + } + + if (nodes.size() != 1) { + return DemotionResults.ambiguousCall(); + } + + Node oldNode = nodes.get(0); + String old = oldNode.getGroupName(); + String previous = getPrevious(old); + + if (!previousGroupPermissionChecker.test(oldNode.getGroupName())) { + return DemotionResults.undefinedFailure(); + } + + if (previous == null) { + user.unsetPermission(oldNode); + this.plugin.getEventFactory().handleUserDemote(user, this, old, null, sender); + return DemotionResults.removedFromFirst(old); + } + + Group previousGroup = this.plugin.getGroupManager().getIfLoaded(previous); + if (previousGroup == null) { + return DemotionResults.malformedTrack(previous); + } + + user.unsetPermission(oldNode); + user.setPermission(NodeFactory.buildGroupNode(previousGroup.getName()).withExtraContext(context).build()); + + if (context.isEmpty() && user.getPrimaryGroup().getStoredValue().orElse(NodeFactory.DEFAULT_GROUP_NAME).equalsIgnoreCase(old)) { + user.getPrimaryGroup().setStoredValue(previousGroup.getName()); + } + + this.plugin.getEventFactory().handleUserDemote(user, this, old, previousGroup.getName(), sender); + return DemotionResults.success(old, previousGroup.getName()); + } + @Override public boolean equals(Object o) { if (o == this) return true; diff --git a/common/src/main/java/me/lucko/luckperms/common/storage/PlayerSaveResult.java b/common/src/main/java/me/lucko/luckperms/common/storage/PlayerSaveResult.java index 7d883fcd..deee2e97 100644 --- a/common/src/main/java/me/lucko/luckperms/common/storage/PlayerSaveResult.java +++ b/common/src/main/java/me/lucko/luckperms/common/storage/PlayerSaveResult.java @@ -27,7 +27,6 @@ package me.lucko.luckperms.common.storage; import com.google.common.collect.ImmutableSet; -import java.util.Arrays; import java.util.EnumSet; import java.util.Objects; import java.util.Set; @@ -52,7 +51,7 @@ public final class PlayerSaveResult { } public static PlayerSaveResult usernameUpdated(String oldUsername) { - return new PlayerSaveResult(oldUsername, null, Status.USERNAME_UPDATED); + return new PlayerSaveResult(EnumSet.of(Status.USERNAME_UPDATED), oldUsername, null); } public static PlayerSaveResult determineBaseResult(String username, String oldUsername) { @@ -77,12 +76,8 @@ public final class PlayerSaveResult { this.otherUuids = otherUuids; } - private PlayerSaveResult(@Nullable String oldUsername, @Nullable Set otherUuids, Status... status) { - this(EnumSet.copyOf(Arrays.asList(status)), oldUsername, otherUuids); - } - - private PlayerSaveResult(Status... status) { - this(null, null, status); + private PlayerSaveResult(Status status) { + this(EnumSet.of(status), null, null); } /**