mirror of
https://github.com/Xiao-MoMi/Custom-Nameplates.git
synced 2025-12-28 19:29:17 +00:00
3.0
This commit is contained in:
@@ -1,3 +1,33 @@
|
||||
dependencies {
|
||||
compileOnly("org.jetbrains:annotations:24.1.0")
|
||||
compileOnly("net.kyori:adventure-api:${rootProject.properties["adventure_bundle_version"]}") {
|
||||
exclude(module = "adventure-bom")
|
||||
exclude(module = "checker-qual")
|
||||
exclude(module = "annotations")
|
||||
}
|
||||
compileOnly("org.incendo:cloud-core:${rootProject.properties["cloud_core_version"]}")
|
||||
compileOnly("org.incendo:cloud-minecraft-extras:${rootProject.properties["cloud_minecraft_extras_version"]}")
|
||||
compileOnly("dev.dejvokep:boosted-yaml:${rootProject.properties["boosted_yaml_version"]}")
|
||||
compileOnly("org.jetbrains:annotations:${rootProject.properties["jetbrains_annotations_version"]}")
|
||||
compileOnly("org.slf4j:slf4j-api:${rootProject.properties["slf4j_version"]}")
|
||||
compileOnly("org.apache.logging.log4j:log4j-core:${rootProject.properties["log4j_version"]}")
|
||||
compileOnly("net.kyori:adventure-text-minimessage:${rootProject.properties["adventure_bundle_version"]}")
|
||||
compileOnly("net.kyori:adventure-text-serializer-gson:${rootProject.properties["adventure_bundle_version"]}")
|
||||
compileOnly("com.github.ben-manes.caffeine:caffeine:${rootProject.properties["caffeine_version"]}")
|
||||
compileOnly("com.google.code.gson:gson:${rootProject.properties["gson_version"]}")
|
||||
compileOnly("net.objecthunter:exp4j:${rootProject.properties["exp4j_version"]}")
|
||||
compileOnly("net.bytebuddy:byte-buddy:${rootProject.properties["byte_buddy_version"]}")
|
||||
}
|
||||
|
||||
java {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
toolchain {
|
||||
languageVersion = JavaLanguageVersion.of(17)
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType<JavaCompile> {
|
||||
options.encoding = "UTF-8"
|
||||
options.release.set(17)
|
||||
dependsOn(tasks.clean)
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customnameplates.common;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public record SimpleLocation(String worldName, int x, int y, int z) {
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
final SimpleLocation other = (SimpleLocation) obj;
|
||||
if (!Objects.equals(worldName, other.worldName())) {
|
||||
return false;
|
||||
}
|
||||
if (Double.doubleToLongBits(this.x) != Double.doubleToLongBits(other.x)) {
|
||||
return false;
|
||||
}
|
||||
if (Double.doubleToLongBits(this.y) != Double.doubleToLongBits(other.y)) {
|
||||
return false;
|
||||
}
|
||||
if (Double.doubleToLongBits(this.z) != Double.doubleToLongBits(other.z)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hash = 3;
|
||||
hash = 19 * hash + (worldName != null ? worldName.hashCode() : 0);
|
||||
hash = 19 * hash + (int) (Double.doubleToLongBits(this.x) ^ (Double.doubleToLongBits(this.x) >>> 32));
|
||||
hash = 19 * hash + (int) (Double.doubleToLongBits(this.y) ^ (Double.doubleToLongBits(this.y) >>> 32));
|
||||
hash = 19 * hash + (int) (Double.doubleToLongBits(this.z) ^ (Double.doubleToLongBits(this.z) >>> 32));
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customnameplates.common;
|
||||
|
||||
public class Tuple<L, M, R> {
|
||||
|
||||
private L left;
|
||||
private M mid;
|
||||
private R right;
|
||||
|
||||
public Tuple(L left, M mid, R right) {
|
||||
this.left = left;
|
||||
this.mid = mid;
|
||||
this.right = right;
|
||||
}
|
||||
|
||||
public static <L, M, R> Tuple<L, M, R> of(final L left, final M mid, final R right) {
|
||||
return new Tuple<>(left, mid, right);
|
||||
}
|
||||
|
||||
public L getLeft() {
|
||||
return left;
|
||||
}
|
||||
|
||||
public void setLeft(L left) {
|
||||
this.left = left;
|
||||
}
|
||||
|
||||
public M getMid() {
|
||||
return mid;
|
||||
}
|
||||
|
||||
public void setMid(M mid) {
|
||||
this.mid = mid;
|
||||
}
|
||||
|
||||
public R getRight() {
|
||||
return right;
|
||||
}
|
||||
|
||||
public void setRight(R right) {
|
||||
this.right = right;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Tuple{" +
|
||||
"left=" + left +
|
||||
", mid=" + mid +
|
||||
", right=" + right +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright (C) <2024> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customnameplates.common.command;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.TranslatableComponent;
|
||||
import net.momirealms.customnameplates.common.sender.SenderFactory;
|
||||
import org.incendo.cloud.Command;
|
||||
import org.incendo.cloud.CommandManager;
|
||||
import org.incendo.cloud.context.CommandContext;
|
||||
|
||||
public abstract class AbstractCommandFeature<C> implements CommandFeature<C> {
|
||||
|
||||
protected final CustomNameplatesCommandManager<C> commandManager;
|
||||
protected CommandConfig<C> commandConfig;
|
||||
|
||||
public AbstractCommandFeature(CustomNameplatesCommandManager<C> commandManager) {
|
||||
this.commandManager = commandManager;
|
||||
}
|
||||
|
||||
protected abstract SenderFactory<?, C> getSenderFactory();
|
||||
|
||||
public abstract Command.Builder<? extends C> assembleCommand(CommandManager<C> manager, Command.Builder<C> builder);
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public Command<C> registerCommand(CommandManager<C> manager, Command.Builder<C> builder) {
|
||||
Command<C> command = (Command<C>) assembleCommand(manager, builder).build();
|
||||
manager.command(command);
|
||||
return command;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerRelatedFunctions() {
|
||||
// empty
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterRelatedFunctions() {
|
||||
// empty
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public void handleFeedback(CommandContext<?> context, TranslatableComponent.Builder key, Component... args) {
|
||||
if (context.flags().hasFlag("silent")) {
|
||||
return;
|
||||
}
|
||||
commandManager.handleCommandFeedback((C) context.sender(), key, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleFeedback(C sender, TranslatableComponent.Builder key, Component... args) {
|
||||
commandManager.handleCommandFeedback(sender, key, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CustomNameplatesCommandManager<C> getCustomFishingCommandManager() {
|
||||
return commandManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandConfig<C> getCommandConfig() {
|
||||
return commandConfig;
|
||||
}
|
||||
|
||||
public void setCommandConfig(CommandConfig<C> commandConfig) {
|
||||
this.commandConfig = commandConfig;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
/*
|
||||
* Copyright (C) <2024> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customnameplates.common.command;
|
||||
|
||||
import dev.dejvokep.boostedyaml.YamlDocument;
|
||||
import dev.dejvokep.boostedyaml.block.implementation.Section;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.ComponentLike;
|
||||
import net.kyori.adventure.text.TranslatableComponent;
|
||||
import net.momirealms.customnameplates.common.locale.CustomNameplatesCaptionFormatter;
|
||||
import net.momirealms.customnameplates.common.locale.CustomNameplatesCaptionProvider;
|
||||
import net.momirealms.customnameplates.common.locale.TranslationManager;
|
||||
import net.momirealms.customnameplates.common.plugin.NameplatesPlugin;
|
||||
import net.momirealms.customnameplates.common.sender.Sender;
|
||||
import net.momirealms.customnameplates.common.util.ArrayUtils;
|
||||
import net.momirealms.customnameplates.common.util.TriConsumer;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.incendo.cloud.Command;
|
||||
import org.incendo.cloud.CommandManager;
|
||||
import org.incendo.cloud.caption.Caption;
|
||||
import org.incendo.cloud.caption.StandardCaptionKeys;
|
||||
import org.incendo.cloud.component.CommandComponent;
|
||||
import org.incendo.cloud.exception.*;
|
||||
import org.incendo.cloud.exception.handling.ExceptionContext;
|
||||
import org.incendo.cloud.minecraft.extras.MinecraftExceptionHandler;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
|
||||
public abstract class AbstractCommandManager<C> implements CustomNameplatesCommandManager<C> {
|
||||
|
||||
protected final HashSet<CommandComponent<C>> registeredRootCommandComponents = new HashSet<>();
|
||||
protected final HashSet<CommandFeature<C>> registeredFeatures = new HashSet<>();
|
||||
protected final CommandManager<C> commandManager;
|
||||
protected final NameplatesPlugin plugin;
|
||||
private final CustomNameplatesCaptionFormatter<C> captionFormatter = new CustomNameplatesCaptionFormatter<C>();
|
||||
private final MinecraftExceptionHandler.Decorator<C> decorator = (formatter, ctx, msg) -> msg;
|
||||
|
||||
private TriConsumer<C, String, Component> feedbackConsumer;
|
||||
|
||||
public AbstractCommandManager(NameplatesPlugin plugin, CommandManager<C> commandManager) {
|
||||
this.commandManager = commandManager;
|
||||
this.plugin = plugin;
|
||||
this.inject();
|
||||
this.feedbackConsumer = defaultFeedbackConsumer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFeedbackConsumer(@NotNull TriConsumer<C, String, Component> feedbackConsumer) {
|
||||
this.feedbackConsumer = feedbackConsumer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TriConsumer<C, String, Component> defaultFeedbackConsumer() {
|
||||
return ((sender, node, component) -> {
|
||||
wrapSender(sender).sendMessage(
|
||||
component, true
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
protected abstract Sender wrapSender(C c);
|
||||
|
||||
private void inject() {
|
||||
getCommandManager().captionRegistry().registerProvider(new CustomNameplatesCaptionProvider<>());
|
||||
injectExceptionHandler(InvalidSyntaxException.class, MinecraftExceptionHandler.createDefaultInvalidSyntaxHandler(), StandardCaptionKeys.EXCEPTION_INVALID_SYNTAX);
|
||||
injectExceptionHandler(InvalidCommandSenderException.class, MinecraftExceptionHandler.createDefaultInvalidSenderHandler(), StandardCaptionKeys.EXCEPTION_INVALID_SENDER);
|
||||
injectExceptionHandler(NoPermissionException.class, MinecraftExceptionHandler.createDefaultNoPermissionHandler(), StandardCaptionKeys.EXCEPTION_NO_PERMISSION);
|
||||
injectExceptionHandler(ArgumentParseException.class, MinecraftExceptionHandler.createDefaultArgumentParsingHandler(), StandardCaptionKeys.EXCEPTION_INVALID_ARGUMENT);
|
||||
injectExceptionHandler(CommandExecutionException.class, MinecraftExceptionHandler.createDefaultCommandExecutionHandler(), StandardCaptionKeys.EXCEPTION_UNEXPECTED);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void injectExceptionHandler(Class<? extends Throwable> type, MinecraftExceptionHandler.MessageFactory<C, ?> factory, Caption key) {
|
||||
getCommandManager().exceptionController().registerHandler(type, ctx -> {
|
||||
final @Nullable ComponentLike message = factory.message(captionFormatter, (ExceptionContext) ctx);
|
||||
if (message != null) {
|
||||
handleCommandFeedback(ctx.context().sender(), key.key(), decorator.decorate(captionFormatter, ctx, message.asComponent()).asComponent());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandConfig<C> getCommandConfig(YamlDocument document, String featureID) {
|
||||
Section section = document.getSection(featureID);
|
||||
if (section == null) return null;
|
||||
return new CommandConfig.Builder<C>()
|
||||
.permission(section.getString("permission"))
|
||||
.usages(section.getStringList("usage"))
|
||||
.enable(section.getBoolean("enable", false))
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Command.Builder<C>> buildCommandBuilders(CommandConfig<C> config) {
|
||||
ArrayList<Command.Builder<C>> list = new ArrayList<>();
|
||||
for (String usage : config.getUsages()) {
|
||||
if (!usage.startsWith("/")) continue;
|
||||
String command = usage.substring(1).trim();
|
||||
String[] split = command.split(" ");
|
||||
Command.Builder<C> builder = new CommandBuilder.BasicCommandBuilder<>(getCommandManager(), split[0])
|
||||
.setCommandNode(ArrayUtils.subArray(split, 1))
|
||||
.setPermission(config.getPermission())
|
||||
.getBuiltCommandBuilder();
|
||||
list.add(builder);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerFeature(CommandFeature<C> feature, CommandConfig<C> config) {
|
||||
if (!config.isEnable()) throw new RuntimeException("Registering a disabled command feature is not allowed");
|
||||
for (Command.Builder<C> builder : buildCommandBuilders(config)) {
|
||||
Command<C> command = feature.registerCommand(commandManager, builder);
|
||||
this.registeredRootCommandComponents.add(command.rootComponent());
|
||||
}
|
||||
feature.registerRelatedFunctions();
|
||||
this.registeredFeatures.add(feature);
|
||||
((AbstractCommandFeature<C>) feature).setCommandConfig(config);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerDefaultFeatures() {
|
||||
YamlDocument document = plugin.getConfigManager().loadConfig(commandsFile, '.');
|
||||
try {
|
||||
document.save(new File(plugin.getDataDirectory().toFile(), "commands.yml"));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
this.getFeatures().values().forEach(feature -> {
|
||||
CommandConfig<C> config = getCommandConfig(document, feature.getFeatureID());
|
||||
if (config.isEnable()) {
|
||||
registerFeature(feature, config);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterFeatures() {
|
||||
this.registeredRootCommandComponents.forEach(component -> this.commandManager.commandRegistrationHandler().unregisterRootCommand(component));
|
||||
this.registeredRootCommandComponents.clear();
|
||||
this.registeredFeatures.forEach(CommandFeature::unregisterRelatedFunctions);
|
||||
this.registeredFeatures.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandManager<C> getCommandManager() {
|
||||
return commandManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommandFeedback(C sender, TranslatableComponent.Builder key, Component... args) {
|
||||
TranslatableComponent component = key.arguments(args).build();
|
||||
this.feedbackConsumer.accept(sender, component.key(), TranslationManager.render(component));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommandFeedback(C sender, String node, Component component) {
|
||||
this.feedbackConsumer.accept(sender, node, component);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright (C) <2024> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customnameplates.common.command;
|
||||
|
||||
import org.incendo.cloud.Command;
|
||||
import org.incendo.cloud.CommandManager;
|
||||
|
||||
public interface CommandBuilder<C> {
|
||||
|
||||
CommandBuilder<C> setPermission(String permission);
|
||||
|
||||
CommandBuilder<C> setCommandNode(String... subNodes);
|
||||
|
||||
Command.Builder<C> getBuiltCommandBuilder();
|
||||
|
||||
class BasicCommandBuilder<C> implements CommandBuilder<C> {
|
||||
|
||||
private Command.Builder<C> commandBuilder;
|
||||
|
||||
public BasicCommandBuilder(CommandManager<C> commandManager, String rootNode) {
|
||||
this.commandBuilder = commandManager.commandBuilder(rootNode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandBuilder<C> setPermission(String permission) {
|
||||
this.commandBuilder = this.commandBuilder.permission(permission);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandBuilder<C> setCommandNode(String... subNodes) {
|
||||
for (String sub : subNodes) {
|
||||
this.commandBuilder = this.commandBuilder.literal(sub);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Command.Builder<C> getBuiltCommandBuilder() {
|
||||
return commandBuilder;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright (C) <2024> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customnameplates.common.command;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class CommandConfig<C> {
|
||||
|
||||
private boolean enable = false;
|
||||
private List<String> usages = new ArrayList<>();
|
||||
private String permission = null;
|
||||
|
||||
private CommandConfig() {
|
||||
}
|
||||
|
||||
public CommandConfig(boolean enable, List<String> usages, String permission) {
|
||||
this.enable = enable;
|
||||
this.usages = usages;
|
||||
this.permission = permission;
|
||||
}
|
||||
|
||||
public boolean isEnable() {
|
||||
return enable;
|
||||
}
|
||||
|
||||
public List<String> getUsages() {
|
||||
return usages;
|
||||
}
|
||||
|
||||
public String getPermission() {
|
||||
return permission;
|
||||
}
|
||||
|
||||
public static class Builder<C> {
|
||||
|
||||
private final CommandConfig<C> config;
|
||||
|
||||
public Builder() {
|
||||
this.config = new CommandConfig<>();
|
||||
}
|
||||
|
||||
public Builder<C> usages(List<String> usages) {
|
||||
config.usages = usages;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder<C> permission(String permission) {
|
||||
config.permission = permission;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder<C> enable(boolean enable) {
|
||||
config.enable = enable;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CommandConfig<C> build() {
|
||||
return config;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (C) <2024> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customnameplates.common.command;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.TranslatableComponent;
|
||||
import org.incendo.cloud.Command;
|
||||
import org.incendo.cloud.CommandManager;
|
||||
import org.incendo.cloud.context.CommandContext;
|
||||
|
||||
public interface CommandFeature<C> {
|
||||
|
||||
Command<C> registerCommand(CommandManager<C> cloudCommandManager, Command.Builder<C> builder);
|
||||
|
||||
String getFeatureID();
|
||||
|
||||
void registerRelatedFunctions();
|
||||
|
||||
void unregisterRelatedFunctions();
|
||||
|
||||
void handleFeedback(CommandContext<?> context, TranslatableComponent.Builder key, Component... args);
|
||||
|
||||
void handleFeedback(C sender, TranslatableComponent.Builder key, Component... args);
|
||||
|
||||
CustomNameplatesCommandManager<C> getCustomFishingCommandManager();
|
||||
|
||||
CommandConfig<C> getCommandConfig();
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright (C) <2024> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customnameplates.common.command;
|
||||
|
||||
import dev.dejvokep.boostedyaml.YamlDocument;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.TranslatableComponent;
|
||||
import net.kyori.adventure.util.Index;
|
||||
import net.momirealms.customnameplates.common.util.TriConsumer;
|
||||
import org.incendo.cloud.Command;
|
||||
import org.incendo.cloud.CommandManager;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
public interface CustomNameplatesCommandManager<C> {
|
||||
|
||||
String commandsFile = "commands.yml";
|
||||
|
||||
void unregisterFeatures();
|
||||
|
||||
void registerFeature(CommandFeature<C> feature, CommandConfig<C> config);
|
||||
|
||||
void registerDefaultFeatures();
|
||||
|
||||
Index<String, CommandFeature<C>> getFeatures();
|
||||
|
||||
void setFeedbackConsumer(@NotNull TriConsumer<C, String, Component> feedbackConsumer);
|
||||
|
||||
TriConsumer<C, String, Component> defaultFeedbackConsumer();
|
||||
|
||||
CommandConfig<C> getCommandConfig(YamlDocument document, String featureID);
|
||||
|
||||
Collection<Command.Builder<C>> buildCommandBuilders(CommandConfig<C> config);
|
||||
|
||||
CommandManager<C> getCommandManager();
|
||||
|
||||
void handleCommandFeedback(C sender, TranslatableComponent.Builder key, Component... args);
|
||||
|
||||
void handleCommandFeedback(C sender, String node, Component component);
|
||||
}
|
||||
@@ -0,0 +1,385 @@
|
||||
/*
|
||||
* 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 net.momirealms.customnameplates.common.dependency;
|
||||
|
||||
import net.momirealms.customnameplates.common.dependency.relocation.Relocation;
|
||||
import net.momirealms.customnameplates.common.plugin.CustomNameplatesProperties;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* The dependencies used by CustomFishing.
|
||||
*/
|
||||
public enum Dependency {
|
||||
|
||||
ASM(
|
||||
"org.ow2.asm",
|
||||
"asm",
|
||||
"maven",
|
||||
"asm"
|
||||
),
|
||||
ASM_COMMONS(
|
||||
"org.ow2.asm",
|
||||
"asm-commons",
|
||||
"maven",
|
||||
"asm-commons"
|
||||
),
|
||||
JAR_RELOCATOR(
|
||||
"me.lucko",
|
||||
"jar-relocator",
|
||||
"maven",
|
||||
"jar-relocator"
|
||||
),
|
||||
H2_DRIVER(
|
||||
"com.h2database",
|
||||
"h2",
|
||||
"maven",
|
||||
"h2-driver"
|
||||
),
|
||||
SQLITE_DRIVER(
|
||||
"org.xerial",
|
||||
"sqlite-jdbc",
|
||||
"maven",
|
||||
"sqlite-driver"
|
||||
),
|
||||
CLOUD_CORE(
|
||||
"org{}incendo",
|
||||
"cloud-core",
|
||||
"maven",
|
||||
"cloud-core",
|
||||
Relocation.of("cloud", "org{}incendo{}cloud"),
|
||||
Relocation.of("geantyref", "io{}leangen{}geantyref")
|
||||
),
|
||||
CLOUD_BRIGADIER(
|
||||
"org{}incendo",
|
||||
"cloud-brigadier",
|
||||
"maven",
|
||||
"cloud-brigadier",
|
||||
Relocation.of("cloud", "org{}incendo{}cloud"),
|
||||
Relocation.of("geantyref", "io{}leangen{}geantyref")
|
||||
),
|
||||
CLOUD_SERVICES(
|
||||
"org{}incendo",
|
||||
"cloud-services",
|
||||
"maven",
|
||||
"cloud-services",
|
||||
Relocation.of("cloud", "org{}incendo{}cloud"),
|
||||
Relocation.of("geantyref", "io{}leangen{}geantyref")
|
||||
),
|
||||
CLOUD_BUKKIT(
|
||||
"org{}incendo",
|
||||
"cloud-bukkit",
|
||||
"maven",
|
||||
"cloud-bukkit",
|
||||
Relocation.of("cloud", "org{}incendo{}cloud"),
|
||||
Relocation.of("geantyref", "io{}leangen{}geantyref")
|
||||
),
|
||||
CLOUD_PAPER(
|
||||
"org{}incendo",
|
||||
"cloud-paper",
|
||||
"maven",
|
||||
"cloud-paper",
|
||||
Relocation.of("cloud", "org{}incendo{}cloud"),
|
||||
Relocation.of("geantyref", "io{}leangen{}geantyref")
|
||||
),
|
||||
CLOUD_MINECRAFT_EXTRAS(
|
||||
"org{}incendo",
|
||||
"cloud-minecraft-extras",
|
||||
"maven",
|
||||
"cloud-minecraft-extras",
|
||||
Relocation.of("cloud", "org{}incendo{}cloud"),
|
||||
Relocation.of("adventure", "net{}kyori{}adventure"),
|
||||
Relocation.of("option", "net{}kyori{}option"),
|
||||
Relocation.of("examination", "net{}kyori{}examination"),
|
||||
Relocation.of("geantyref", "io{}leangen{}geantyref")
|
||||
),
|
||||
GEANTY_REF(
|
||||
"io{}leangen{}geantyref",
|
||||
"geantyref",
|
||||
"maven",
|
||||
"geantyref",
|
||||
Relocation.of("geantyref", "io{}leangen{}geantyref")
|
||||
),
|
||||
BOOSTED_YAML(
|
||||
"dev{}dejvokep",
|
||||
"boosted-yaml",
|
||||
"maven",
|
||||
"boosted-yaml",
|
||||
Relocation.of("boostedyaml", "dev{}dejvokep{}boostedyaml")
|
||||
),
|
||||
BYTEBUDDY(
|
||||
"net{}bytebuddy",
|
||||
"byte-buddy",
|
||||
"maven",
|
||||
"byte-buddy",
|
||||
Relocation.of("bytebuddy", "net{}bytebuddy")
|
||||
),
|
||||
MARIADB_DRIVER(
|
||||
"org{}mariadb{}jdbc",
|
||||
"mariadb-java-client",
|
||||
"maven",
|
||||
"mariadb-java-client",
|
||||
Relocation.of("mariadb", "org{}mariadb")
|
||||
),
|
||||
MYSQL_DRIVER(
|
||||
"com{}mysql",
|
||||
"mysql-connector-j",
|
||||
"maven",
|
||||
"mysql-connector-j",
|
||||
Relocation.of("mysql", "com{}mysql")
|
||||
),
|
||||
HIKARI_CP(
|
||||
"com{}zaxxer",
|
||||
"HikariCP",
|
||||
"maven",
|
||||
"hikari-cp",
|
||||
Relocation.of("hikari", "com{}zaxxer{}hikari")
|
||||
),
|
||||
MONGODB_DRIVER_CORE(
|
||||
"org{}mongodb",
|
||||
"mongodb-driver-core",
|
||||
"maven",
|
||||
"mongodb-driver-core",
|
||||
Relocation.of("mongodb", "com{}mongodb"),
|
||||
Relocation.of("bson", "org{}bson")
|
||||
),
|
||||
MONGODB_DRIVER_SYNC(
|
||||
"org{}mongodb",
|
||||
"mongodb-driver-sync",
|
||||
"maven",
|
||||
"mongodb-driver-sync",
|
||||
Relocation.of("mongodb", "com{}mongodb"),
|
||||
Relocation.of("bson", "org{}bson")
|
||||
) {
|
||||
@Override
|
||||
public String getVersion() {
|
||||
return Dependency.MONGODB_DRIVER_CORE.getVersion();
|
||||
}
|
||||
},
|
||||
MONGODB_DRIVER_BSON(
|
||||
"org{}mongodb",
|
||||
"bson",
|
||||
"maven",
|
||||
"mongodb-bson",
|
||||
Relocation.of("mongodb", "com{}mongodb"),
|
||||
Relocation.of("bson", "org{}bson")
|
||||
) {
|
||||
@Override
|
||||
public String getVersion() {
|
||||
return Dependency.MONGODB_DRIVER_CORE.getVersion();
|
||||
}
|
||||
},
|
||||
COMMONS_POOL_2(
|
||||
"org{}apache{}commons",
|
||||
"commons-pool2",
|
||||
"maven",
|
||||
"commons-pool",
|
||||
Relocation.of("commonspool2", "org{}apache{}commons{}pool2")
|
||||
),
|
||||
BSTATS_BASE(
|
||||
"org{}bstats",
|
||||
"bstats-base",
|
||||
"maven",
|
||||
"bstats-base",
|
||||
Relocation.of("bstats", "org{}bstats")
|
||||
),
|
||||
BSTATS_BUKKIT(
|
||||
"org{}bstats",
|
||||
"bstats-bukkit",
|
||||
"maven",
|
||||
"bstats-bukkit",
|
||||
Relocation.of("bstats", "org{}bstats")
|
||||
) {
|
||||
@Override
|
||||
public String getVersion() {
|
||||
return Dependency.BSTATS_BASE.getVersion();
|
||||
}
|
||||
},
|
||||
GSON(
|
||||
"com.google.code.gson",
|
||||
"gson",
|
||||
"maven",
|
||||
"gson"
|
||||
),
|
||||
CAFFEINE(
|
||||
"com{}github{}ben-manes{}caffeine",
|
||||
"caffeine",
|
||||
"maven",
|
||||
"caffeine",
|
||||
Relocation.of("caffeine", "com{}github{}benmanes{}caffeine")
|
||||
),
|
||||
JEDIS(
|
||||
"redis{}clients",
|
||||
"jedis",
|
||||
"maven",
|
||||
"jedis",
|
||||
Relocation.of("jedis", "redis{}clients{}jedis"),
|
||||
Relocation.of("commonspool2", "org{}apache{}commons{}pool2")
|
||||
),
|
||||
EXP4J(
|
||||
"net{}objecthunter",
|
||||
"exp4j",
|
||||
"maven",
|
||||
"exp4j",
|
||||
Relocation.of("exp4j", "net{}objecthunter{}exp4j")
|
||||
),
|
||||
SLF4J_SIMPLE(
|
||||
"org.slf4j",
|
||||
"slf4j-simple",
|
||||
"maven",
|
||||
"slf4j_simple"
|
||||
) {
|
||||
@Override
|
||||
public String getVersion() {
|
||||
return Dependency.SLF4J_API.getVersion();
|
||||
}
|
||||
},
|
||||
SLF4J_API(
|
||||
"org.slf4j",
|
||||
"slf4j-api",
|
||||
"maven",
|
||||
"slf4j"
|
||||
),
|
||||
FONT_BOX(
|
||||
"org{}apache{}pdfbox",
|
||||
"fontbox",
|
||||
"maven",
|
||||
"fontbox",
|
||||
Relocation.of("fontbox", "org{}apache{}fontbox"),
|
||||
Relocation.of("pdfbox", "org{}apache{}pdfbox")
|
||||
),
|
||||
PDF_BOX(
|
||||
"org{}apache{}pdfbox",
|
||||
"pdfbox-io",
|
||||
"maven",
|
||||
"pdfbox-io",
|
||||
Relocation.of("pdfbox", "org{}apache{}pdfbox")
|
||||
),
|
||||
BYTE_BUDDY(
|
||||
"net{}bytebuddy",
|
||||
"byte-buddy",
|
||||
"maven",
|
||||
"byte-buddy",
|
||||
Relocation.of("bytebuddy", "net{}bytebuddy")
|
||||
),
|
||||
COMMONS_IO(
|
||||
"commons-io",
|
||||
"commons-io",
|
||||
"maven",
|
||||
"commons-io",
|
||||
Relocation.of("commons", "org{}apache{}commons")
|
||||
);
|
||||
|
||||
private final List<Relocation> relocations;
|
||||
private final String repo;
|
||||
private final String groupId;
|
||||
private final String artifactId;
|
||||
private final String customArtifactID;
|
||||
private String artifactIdSuffix;
|
||||
|
||||
private static final String MAVEN_FORMAT = "%s/%s/%s/%s-%s.jar";
|
||||
|
||||
Dependency(String groupId, String artifactId, String repo, String customArtifactID) {
|
||||
this(groupId, artifactId, repo, customArtifactID, new Relocation[0]);
|
||||
}
|
||||
|
||||
Dependency(String groupId, String artifactId, String repo, String customArtifactID, Relocation... relocations) {
|
||||
this.artifactId = artifactId;
|
||||
this.artifactIdSuffix = "";
|
||||
this.groupId = groupId;
|
||||
this.relocations = new ArrayList<>(Arrays.stream(relocations).toList());
|
||||
this.repo = repo;
|
||||
this.customArtifactID = customArtifactID;
|
||||
}
|
||||
|
||||
Dependency(String groupId, String artifactId, String repo, String customArtifactID, String artifactIdSuffix, Relocation... relocations) {
|
||||
this.artifactId = artifactId;
|
||||
this.artifactIdSuffix = artifactIdSuffix;
|
||||
this.groupId = groupId;
|
||||
this.relocations = new ArrayList<>(Arrays.stream(relocations).toList());
|
||||
this.repo = repo;
|
||||
this.customArtifactID = customArtifactID;
|
||||
}
|
||||
|
||||
public void setArtifactIdSuffix(String artifactIdSuffix) {
|
||||
this.artifactIdSuffix = artifactIdSuffix;
|
||||
}
|
||||
|
||||
public String getVersion() {
|
||||
return CustomNameplatesProperties.getValue(customArtifactID);
|
||||
}
|
||||
|
||||
private static String rewriteEscaping(String s) {
|
||||
return s.replace("{}", ".");
|
||||
}
|
||||
|
||||
public String getFileName(String classifier) {
|
||||
String name = customArtifactID.toLowerCase(Locale.ROOT).replace('_', '-');
|
||||
String extra = classifier == null || classifier.isEmpty()
|
||||
? ""
|
||||
: "-" + classifier;
|
||||
return name + "-" + this.getVersion() + extra + ".jar";
|
||||
}
|
||||
|
||||
String getMavenRepoPath() {
|
||||
return String.format(MAVEN_FORMAT,
|
||||
rewriteEscaping(groupId).replace(".", "/"),
|
||||
rewriteEscaping(artifactId),
|
||||
getVersion(),
|
||||
rewriteEscaping(artifactId),
|
||||
getVersion() + artifactIdSuffix
|
||||
);
|
||||
}
|
||||
|
||||
public List<Relocation> getRelocations() {
|
||||
return this.relocations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link MessageDigest} suitable for computing the checksums
|
||||
* of dependencies.
|
||||
*
|
||||
* @return the digest
|
||||
*/
|
||||
public static MessageDigest createDigest() {
|
||||
try {
|
||||
return MessageDigest.getInstance("SHA-256");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getRepo() {
|
||||
return repo;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* 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 net.momirealms.customnameplates.common.dependency;
|
||||
|
||||
/**
|
||||
* Exception thrown if a dependency cannot be downloaded.
|
||||
*/
|
||||
public class DependencyDownloadException extends Exception {
|
||||
|
||||
public DependencyDownloadException() {
|
||||
|
||||
}
|
||||
|
||||
public DependencyDownloadException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public DependencyDownloadException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public DependencyDownloadException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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 net.momirealms.customnameplates.common.dependency;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Loads and manages runtime dependencies for the plugin.
|
||||
*/
|
||||
public interface DependencyManager extends AutoCloseable {
|
||||
|
||||
/**
|
||||
* Loads dependencies.
|
||||
*
|
||||
* @param dependencies the dependencies to load
|
||||
*/
|
||||
void loadDependencies(Collection<Dependency> dependencies);
|
||||
|
||||
/**
|
||||
* Obtains an isolated classloader containing the given dependencies.
|
||||
*
|
||||
* @param dependencies the dependencies
|
||||
* @return the classloader
|
||||
*/
|
||||
ClassLoader obtainClassLoaderWith(Set<Dependency> dependencies);
|
||||
|
||||
@Override
|
||||
void close();
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
/*
|
||||
* 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 net.momirealms.customnameplates.common.dependency;
|
||||
|
||||
import net.momirealms.customnameplates.common.dependency.classloader.IsolatedClassLoader;
|
||||
import net.momirealms.customnameplates.common.dependency.relocation.Relocation;
|
||||
import net.momirealms.customnameplates.common.dependency.relocation.RelocationHandler;
|
||||
import net.momirealms.customnameplates.common.plugin.NameplatesPlugin;
|
||||
import net.momirealms.customnameplates.common.plugin.classpath.ClassPathAppender;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.FileAlreadyExistsException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
/**
|
||||
* Loads and manages runtime dependencies for the plugin.
|
||||
*/
|
||||
public class DependencyManagerImpl implements DependencyManager {
|
||||
|
||||
/** A registry containing plugin specific behaviour for dependencies. */
|
||||
private final DependencyRegistry registry;
|
||||
/** The path where library jars are cached. */
|
||||
private final Path cacheDirectory;
|
||||
/** The classpath appender to preload dependencies into */
|
||||
private final ClassPathAppender classPathAppender;
|
||||
/** A map of dependencies which have already been loaded. */
|
||||
private final EnumMap<Dependency, Path> loaded = new EnumMap<>(Dependency.class);
|
||||
/** A map of isolated classloaders which have been created. */
|
||||
private final Map<Set<Dependency>, IsolatedClassLoader> loaders = new HashMap<>();
|
||||
/** Cached relocation handler instance. */
|
||||
private final RelocationHandler relocationHandler;
|
||||
private final Executor loadingExecutor;
|
||||
private final NameplatesPlugin plugin;
|
||||
|
||||
public DependencyManagerImpl(NameplatesPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
this.registry = new DependencyRegistry();
|
||||
this.cacheDirectory = setupCacheDirectory(plugin);
|
||||
this.classPathAppender = plugin.getClassPathAppender();
|
||||
this.loadingExecutor = plugin.getScheduler().async();
|
||||
this.relocationHandler = new RelocationHandler(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClassLoader obtainClassLoaderWith(Set<Dependency> dependencies) {
|
||||
Set<Dependency> set = new HashSet<>(dependencies);
|
||||
|
||||
for (Dependency dependency : dependencies) {
|
||||
if (!this.loaded.containsKey(dependency)) {
|
||||
throw new IllegalStateException("Dependency " + dependency + " is not loaded.");
|
||||
}
|
||||
}
|
||||
|
||||
synchronized (this.loaders) {
|
||||
IsolatedClassLoader classLoader = this.loaders.get(set);
|
||||
if (classLoader != null) {
|
||||
return classLoader;
|
||||
}
|
||||
|
||||
URL[] urls = set.stream()
|
||||
.map(this.loaded::get)
|
||||
.map(file -> {
|
||||
try {
|
||||
return file.toUri().toURL();
|
||||
} catch (MalformedURLException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
})
|
||||
.toArray(URL[]::new);
|
||||
|
||||
classLoader = new IsolatedClassLoader(urls);
|
||||
this.loaders.put(set, classLoader);
|
||||
return classLoader;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadDependencies(Collection<Dependency> dependencies) {
|
||||
CountDownLatch latch = new CountDownLatch(dependencies.size());
|
||||
|
||||
for (Dependency dependency : dependencies) {
|
||||
if (this.loaded.containsKey(dependency)) {
|
||||
latch.countDown();
|
||||
continue;
|
||||
}
|
||||
|
||||
this.loadingExecutor.execute(() -> {
|
||||
try {
|
||||
loadDependency(dependency);
|
||||
} catch (Throwable e) {
|
||||
this.plugin.getPluginLogger().warn("Unable to load dependency " + dependency.name(), e);
|
||||
} finally {
|
||||
latch.countDown();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
latch.await();
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
private void loadDependency(Dependency dependency) throws Exception {
|
||||
if (this.loaded.containsKey(dependency)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Path file = remapDependency(dependency, downloadDependency(dependency));
|
||||
|
||||
this.loaded.put(dependency, file);
|
||||
|
||||
if (this.classPathAppender != null && this.registry.shouldAutoLoad(dependency)) {
|
||||
this.classPathAppender.addJarToClasspath(file);
|
||||
}
|
||||
}
|
||||
|
||||
private Path downloadDependency(Dependency dependency) throws DependencyDownloadException {
|
||||
String fileName = dependency.getFileName(null);
|
||||
Path file = this.cacheDirectory.resolve(fileName);
|
||||
|
||||
// if the file already exists, don't attempt to re-download it.
|
||||
if (Files.exists(file)) {
|
||||
return file;
|
||||
}
|
||||
|
||||
DependencyDownloadException lastError = null;
|
||||
String forceRepo = dependency.getRepo();
|
||||
List<DependencyRepository> repository = DependencyRepository.getByID(forceRepo);
|
||||
if (!repository.isEmpty()) {
|
||||
int i = 0;
|
||||
while (i < repository.size()) {
|
||||
try {
|
||||
plugin.getPluginLogger().info("Downloading dependency(" + fileName + ")[" + repository.get(i).getUrl() + dependency.getMavenRepoPath() + "]");
|
||||
repository.get(i).download(dependency, file);
|
||||
plugin.getPluginLogger().info("Successfully downloaded " + fileName);
|
||||
return file;
|
||||
} catch (DependencyDownloadException e) {
|
||||
lastError = e;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
throw Objects.requireNonNull(lastError);
|
||||
}
|
||||
|
||||
private Path remapDependency(Dependency dependency, Path normalFile) throws Exception {
|
||||
List<Relocation> rules = new ArrayList<>(dependency.getRelocations());
|
||||
if (rules.isEmpty()) {
|
||||
return normalFile;
|
||||
}
|
||||
|
||||
Path remappedFile = this.cacheDirectory.resolve(dependency.getFileName(DependencyRegistry.isGsonRelocated() ? "remapped-legacy" : "remapped"));
|
||||
|
||||
// if the remapped source exists already, just use that.
|
||||
if (Files.exists(remappedFile)) {
|
||||
return remappedFile;
|
||||
}
|
||||
|
||||
plugin.getPluginLogger().info("Remapping " + dependency.getFileName(null));
|
||||
relocationHandler.remap(normalFile, remappedFile, rules);
|
||||
plugin.getPluginLogger().info("Successfully remapped " + dependency.getFileName(null));
|
||||
return remappedFile;
|
||||
}
|
||||
|
||||
private static Path setupCacheDirectory(NameplatesPlugin plugin) {
|
||||
Path cacheDirectory = plugin.getDataDirectory().resolve("libs");
|
||||
try {
|
||||
if (Files.exists(cacheDirectory) && (Files.isDirectory(cacheDirectory) || Files.isSymbolicLink(cacheDirectory))) {
|
||||
return cacheDirectory;
|
||||
}
|
||||
|
||||
try {
|
||||
Files.createDirectories(cacheDirectory);
|
||||
} catch (FileAlreadyExistsException e) {
|
||||
// ignore
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Unable to create libs directory", e);
|
||||
}
|
||||
|
||||
return cacheDirectory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
IOException firstEx = null;
|
||||
|
||||
for (IsolatedClassLoader loader : this.loaders.values()) {
|
||||
try {
|
||||
loader.close();
|
||||
} catch (IOException ex) {
|
||||
if (firstEx == null) {
|
||||
firstEx = ex;
|
||||
} else {
|
||||
firstEx.addSuppressed(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (firstEx != null) {
|
||||
plugin.getPluginLogger().severe(firstEx.getMessage(), firstEx);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* 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 net.momirealms.customnameplates.common.dependency;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
|
||||
/**
|
||||
* Applies CustomFishing specific behaviour for {@link Dependency}s.
|
||||
*/
|
||||
public class DependencyRegistry {
|
||||
|
||||
public boolean shouldAutoLoad(Dependency dependency) {
|
||||
return switch (dependency) {
|
||||
// all used within 'isolated' classloaders, and are therefore not
|
||||
// relocated.
|
||||
case ASM, ASM_COMMONS, JAR_RELOCATOR, H2_DRIVER, SQLITE_DRIVER -> false;
|
||||
default -> true;
|
||||
};
|
||||
}
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
public static boolean isGsonRelocated() {
|
||||
return JsonElement.class.getName().startsWith("net.momirealms");
|
||||
}
|
||||
|
||||
private static boolean classExists(String className) {
|
||||
try {
|
||||
Class.forName(className);
|
||||
return true;
|
||||
} catch (ClassNotFoundException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean slf4jPresent() {
|
||||
return classExists("org.slf4j.Logger") && classExists("org.slf4j.LoggerFactory");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
* 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 net.momirealms.customnameplates.common.dependency;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Represents a repository which contains {@link Dependency}s.
|
||||
*/
|
||||
public enum DependencyRepository {
|
||||
|
||||
/**
|
||||
* Maven Central
|
||||
*/
|
||||
MAVEN_CENTRAL("maven", "https://repo1.maven.org/maven2/") {
|
||||
@Override
|
||||
protected URLConnection openConnection(Dependency dependency) throws IOException {
|
||||
URLConnection connection = super.openConnection(dependency);
|
||||
connection.setConnectTimeout(5000);
|
||||
connection.setReadTimeout(5000);
|
||||
return connection;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Maven Central Mirror
|
||||
*/
|
||||
MAVEN_CENTRAL_MIRROR("maven", "https://maven.aliyun.com/repository/public/");
|
||||
|
||||
private final String url;
|
||||
private final String id;
|
||||
|
||||
DependencyRepository(String id, String url) {
|
||||
this.url = url;
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public static List<DependencyRepository> getByID(String id) {
|
||||
ArrayList<DependencyRepository> repositories = new ArrayList<>();
|
||||
for (DependencyRepository repository : values()) {
|
||||
if (id.equals(repository.id)) {
|
||||
repositories.add(repository);
|
||||
}
|
||||
}
|
||||
// 中国大陆优先使用国内阿里云镜像
|
||||
if (id.equals("maven") && Locale.getDefault() == Locale.SIMPLIFIED_CHINESE) {
|
||||
Collections.reverse(repositories);
|
||||
}
|
||||
return repositories;
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a connection to the given {@code dependency}.
|
||||
*
|
||||
* @param dependency the dependency to download
|
||||
* @return the connection
|
||||
* @throws IOException if unable to open a connection
|
||||
*/
|
||||
protected URLConnection openConnection(Dependency dependency) throws IOException {
|
||||
URL dependencyUrl = new URL(this.url + dependency.getMavenRepoPath());
|
||||
return dependencyUrl.openConnection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads the raw bytes of the {@code dependency}.
|
||||
*
|
||||
* @param dependency the dependency to download
|
||||
* @return the downloaded bytes
|
||||
* @throws DependencyDownloadException if unable to download
|
||||
*/
|
||||
public byte[] downloadRaw(Dependency dependency) throws DependencyDownloadException {
|
||||
try {
|
||||
URLConnection connection = openConnection(dependency);
|
||||
try (InputStream in = connection.getInputStream()) {
|
||||
byte[] bytes = in.readAllBytes();
|
||||
if (bytes.length == 0) {
|
||||
throw new DependencyDownloadException("Empty stream");
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new DependencyDownloadException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param dependency the dependency to download
|
||||
* @return the downloaded bytes
|
||||
* @throws DependencyDownloadException if unable to download
|
||||
*/
|
||||
public byte[] download(Dependency dependency) throws DependencyDownloadException {
|
||||
return downloadRaw(dependency);
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads the the {@code dependency} to the {@code file}, ensuring the
|
||||
* downloaded bytes match the checksum.
|
||||
*
|
||||
* @param dependency the dependency to download
|
||||
* @param file the file to write to
|
||||
* @throws DependencyDownloadException if unable to download
|
||||
*/
|
||||
public void download(Dependency dependency, Path file) throws DependencyDownloadException {
|
||||
try {
|
||||
Files.write(file, download(dependency));
|
||||
} catch (IOException e) {
|
||||
throw new DependencyDownloadException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* 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 net.momirealms.customnameplates.common.dependency.classloader;
|
||||
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
|
||||
/**
|
||||
* A classloader "isolated" from the rest of the Minecraft server.
|
||||
*
|
||||
* <p>Used to load specific CustomFishing dependencies without causing conflicts
|
||||
* with other plugins, or libraries provided by the server implementation.</p>
|
||||
*/
|
||||
public class IsolatedClassLoader extends URLClassLoader {
|
||||
static {
|
||||
ClassLoader.registerAsParallelCapable();
|
||||
}
|
||||
|
||||
public IsolatedClassLoader(URL[] urls) {
|
||||
/*
|
||||
* ClassLoader#getSystemClassLoader returns the AppClassLoader
|
||||
*
|
||||
* Calling #getParent on this returns the ExtClassLoader (Java 8) or
|
||||
* the PlatformClassLoader (Java 9). Since we want this classloader to
|
||||
* be isolated from the Minecraft server (the app), we set the parent
|
||||
* to be the platform class loader.
|
||||
*/
|
||||
super(urls, ClassLoader.getSystemClassLoader().getParent());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* 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 net.momirealms.customnameplates.common.dependency.relocation;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public final class Relocation {
|
||||
private static final String RELOCATION_PREFIX = "net.momirealms.customnameplates.libraries.";
|
||||
|
||||
public static Relocation of(String id, String pattern) {
|
||||
return new Relocation(pattern.replace("{}", "."), RELOCATION_PREFIX + id);
|
||||
}
|
||||
|
||||
private final String pattern;
|
||||
private final String relocatedPattern;
|
||||
|
||||
private Relocation(String pattern, String relocatedPattern) {
|
||||
this.pattern = pattern;
|
||||
this.relocatedPattern = relocatedPattern;
|
||||
}
|
||||
|
||||
public String getPattern() {
|
||||
return this.pattern;
|
||||
}
|
||||
|
||||
public String getRelocatedPattern() {
|
||||
return this.relocatedPattern;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
Relocation that = (Relocation) o;
|
||||
return Objects.equals(this.pattern, that.pattern) &&
|
||||
Objects.equals(this.relocatedPattern, that.relocatedPattern);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(this.pattern, this.relocatedPattern);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* 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 net.momirealms.customnameplates.common.dependency.relocation;
|
||||
|
||||
import net.momirealms.customnameplates.common.dependency.Dependency;
|
||||
import net.momirealms.customnameplates.common.dependency.DependencyManager;
|
||||
import net.momirealms.customnameplates.common.dependency.classloader.IsolatedClassLoader;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Method;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Handles class runtime relocation of packages in downloaded dependencies
|
||||
*/
|
||||
public class RelocationHandler {
|
||||
public static final Set<Dependency> DEPENDENCIES = EnumSet.of(Dependency.ASM, Dependency.ASM_COMMONS, Dependency.JAR_RELOCATOR);
|
||||
private static final String JAR_RELOCATOR_CLASS = "me.lucko.jarrelocator.JarRelocator";
|
||||
private static final String JAR_RELOCATOR_RUN_METHOD = "run";
|
||||
|
||||
private final Constructor<?> jarRelocatorConstructor;
|
||||
private final Method jarRelocatorRunMethod;
|
||||
|
||||
public RelocationHandler(DependencyManager dependencyManager) {
|
||||
ClassLoader classLoader = null;
|
||||
try {
|
||||
// download the required dependencies for remapping
|
||||
dependencyManager.loadDependencies(DEPENDENCIES);
|
||||
// get a classloader containing the required dependencies as sources
|
||||
classLoader = dependencyManager.obtainClassLoaderWith(DEPENDENCIES);
|
||||
|
||||
// load the relocator class
|
||||
Class<?> jarRelocatorClass = classLoader.loadClass(JAR_RELOCATOR_CLASS);
|
||||
|
||||
// prepare the the reflected constructor & method instances
|
||||
this.jarRelocatorConstructor = jarRelocatorClass.getDeclaredConstructor(File.class, File.class, Map.class);
|
||||
this.jarRelocatorConstructor.setAccessible(true);
|
||||
|
||||
this.jarRelocatorRunMethod = jarRelocatorClass.getDeclaredMethod(JAR_RELOCATOR_RUN_METHOD);
|
||||
this.jarRelocatorRunMethod.setAccessible(true);
|
||||
} catch (Exception e) {
|
||||
try {
|
||||
if (classLoader instanceof IsolatedClassLoader isolatedClassLoader) {
|
||||
isolatedClassLoader.close();
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
e.addSuppressed(ex);
|
||||
}
|
||||
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void remap(Path input, Path output, List<Relocation> relocations) throws Exception {
|
||||
Map<String, String> mappings = new HashMap<>();
|
||||
for (Relocation relocation : relocations) {
|
||||
mappings.put(relocation.getPattern(), relocation.getRelocatedPattern());
|
||||
}
|
||||
|
||||
// create and invoke a new relocator
|
||||
Object relocator = this.jarRelocatorConstructor.newInstance(input.toFile(), output.toFile(), mappings);
|
||||
this.jarRelocatorRunMethod.invoke(relocator);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* 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 net.momirealms.customnameplates.common.dependency.relocation;
|
||||
|
||||
public final class RelocationHelper {
|
||||
|
||||
// screw maven shade
|
||||
public static final String OKIO_STRING = String.valueOf(new char[]{'o', 'k', 'i', 'o'});
|
||||
public static final String OKHTTP3_STRING = String.valueOf(new char[]{'o', 'k', 'h', 't', 't', 'p', '3'});
|
||||
|
||||
private RelocationHelper() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package net.momirealms.customnameplates.common.event;
|
||||
|
||||
public interface Cancellable {
|
||||
|
||||
/**
|
||||
* Gets the cancelled state.
|
||||
*/
|
||||
boolean cancelled();
|
||||
|
||||
/**
|
||||
* Sets the cancelled state.
|
||||
*/
|
||||
void cancelled(final boolean cancelled);
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* This file is part of event, licensed under the MIT License.
|
||||
*
|
||||
* Copyright (c) 2021-2023 Seiama
|
||||
*
|
||||
* 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 net.momirealms.customnameplates.common.event;
|
||||
|
||||
public interface EventConfig {
|
||||
|
||||
/**
|
||||
* The default value for {@link #order()}.
|
||||
*
|
||||
*/
|
||||
int DEFAULT_ORDER = 0;
|
||||
|
||||
/**
|
||||
* The default value for {@link #acceptsCancelled()}.
|
||||
*
|
||||
*/
|
||||
boolean DEFAULT_ACCEPTS_CANCELLED = true;
|
||||
|
||||
/**
|
||||
* The default value for {@link #exact()}.
|
||||
*
|
||||
*/
|
||||
boolean DEFAULT_EXACT = false;
|
||||
|
||||
/**
|
||||
* Gets the post order.
|
||||
*
|
||||
* @return the post order
|
||||
*/
|
||||
int order();
|
||||
|
||||
/**
|
||||
* Sets the post order.
|
||||
*
|
||||
* @param order the post order
|
||||
* @return an {@link EventConfig}
|
||||
*/
|
||||
EventConfig order(final int order);
|
||||
|
||||
/**
|
||||
* Gets if cancelled events are accepted.
|
||||
*
|
||||
* @return if cancelled events are accepted
|
||||
*/
|
||||
boolean acceptsCancelled();
|
||||
|
||||
/**
|
||||
* Sets if cancelled events are accepted.
|
||||
*
|
||||
* @param acceptsCancelled if cancelled events are accepted
|
||||
* @return an {@link EventConfig}
|
||||
*/
|
||||
EventConfig acceptsCancelled(final boolean acceptsCancelled);
|
||||
|
||||
/**
|
||||
* Gets if only the exact event type is accepted.
|
||||
*
|
||||
* @return if only the exact event type is accepted
|
||||
*/
|
||||
boolean exact();
|
||||
|
||||
/**
|
||||
* Sets if only the exact event type is accepted.
|
||||
*
|
||||
* @param exact if only the exact event type is accepted
|
||||
* @return an {@link EventConfig}
|
||||
*/
|
||||
EventConfig exact(final boolean exact);
|
||||
|
||||
static EventConfig defaults() {
|
||||
return EventConfigImpl.DEFAULTS;
|
||||
}
|
||||
|
||||
static EventConfig of(
|
||||
final int order,
|
||||
final boolean acceptsCancelled,
|
||||
final boolean exact
|
||||
) {
|
||||
return new EventConfigImpl(order, acceptsCancelled, exact);
|
||||
}
|
||||
|
||||
static Builder builder() {
|
||||
return new EventConfigImpl.BuilderImpl();
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder.
|
||||
*/
|
||||
interface Builder {
|
||||
/**
|
||||
* Sets the post order.
|
||||
*
|
||||
* @param order the post order
|
||||
* @return {@code this}
|
||||
*/
|
||||
Builder order(final int order);
|
||||
|
||||
/**
|
||||
* Sets if cancelled events are accepted.
|
||||
*
|
||||
* @param acceptsCancelled if cancelled events are accepted
|
||||
* @return {@code this}
|
||||
*/
|
||||
Builder acceptsCancelled(final boolean acceptsCancelled);
|
||||
|
||||
/**
|
||||
* Sets if only the exact event type is accepted.
|
||||
*
|
||||
* @param exact if only the exact event type is accepted
|
||||
* @return {@code this}
|
||||
*/
|
||||
Builder exact(final boolean exact);
|
||||
|
||||
/**
|
||||
* Builds.
|
||||
*
|
||||
* @return an {@link EventConfig}
|
||||
*/
|
||||
EventConfig build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* This file is part of event, licensed under the MIT License.
|
||||
*
|
||||
* Copyright (c) 2021-2023 Seiama
|
||||
*
|
||||
* 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 net.momirealms.customnameplates.common.event;
|
||||
|
||||
record EventConfigImpl(
|
||||
int order,
|
||||
boolean acceptsCancelled,
|
||||
boolean exact
|
||||
) implements EventConfig {
|
||||
static final EventConfigImpl DEFAULTS = new EventConfigImpl(DEFAULT_ORDER, DEFAULT_ACCEPTS_CANCELLED, DEFAULT_EXACT);
|
||||
|
||||
static EventConfigImpl create(
|
||||
final int order,
|
||||
final boolean acceptsCancelled,
|
||||
final boolean exact
|
||||
) {
|
||||
if (order == DEFAULT_ORDER && acceptsCancelled == DEFAULT_ACCEPTS_CANCELLED && exact == DEFAULT_EXACT) {
|
||||
return DEFAULTS;
|
||||
}
|
||||
return new EventConfigImpl(order, acceptsCancelled, exact);
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventConfig order(final int order) {
|
||||
return create(order, this.acceptsCancelled, this.exact);
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventConfig acceptsCancelled(final boolean acceptsCancelled) {
|
||||
return create(this.order, acceptsCancelled, this.exact);
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventConfig exact(final boolean exact) {
|
||||
return create(this.order, this.acceptsCancelled, exact);
|
||||
}
|
||||
|
||||
static final class BuilderImpl implements Builder {
|
||||
private int order = DEFAULT_ORDER;
|
||||
private boolean acceptsCancelled = DEFAULT_ACCEPTS_CANCELLED;
|
||||
private boolean exact = DEFAULT_EXACT;
|
||||
|
||||
@Override
|
||||
public Builder order(final int order) {
|
||||
this.order = order;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder acceptsCancelled(final boolean acceptsCancelled) {
|
||||
this.acceptsCancelled = acceptsCancelled;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder exact(final boolean exact) {
|
||||
this.exact = exact;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventConfig build() {
|
||||
return create(this.order, this.acceptsCancelled, this.exact);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* This file is part of event, licensed under the MIT License.
|
||||
*
|
||||
* Copyright (c) 2021-2023 Seiama
|
||||
*
|
||||
* 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 net.momirealms.customnameplates.common.event;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface EventConsumer<E> {
|
||||
|
||||
/**
|
||||
* Invokes this event consumer.
|
||||
*/
|
||||
void onEvent(final E event) throws Throwable;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package net.momirealms.customnameplates.common.event;
|
||||
|
||||
import net.momirealms.customnameplates.common.event.bus.EventBus;
|
||||
import net.momirealms.customnameplates.common.plugin.NameplatesPlugin;
|
||||
|
||||
import java.util.OptionalInt;
|
||||
|
||||
public interface EventManager {
|
||||
|
||||
class SingletonHolder {
|
||||
private static EventManager INSTANCE = null;
|
||||
}
|
||||
|
||||
static EventManager create(NameplatesPlugin plugin) {
|
||||
if (SingletonHolder.INSTANCE == null) {
|
||||
SingletonHolder.INSTANCE = new EventManagerImpl(plugin);
|
||||
}
|
||||
return SingletonHolder.INSTANCE;
|
||||
}
|
||||
|
||||
<T extends NameplatesEvent> EventSubscription<T> subscribe(Class<T> event, EventSubscriber<? super T> subscriber);
|
||||
|
||||
<T extends NameplatesEvent> EventSubscription<T> subscribe(Class<T> event, EventConfig config, EventSubscriber<? super T> subscriber);
|
||||
|
||||
NameplatesEvent dispatch(Class<? extends NameplatesEvent> eventClass, Object... params);
|
||||
|
||||
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
|
||||
NameplatesEvent dispatch(Class<? extends NameplatesEvent> eventClass, OptionalInt order, Object... params);
|
||||
|
||||
EventBus<?> getEventBus();
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package net.momirealms.customnameplates.common.event;
|
||||
|
||||
import net.momirealms.customnameplates.common.event.bus.EventBus;
|
||||
import net.momirealms.customnameplates.common.event.bus.SimpleEventBus;
|
||||
import net.momirealms.customnameplates.common.event.gen.EventGenerator;
|
||||
import net.momirealms.customnameplates.common.event.registry.EventRegistry;
|
||||
import net.momirealms.customnameplates.common.event.registry.SimpleEventRegistry;
|
||||
import net.momirealms.customnameplates.common.plugin.NameplatesPlugin;
|
||||
|
||||
import java.util.OptionalInt;
|
||||
|
||||
public class EventManagerImpl implements EventManager {
|
||||
|
||||
private final EventBus<NameplatesEvent> eventBus;
|
||||
private final EventRegistry<NameplatesEvent> registry;
|
||||
private final NameplatesPlugin plugin;
|
||||
|
||||
protected EventManagerImpl(NameplatesPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
this.registry = new SimpleEventRegistry<>(NameplatesEvent.class);
|
||||
this.eventBus = new SimpleEventBus<>(registry, new EventBus.EventExceptionHandler() {
|
||||
@Override
|
||||
public <E> void eventExceptionCaught(EventBus<? super E> bus, EventSubscription<? super E> subscription, E event, Throwable throwable) {
|
||||
plugin.getPluginLogger().severe("Exception caught in event handler", throwable);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends NameplatesEvent> EventSubscription<T> subscribe(final Class<T> event, final EventSubscriber<? super T> subscriber) {
|
||||
return registry.subscribe(event, subscriber);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends NameplatesEvent> EventSubscription<T> subscribe(final Class<T> event, EventConfig config, final EventSubscriber<? super T> subscriber) {
|
||||
return registry.subscribe(event, config, subscriber);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NameplatesEvent dispatch(Class<? extends NameplatesEvent> eventClass, Object... params) {
|
||||
NameplatesEvent event = generate(eventClass, params);
|
||||
this.eventBus.post(event, OptionalInt.empty());
|
||||
return event;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NameplatesEvent dispatch(Class<? extends NameplatesEvent> eventClass, OptionalInt order, Object... params) {
|
||||
NameplatesEvent event = generate(eventClass, params);
|
||||
this.eventBus.post(event, order);
|
||||
return event;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventBus<?> getEventBus() {
|
||||
return this.eventBus;
|
||||
}
|
||||
|
||||
private NameplatesEvent generate(Class<? extends NameplatesEvent> eventClass, Object... params) {
|
||||
try {
|
||||
return EventGenerator.generate(eventClass).newInstance(this.plugin, params);
|
||||
} catch (Throwable e) {
|
||||
throw new RuntimeException("Exception occurred whilst generating event instance", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* This file is part of event, licensed under the MIT License.
|
||||
*
|
||||
* Copyright (c) 2021-2023 Seiama
|
||||
*
|
||||
* 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 net.momirealms.customnameplates.common.event;
|
||||
|
||||
public interface EventSubscriber<E> extends EventConsumer<E> {
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* This file is part of event, licensed under the MIT License.
|
||||
*
|
||||
* Copyright (c) 2021-2023 Seiama
|
||||
*
|
||||
* 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 net.momirealms.customnameplates.common.event;
|
||||
|
||||
public interface EventSubscription<E> {
|
||||
|
||||
/**
|
||||
* Gets the event type.
|
||||
*/
|
||||
Class<E> event();
|
||||
|
||||
/**
|
||||
* Gets the configuration.
|
||||
*/
|
||||
EventConfig config();
|
||||
|
||||
/**
|
||||
* Gets the subscriber.
|
||||
*/
|
||||
EventSubscriber<? super E> subscriber();
|
||||
|
||||
/**
|
||||
* Disposes this subscription.
|
||||
*
|
||||
* <p>The subscriber held by this subscription will no longer receive events.</p>
|
||||
*/
|
||||
void dispose();
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package net.momirealms.customnameplates.common.event;
|
||||
|
||||
import net.momirealms.customnameplates.common.plugin.NameplatesPlugin;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public interface NameplatesEvent {
|
||||
|
||||
/**
|
||||
* Get the plugin instance this event was dispatched from
|
||||
*/
|
||||
@NotNull
|
||||
NameplatesPlugin plugin();
|
||||
|
||||
/**
|
||||
* Gets the type of the event.
|
||||
*
|
||||
* @return the type of the event
|
||||
*/
|
||||
@NotNull
|
||||
Class<? extends NameplatesEvent> eventType();
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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 net.momirealms.customnameplates.common.event;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Represents the position of a parameter within an event.
|
||||
*
|
||||
* <p>This is an implementation detail and should not be relied upon.</p>
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface Param {
|
||||
|
||||
/**
|
||||
* Gets the index of the parameter.
|
||||
*
|
||||
* @return the index
|
||||
*/
|
||||
int value();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* This file is part of event, licensed under the MIT License.
|
||||
*
|
||||
* Copyright (c) 2021-2023 Seiama
|
||||
*
|
||||
* 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 net.momirealms.customnameplates.common.event.bus;
|
||||
|
||||
import net.momirealms.customnameplates.common.event.EventSubscription;
|
||||
|
||||
import java.util.OptionalInt;
|
||||
|
||||
public interface EventBus<E> {
|
||||
|
||||
default void post(final E event) {
|
||||
this.post(event, OptionalInt.empty());
|
||||
}
|
||||
|
||||
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
|
||||
void post(final E event, final OptionalInt order);
|
||||
|
||||
@FunctionalInterface
|
||||
interface EventExceptionHandler {
|
||||
/**
|
||||
* Handles a caught exception.
|
||||
*/
|
||||
<E> void eventExceptionCaught(final EventBus<? super E> bus, final EventSubscription<? super E> subscription, final E event, final Throwable throwable);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* This file is part of event, licensed under the MIT License.
|
||||
*
|
||||
* Copyright (c) 2021-2023 Seiama
|
||||
*
|
||||
* 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 net.momirealms.customnameplates.common.event.bus;
|
||||
|
||||
import net.momirealms.customnameplates.common.event.Cancellable;
|
||||
import net.momirealms.customnameplates.common.event.EventConfig;
|
||||
import net.momirealms.customnameplates.common.event.EventSubscription;
|
||||
import net.momirealms.customnameplates.common.event.registry.EventRegistry;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.OptionalInt;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
|
||||
public class SimpleEventBus<E> implements EventBus<E> {
|
||||
protected final EventRegistry<E> registry;
|
||||
protected final EventExceptionHandler exceptions;
|
||||
|
||||
/**
|
||||
* Constructs a new {@code SimpleEventBus}.
|
||||
*
|
||||
* @param registry the event registry
|
||||
* @param exceptions the event exception handler
|
||||
*/
|
||||
public SimpleEventBus(final EventRegistry<E> registry, final EventBus.EventExceptionHandler exceptions) {
|
||||
this.registry = requireNonNull(registry, "registry");
|
||||
this.exceptions = requireNonNull(exceptions, "exceptions");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void post(final E event, final OptionalInt order) {
|
||||
@SuppressWarnings("unchecked")
|
||||
final Class<? extends E> type = (Class<? extends E>) event.getClass();
|
||||
final List<EventSubscription<? super E>> subscriptions = this.registry.subscriptions(type);
|
||||
if (subscriptions.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
for (final EventSubscription<? super E> subscription : subscriptions) {
|
||||
if (this.accepts(subscription, event, order)) {
|
||||
try {
|
||||
subscription.subscriber().onEvent(event);
|
||||
} catch (final Throwable t) {
|
||||
this.exceptions.eventExceptionCaught(this, subscription, event, t);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("RedundantIfStatement")
|
||||
protected boolean accepts(final EventSubscription<? super E> subscription, final E event, final OptionalInt order) {
|
||||
final EventConfig config = subscription.config();
|
||||
|
||||
if (config.exact()) {
|
||||
if (event.getClass() != subscription.event()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (order.isPresent()) {
|
||||
if (config.order() != order.getAsInt()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!config.acceptsCancelled()) {
|
||||
if (this.currentlyCancelled(event)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if {@code event} is cancelled.
|
||||
*
|
||||
* @param event the event
|
||||
* @return {@code true} if the event is cancelled, {@code false} otherwise
|
||||
*/
|
||||
protected boolean currentlyCancelled(final E event) {
|
||||
return event instanceof Cancellable && ((Cancellable) event).cancelled();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package net.momirealms.customnameplates.common.event.gen;
|
||||
|
||||
import net.momirealms.customnameplates.common.event.NameplatesEvent;
|
||||
import net.momirealms.customnameplates.common.plugin.NameplatesPlugin;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.invoke.MethodHandles;
|
||||
|
||||
public abstract class AbstractNameplatesEvent implements NameplatesEvent {
|
||||
|
||||
private final NameplatesPlugin plugin;
|
||||
|
||||
protected AbstractNameplatesEvent(NameplatesPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public NameplatesPlugin plugin() {
|
||||
return plugin;
|
||||
}
|
||||
|
||||
public MethodHandles.Lookup mhl() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
/*
|
||||
* 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 net.momirealms.customnameplates.common.event.gen;
|
||||
|
||||
import net.bytebuddy.ByteBuddy;
|
||||
import net.bytebuddy.ClassFileVersion;
|
||||
import net.bytebuddy.description.NamedElement;
|
||||
import net.bytebuddy.description.modifier.Visibility;
|
||||
import net.bytebuddy.description.type.TypeDescription;
|
||||
import net.bytebuddy.dynamic.DynamicType;
|
||||
import net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy;
|
||||
import net.bytebuddy.implementation.FieldAccessor;
|
||||
import net.bytebuddy.implementation.FixedValue;
|
||||
import net.bytebuddy.implementation.MethodCall;
|
||||
import net.momirealms.customnameplates.common.event.Cancellable;
|
||||
import net.momirealms.customnameplates.common.event.NameplatesEvent;
|
||||
import net.momirealms.customnameplates.common.event.Param;
|
||||
import net.momirealms.customnameplates.common.plugin.NameplatesPlugin;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static net.bytebuddy.matcher.ElementMatchers.*;
|
||||
|
||||
public class EventGenerator {
|
||||
|
||||
/**
|
||||
* A loading cache of event types to {@link EventGenerator}s.
|
||||
*/
|
||||
private static final Map<Class<? extends NameplatesEvent>, EventGenerator> CACHE = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Generate a {@link EventGenerator} for the given {@code event} type.
|
||||
*
|
||||
* @param event the event type
|
||||
* @return the generated class
|
||||
*/
|
||||
public static EventGenerator generate(Class<? extends NameplatesEvent> event) {
|
||||
if (CACHE.containsKey(event)) {
|
||||
return CACHE.get(event);
|
||||
} else {
|
||||
try {
|
||||
var generator = new EventGenerator(event);
|
||||
CACHE.put(event, generator);
|
||||
return generator;
|
||||
} catch (Throwable throwable) {
|
||||
throw new RuntimeException(throwable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A method handle for the constructor of the event class.
|
||||
*/
|
||||
private final MethodHandle constructor;
|
||||
|
||||
/**
|
||||
* An array of {@link MethodHandle}s, which can set values for each of the properties in the event class.
|
||||
*/
|
||||
private final MethodHandle[] setters;
|
||||
|
||||
private EventGenerator(Class<? extends NameplatesEvent> eventClass) throws Throwable {
|
||||
// get a TypeDescription for the event class
|
||||
TypeDescription eventClassType = new TypeDescription.ForLoadedType(eventClass);
|
||||
|
||||
// determine a generated class name of the event
|
||||
String eventClassSuffix = eventClass.getName().substring(NameplatesEvent.class.getPackage().getName().length());
|
||||
String packageWithName = EventGenerator.class.getName();
|
||||
String generatedClassName = packageWithName.substring(0, packageWithName.lastIndexOf('.')) + eventClassSuffix;
|
||||
|
||||
DynamicType.Builder<AbstractNameplatesEvent> builder = new ByteBuddy(ClassFileVersion.JAVA_V17)
|
||||
// create a subclass of AbstractEvent
|
||||
.subclass(AbstractNameplatesEvent.class, ConstructorStrategy.Default.IMITATE_SUPER_CLASS_OPENING)
|
||||
// using the predetermined generated class name
|
||||
.name(generatedClassName)
|
||||
// implement the event interface
|
||||
.implement(eventClassType)
|
||||
// implement all methods annotated with Param by simply returning the value from the corresponding field with the same name
|
||||
.method(isAnnotatedWith(Param.class))
|
||||
.intercept(FieldAccessor.of(NamedElement.WithRuntimeName::getInternalName))
|
||||
// implement NameplatesEvent#eventType by returning the event class type
|
||||
.method(named("eventType").and(returns(Class.class)).and(takesArguments(0)))
|
||||
.intercept(FixedValue.value(eventClassType))
|
||||
// implement AbstractEvent#mh by calling & returning the value of MethodHandles.lookup()
|
||||
.method(named("mhl").and(returns(MethodHandles.Lookup.class)).and(takesArguments(0)))
|
||||
.intercept(MethodCall.invoke(MethodHandles.class.getMethod("lookup")))
|
||||
// implement a toString method
|
||||
.withToString();
|
||||
|
||||
if (Cancellable.class.isAssignableFrom(eventClass)) {
|
||||
builder = builder
|
||||
.defineField("cancelled", boolean.class, Visibility.PRIVATE)
|
||||
.method(named("cancelled").and(returns(boolean.class)).and(takesArguments(0)))
|
||||
.intercept(FieldAccessor.ofField("cancelled"))
|
||||
.method(named("cancelled").and(takesArguments(boolean.class)))
|
||||
.intercept(FieldAccessor.ofField("cancelled"));
|
||||
}
|
||||
|
||||
// get a sorted array of all methods on the event interface annotated with @Param
|
||||
Method[] properties = Arrays.stream(eventClass.getMethods())
|
||||
.filter(m -> m.isAnnotationPresent(Param.class))
|
||||
.sorted(Comparator.comparingInt(o -> o.getAnnotation(Param.class).value()))
|
||||
.toArray(Method[]::new);
|
||||
|
||||
// for each property, define a field on the generated class to hold the value
|
||||
for (Method method : properties) {
|
||||
builder = builder.defineField(method.getName(), method.getReturnType(), Visibility.PRIVATE);
|
||||
}
|
||||
|
||||
// finish building, load the class, get a constructor
|
||||
Class<? extends AbstractNameplatesEvent> generatedClass = builder.make().load(EventGenerator.class.getClassLoader()).getLoaded();
|
||||
this.constructor = MethodHandles.publicLookup().in(generatedClass)
|
||||
.findConstructor(generatedClass, MethodType.methodType(void.class, NameplatesPlugin.class))
|
||||
.asType(MethodType.methodType(AbstractNameplatesEvent.class, NameplatesPlugin.class));
|
||||
|
||||
// create a dummy instance of the generated class & get the method handle lookup instance
|
||||
MethodHandles.Lookup lookup = ((AbstractNameplatesEvent) this.constructor.invoke((Object) null)).mhl();
|
||||
|
||||
// get 'setter' MethodHandles for each property
|
||||
this.setters = new MethodHandle[properties.length];
|
||||
for (int i = 0; i < properties.length; i++) {
|
||||
Method method = properties[i];
|
||||
this.setters[i] = lookup.findSetter(generatedClass, method.getName(), method.getReturnType())
|
||||
.asType(MethodType.methodType(void.class, new Class[]{AbstractNameplatesEvent.class, Object.class}));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance of the event class.
|
||||
*/
|
||||
public NameplatesEvent newInstance(NameplatesPlugin plugin, Object... properties) throws Throwable {
|
||||
if (properties.length != this.setters.length) {
|
||||
throw new IllegalStateException("Unexpected number of properties. given: " + properties.length + ", expected: " + this.setters.length);
|
||||
}
|
||||
|
||||
// create a new instance of the event
|
||||
final AbstractNameplatesEvent event = (AbstractNameplatesEvent) this.constructor.invokeExact(plugin);
|
||||
|
||||
// set the properties onto the event instance
|
||||
for (int i = 0; i < this.setters.length; i++) {
|
||||
MethodHandle setter = this.setters[i];
|
||||
Object value = properties[i];
|
||||
setter.invokeExact(event, value);
|
||||
}
|
||||
|
||||
return event;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* This file is part of event, licensed under the MIT License.
|
||||
*
|
||||
* Copyright (c) 2021-2023 Seiama
|
||||
*
|
||||
* 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 net.momirealms.customnameplates.common.event.registry;
|
||||
|
||||
import net.momirealms.customnameplates.common.event.EventConfig;
|
||||
import net.momirealms.customnameplates.common.event.EventSubscriber;
|
||||
import net.momirealms.customnameplates.common.event.EventSubscription;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public interface EventRegistry<E> {
|
||||
|
||||
/**
|
||||
* Gets the base event type.
|
||||
*
|
||||
* <p>This is represented by the {@code E} type parameter.</p>
|
||||
*/
|
||||
Class<E> type();
|
||||
|
||||
/**
|
||||
* Determines whether the specified event has subscribers.
|
||||
*
|
||||
* @param event the event type
|
||||
* @return whether the specified event has subscribers
|
||||
*/
|
||||
default boolean subscribed(final Class<? extends E> event) {
|
||||
return !this.subscriptions(event).isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the given {@code subscriber} to receive events, using the default {@link EventConfig configuration}.
|
||||
*
|
||||
* @param event the event type
|
||||
* @param subscriber the subscriber
|
||||
* @param <T> the event type
|
||||
*/
|
||||
default <T extends E> EventSubscription<T> subscribe(final Class<T> event, final EventSubscriber<? super T> subscriber) {
|
||||
return this.subscribe(event, EventConfig.defaults(), subscriber);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the given {@code subscriber} to receive events.
|
||||
*
|
||||
* @param event the event type
|
||||
* @param config the event configuration
|
||||
* @param subscriber the subscriber
|
||||
* @param <T> the event type
|
||||
*/
|
||||
<T extends E> EventSubscription<T> subscribe(final Class<T> event, final EventConfig config, final EventSubscriber<? super T> subscriber);
|
||||
|
||||
/**
|
||||
* Removes subscriptions matching {@code predicate}.
|
||||
*
|
||||
* @param predicate the predicate used to determine which subscriptions to remove
|
||||
*/
|
||||
void unsubscribeIf(final Predicate<EventSubscription<? super E>> predicate);
|
||||
|
||||
/**
|
||||
* Gets an unmodifiable list containing all subscriptions currently registered for events of type {@code event}.
|
||||
*
|
||||
* @return a list of all subscriptions for events of type {@code event}
|
||||
*/
|
||||
List<EventSubscription<? super E>> subscriptions(final Class<? extends E> event);
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* This file is part of event, licensed under the MIT License.
|
||||
*
|
||||
* Copyright (c) 2021-2023 Seiama
|
||||
*
|
||||
* 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 net.momirealms.customnameplates.common.event.registry;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
final class Internals {
|
||||
private Internals() {
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
static <T> List<Class<? super T>> ancestors(final Class<T> type) {
|
||||
final List<Class<? super T>> types = new ArrayList<>();
|
||||
types.add(type);
|
||||
for (int i = 0; i < types.size(); i++) {
|
||||
final Class<?> next = types.get(i);
|
||||
final Class<?> superclass = next.getSuperclass();
|
||||
if (superclass != null) {
|
||||
types.add((Class<? super T>) superclass);
|
||||
}
|
||||
final Class<?>[] interfaces = next.getInterfaces();
|
||||
for (final Class<?> iface : interfaces) {
|
||||
// we have a list because we want to preserve order, but we don't want duplicates
|
||||
if (!types.contains(iface)) {
|
||||
types.add((Class<? super T>) iface);
|
||||
}
|
||||
}
|
||||
}
|
||||
return types;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
* This file is part of event, licensed under the MIT License.
|
||||
*
|
||||
* Copyright (c) 2021-2023 Seiama
|
||||
*
|
||||
* 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 net.momirealms.customnameplates.common.event.registry;
|
||||
|
||||
import net.momirealms.customnameplates.common.event.EventConfig;
|
||||
import net.momirealms.customnameplates.common.event.EventSubscriber;
|
||||
import net.momirealms.customnameplates.common.event.EventSubscription;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
public class SimpleEventRegistry<E> implements EventRegistry<E> {
|
||||
private static final Comparator<EventSubscription<?>> ORDER_COMPARATOR = Comparator.comparingInt(subscription -> subscription.config().order());
|
||||
|
||||
private final Map<Class<? extends E>, Collection<? extends Class<?>>> classes = new HashMap<>();
|
||||
|
||||
private final Map<Class<? extends E>, List<EventSubscription<? super E>>> unbaked = new HashMap<>();
|
||||
private final Map<Class<? extends E>, List<EventSubscription<? super E>>> baked = new HashMap<>();
|
||||
|
||||
private final Object lock = new Object();
|
||||
|
||||
private final Class<E> type;
|
||||
|
||||
/**
|
||||
* Constructs a new {@code SimpleEventRegistry}.
|
||||
*
|
||||
* @param type the base event type
|
||||
*/
|
||||
public SimpleEventRegistry(final Class<E> type) {
|
||||
this.type = requireNonNull(type, "type");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<E> type() {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends E> EventSubscription<T> subscribe(final Class<T> event, final EventConfig config, final EventSubscriber<? super T> subscriber) {
|
||||
requireNonNull(event, "event");
|
||||
requireNonNull(config, "config");
|
||||
requireNonNull(subscriber, "subscriber");
|
||||
final EventSubscription<T> subscription = new EventSubscriptionImpl<>(event, config, subscriber);
|
||||
synchronized (this.lock) {
|
||||
final List<EventSubscription<? super T>> subscriptions = yayGenerics(this.unbaked.computeIfAbsent(event, key -> new ArrayList<>()));
|
||||
subscriptions.add(subscription);
|
||||
this.baked.clear();
|
||||
}
|
||||
return subscription;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unsubscribeIf(final Predicate<EventSubscription<? super E>> predicate) {
|
||||
synchronized (this.lock) {
|
||||
boolean removedAny = false;
|
||||
for (final List<EventSubscription<? super E>> subscriptions : this.unbaked.values()) {
|
||||
removedAny |= subscriptions.removeIf(predicate);
|
||||
}
|
||||
if (removedAny) {
|
||||
this.baked.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<EventSubscription<? super E>> subscriptions(final Class<? extends E> event) {
|
||||
synchronized (this.lock) {
|
||||
return this.baked.computeIfAbsent(event, this::computeSubscriptions);
|
||||
}
|
||||
}
|
||||
|
||||
private List<EventSubscription<? super E>> computeSubscriptions(final Class<? extends E> event) {
|
||||
final List<EventSubscription<? super E>> subscriptions = new ArrayList<>();
|
||||
final Collection<? extends Class<?>> types = this.classes.computeIfAbsent(event, this::findClasses);
|
||||
for (final Class<?> type : types) {
|
||||
subscriptions.addAll(this.unbaked.getOrDefault(type, Collections.emptyList()));
|
||||
}
|
||||
subscriptions.sort(ORDER_COMPARATOR);
|
||||
return subscriptions;
|
||||
}
|
||||
|
||||
private Collection<? extends Class<?>> findClasses(final Class<?> type) {
|
||||
final Collection<? extends Class<?>> classes = Internals.ancestors(type);
|
||||
classes.removeIf(klass -> !this.type.isAssignableFrom(klass));
|
||||
return classes;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <T extends U, U> List<U> yayGenerics(final List<T> list) {
|
||||
return (List<U>) list;
|
||||
}
|
||||
|
||||
private class EventSubscriptionImpl<T extends E> implements EventSubscription<T> {
|
||||
private final Class<T> event;
|
||||
private final EventConfig config;
|
||||
private final EventSubscriber<? super T> subscriber;
|
||||
|
||||
EventSubscriptionImpl(final Class<T> event, final EventConfig config, final EventSubscriber<? super T> subscriber) {
|
||||
this.event = event;
|
||||
this.config = config;
|
||||
this.subscriber = subscriber;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<T> event() {
|
||||
return this.event;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventConfig config() {
|
||||
return this.config;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventSubscriber<? super T> subscriber() {
|
||||
return this.subscriber;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
synchronized (SimpleEventRegistry.this.lock) {
|
||||
final @Nullable List<EventSubscription<? super T>> subscriptions = yayGenerics(SimpleEventRegistry.this.unbaked.get(this.event));
|
||||
if (subscriptions != null) {
|
||||
subscriptions.remove(this);
|
||||
SimpleEventRegistry.this.baked.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new StringJoiner(", ", this.getClass().getSimpleName() + "[", "]")
|
||||
.add("event=" + this.event)
|
||||
.add("config=" + this.config)
|
||||
.add("subscriber=" + this.subscriber)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright (C) <2024> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customnameplates.common.locale;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.incendo.cloud.caption.Caption;
|
||||
import org.incendo.cloud.caption.CaptionVariable;
|
||||
import org.incendo.cloud.minecraft.extras.caption.ComponentCaptionFormatter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class CustomNameplatesCaptionFormatter<C> implements ComponentCaptionFormatter<C> {
|
||||
|
||||
@Override
|
||||
public @NonNull Component formatCaption(@NonNull Caption captionKey, @NonNull C recipient, @NonNull String caption, @NonNull List<@NonNull CaptionVariable> variables) {
|
||||
Component component = ComponentCaptionFormatter.translatable().formatCaption(captionKey, recipient, caption, variables);
|
||||
return TranslationManager.render(component);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright (C) <2024> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customnameplates.common.locale;
|
||||
|
||||
import org.incendo.cloud.caption.Caption;
|
||||
|
||||
public final class CustomNameplatesCaptionKeys {
|
||||
|
||||
public static final Caption ARGUMENT_PARSE_FAILURE_TIME = Caption.of("argument.parse.failure.time");
|
||||
public static final Caption ARGUMENT_PARSE_FAILURE_URL = Caption.of("argument.parse.failure.url");
|
||||
public static final Caption ARGUMENT_PARSE_FAILURE_NAMEDTEXTCOLOR = Caption.of("argument.parse.failure.namedtextcolor");
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright (C) <2024> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customnameplates.common.locale;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.incendo.cloud.caption.CaptionProvider;
|
||||
import org.incendo.cloud.caption.DelegatingCaptionProvider;
|
||||
|
||||
public final class CustomNameplatesCaptionProvider<C> extends DelegatingCaptionProvider<C> {
|
||||
|
||||
private static final CaptionProvider<?> PROVIDER = CaptionProvider.constantProvider()
|
||||
.putCaption(CustomNameplatesCaptionKeys.ARGUMENT_PARSE_FAILURE_URL, "")
|
||||
.putCaption(CustomNameplatesCaptionKeys.ARGUMENT_PARSE_FAILURE_TIME, "")
|
||||
.putCaption(CustomNameplatesCaptionKeys.ARGUMENT_PARSE_FAILURE_NAMEDTEXTCOLOR, "")
|
||||
.build();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public @NonNull CaptionProvider<C> delegate() {
|
||||
return (CaptionProvider<C>) PROVIDER;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright (C) <2024> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customnameplates.common.locale;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.TranslatableComponent;
|
||||
|
||||
public interface MessageConstants {
|
||||
|
||||
TranslatableComponent.Builder COMMAND_RELOAD_SUCCESS = Component.translatable().key("command.reload.success");
|
||||
TranslatableComponent.Builder COMMAND_RELOAD_GENERATING = Component.translatable().key("command.reload.generating");
|
||||
TranslatableComponent.Builder COMMAND_RELOAD_GENERATED = Component.translatable().key("command.reload.generated");
|
||||
TranslatableComponent.Builder COMMAND_DEBUG_PERFORMANCE = Component.translatable().key("command.debug.performance");
|
||||
|
||||
TranslatableComponent.Builder COMMAND_NAMEPLATES_EQUIP_FAILURE_NOT_EXISTS = Component.translatable().key("command.nameplates.equip.failure.not_exists");
|
||||
TranslatableComponent.Builder COMMAND_NAMEPLATES_EQUIP_FAILURE_PERMISSION = Component.translatable().key("command.nameplates.equip.failure.permission");
|
||||
TranslatableComponent.Builder COMMAND_NAMEPLATES_EQUIP_FAILURE_NO_CHANGE = Component.translatable().key("command.nameplates.equip.failure.no_change");
|
||||
TranslatableComponent.Builder COMMAND_NAMEPLATES_EQUIP_SUCCESS = Component.translatable().key("command.nameplates.equip.success");
|
||||
TranslatableComponent.Builder COMMAND_NAMEPLATES_UNEQUIP_FAILURE_NOT_EQUIP = Component.translatable().key("command.nameplates.unequip.failure.not_equip");
|
||||
TranslatableComponent.Builder COMMAND_NAMEPLATES_UNEQUIP_SUCCESS = Component.translatable().key("command.nameplates.unequip.success");
|
||||
TranslatableComponent.Builder COMMAND_NAMEPLATES_PREVIEW_FAILURE_COOLDOWN = Component.translatable().key("command.nameplates.preview.failure.cooldown");
|
||||
TranslatableComponent.Builder COMMAND_NAMEPLATES_PREVIEW_SUCCESS = Component.translatable().key("command.nameplates.preview.success");
|
||||
TranslatableComponent.Builder COMMAND_NAMEPLATES_LIST_FAILURE_NONE = Component.translatable().key("command.nameplates.list.failure.none");
|
||||
TranslatableComponent.Builder COMMAND_NAMEPLATES_LIST_SUCCESS = Component.translatable().key("command.nameplates.list.success");
|
||||
TranslatableComponent.Builder COMMAND_NAMEPLATES_LIST_DELIMITER = Component.translatable().key("command.nameplates.list.delimiter");
|
||||
|
||||
TranslatableComponent.Builder COMMAND_NAMEPLATES_FORCE_EQUIP_SUCCESS = Component.translatable().key("command.nameplates.force_equip.success");
|
||||
TranslatableComponent.Builder COMMAND_NAMEPLATES_FORCE_EQUIP_FAILURE_NOT_EXISTS = Component.translatable().key("command.nameplates.force_equip.failure.not_exists");
|
||||
TranslatableComponent.Builder COMMAND_NAMEPLATES_FORCE_UNEQUIP_SUCCESS = Component.translatable().key("command.nameplates.force_unequip.success");
|
||||
TranslatableComponent.Builder COMMAND_NAMEPLATES_FORCE_PREVIEW_SUCCESS = Component.translatable().key("command.nameplates.force_preview.success");
|
||||
|
||||
TranslatableComponent.Builder COMMAND_BUBBLES_EQUIP_FAILURE_NOT_EXISTS = Component.translatable().key("command.bubbles.equip.failure.not_exists");
|
||||
TranslatableComponent.Builder COMMAND_BUBBLES_EQUIP_FAILURE_PERMISSION = Component.translatable().key("command.bubbles.equip.failure.permission");
|
||||
TranslatableComponent.Builder COMMAND_BUBBLES_EQUIP_FAILURE_NO_CHANGE = Component.translatable().key("command.bubbles.equip.failure.no_change");
|
||||
TranslatableComponent.Builder COMMAND_BUBBLES_EQUIP_SUCCESS = Component.translatable().key("command.bubbles.equip.success");
|
||||
TranslatableComponent.Builder COMMAND_BUBBLES_UNEQUIP_FAILURE_NOT_EQUIP = Component.translatable().key("command.bubbles.unequip.failure.not_equip");
|
||||
TranslatableComponent.Builder COMMAND_BUBBLES_UNEQUIP_SUCCESS = Component.translatable().key("command.bubbles.unequip.success");
|
||||
TranslatableComponent.Builder COMMAND_BUBBLES_LIST_FAILURE_NONE = Component.translatable().key("command.bubbles.list.failure.none");
|
||||
TranslatableComponent.Builder COMMAND_BUBBLES_LIST_SUCCESS = Component.translatable().key("command.bubbles.list.success");
|
||||
TranslatableComponent.Builder COMMAND_BUBBLES_LIST_DELIMITER = Component.translatable().key("command.bubbles.list.delimiter");
|
||||
|
||||
TranslatableComponent.Builder COMMAND_BUBBLES_FORCE_EQUIP_SUCCESS = Component.translatable().key("command.bubbles.force_equip.success");
|
||||
TranslatableComponent.Builder COMMAND_BUBBLES_FORCE_EQUIP_FAILURE_NOT_EXISTS = Component.translatable().key("command.bubbles.force_equip.failure.not_exists");
|
||||
TranslatableComponent.Builder COMMAND_BUBBLES_FORCE_UNEQUIP_SUCCESS = Component.translatable().key("command.bubbles.force_unequip.success");
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright (C) <2024> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customnameplates.common.locale;
|
||||
|
||||
import net.kyori.adventure.key.Key;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import net.kyori.adventure.translation.Translator;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
public interface MiniMessageTranslationRegistry extends Translator {
|
||||
|
||||
static @NotNull MiniMessageTranslationRegistry create(final Key name, final MiniMessage miniMessage) {
|
||||
return new MiniMessageTranslationRegistryImpl(requireNonNull(name, "name"), requireNonNull(miniMessage, "MiniMessage"));
|
||||
}
|
||||
|
||||
void register(@NotNull String key, @NotNull Locale locale, @NotNull String format);
|
||||
|
||||
void unregister(@NotNull String key);
|
||||
|
||||
boolean contains(@NotNull String key);
|
||||
|
||||
String miniMessageTranslation(@NotNull String key, @NotNull Locale locale);
|
||||
|
||||
void defaultLocale(@NotNull Locale defaultLocale);
|
||||
|
||||
default void registerAll(final @NotNull Locale locale, final @NotNull Map<String, String> bundle) {
|
||||
this.registerAll(locale, bundle.keySet(), bundle::get);
|
||||
}
|
||||
|
||||
default void registerAll(final @NotNull Locale locale, final @NotNull Set<String> keys, final Function<String, String> function) {
|
||||
IllegalArgumentException firstError = null;
|
||||
int errorCount = 0;
|
||||
for (final String key : keys) {
|
||||
try {
|
||||
this.register(key, locale, function.apply(key));
|
||||
} catch (final IllegalArgumentException e) {
|
||||
if (firstError == null) {
|
||||
firstError = e;
|
||||
}
|
||||
errorCount++;
|
||||
}
|
||||
}
|
||||
if (firstError != null) {
|
||||
if (errorCount == 1) {
|
||||
throw firstError;
|
||||
} else if (errorCount > 1) {
|
||||
throw new IllegalArgumentException(String.format("Invalid key (and %d more)", errorCount - 1), firstError);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
/*
|
||||
* Copyright (C) <2024> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customnameplates.common.locale;
|
||||
|
||||
import net.kyori.adventure.internal.Internals;
|
||||
import net.kyori.adventure.key.Key;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.ComponentLike;
|
||||
import net.kyori.adventure.text.TranslatableComponent;
|
||||
import net.kyori.adventure.text.minimessage.Context;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import net.kyori.adventure.text.minimessage.ParsingException;
|
||||
import net.kyori.adventure.text.minimessage.tag.Tag;
|
||||
import net.kyori.adventure.text.minimessage.tag.resolver.ArgumentQueue;
|
||||
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
|
||||
import net.kyori.adventure.util.TriState;
|
||||
import net.kyori.examination.Examinable;
|
||||
import net.kyori.examination.ExaminableProperty;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.text.MessageFormat;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
public class MiniMessageTranslationRegistryImpl implements Examinable, MiniMessageTranslationRegistry {
|
||||
private final Key name;
|
||||
private final Map<String, Translation> translations = new ConcurrentHashMap<>();
|
||||
private Locale defaultLocale = Locale.US;
|
||||
private final MiniMessage miniMessage;
|
||||
|
||||
MiniMessageTranslationRegistryImpl(final Key name, final MiniMessage miniMessage) {
|
||||
this.name = name;
|
||||
this.miniMessage = miniMessage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void register(final @NotNull String key, final @NotNull Locale locale, final @NotNull String format) {
|
||||
this.translations.computeIfAbsent(key, Translation::new).register(locale, format);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregister(final @NotNull String key) {
|
||||
this.translations.remove(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(final @NotNull String key) {
|
||||
return this.translations.containsKey(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Key name() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable MessageFormat translate(@NotNull String key, @NotNull Locale locale) {
|
||||
// No need to implement this method
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Component translate(@NotNull TranslatableComponent component, @NotNull Locale locale) {
|
||||
Translation translation = translations.get(component.key());
|
||||
if (translation == null) {
|
||||
return null;
|
||||
}
|
||||
String miniMessageString = translation.translate(locale);
|
||||
if (miniMessageString == null) {
|
||||
return null;
|
||||
}
|
||||
if (miniMessageString.isEmpty()) {
|
||||
return Component.empty();
|
||||
}
|
||||
final Component resultingComponent;
|
||||
if (component.arguments().isEmpty()) {
|
||||
resultingComponent = this.miniMessage.deserialize(miniMessageString);
|
||||
} else {
|
||||
resultingComponent = this.miniMessage.deserialize(miniMessageString, new ArgumentTag(component.arguments()));
|
||||
}
|
||||
if (component.children().isEmpty()) {
|
||||
return resultingComponent;
|
||||
} else {
|
||||
return resultingComponent.children(component.children());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String miniMessageTranslation(@NotNull String key, @NotNull Locale locale) {
|
||||
Translation translation = translations.get(key);
|
||||
if (translation == null) {
|
||||
return null;
|
||||
}
|
||||
return translation.translate(locale);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull TriState hasAnyTranslations() {
|
||||
if (!this.translations.isEmpty()) {
|
||||
return TriState.TRUE;
|
||||
}
|
||||
return TriState.FALSE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void defaultLocale(final @NotNull Locale defaultLocale) {
|
||||
this.defaultLocale = requireNonNull(defaultLocale, "defaultLocale");
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Stream<? extends ExaminableProperty> examinableProperties() {
|
||||
return Stream.of(ExaminableProperty.of("translations", this.translations));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object other) {
|
||||
if (this == other) return true;
|
||||
if (!(other instanceof MiniMessageTranslationRegistryImpl that)) return false;
|
||||
return this.name.equals(that.name)
|
||||
&& this.translations.equals(that.translations)
|
||||
&& this.defaultLocale.equals(that.defaultLocale);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(this.name, this.translations, this.defaultLocale);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return Internals.toString(this);
|
||||
}
|
||||
|
||||
public static class ArgumentTag implements TagResolver {
|
||||
private static final String NAME = "argument";
|
||||
private static final String NAME_1 = "arg";
|
||||
|
||||
private final List<? extends ComponentLike> argumentComponents;
|
||||
|
||||
public ArgumentTag(final @NotNull List<? extends ComponentLike> argumentComponents) {
|
||||
this.argumentComponents = Objects.requireNonNull(argumentComponents, "argumentComponents");
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Tag resolve(final @NotNull String name, final @NotNull ArgumentQueue arguments, final @NotNull Context ctx) throws ParsingException {
|
||||
final int index = arguments.popOr("No argument number provided").asInt().orElseThrow(() -> ctx.newException("Invalid argument number", arguments));
|
||||
|
||||
if (index < 0 || index >= argumentComponents.size()) {
|
||||
throw ctx.newException("Invalid argument number", arguments);
|
||||
}
|
||||
|
||||
return Tag.inserting(argumentComponents.get(index));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean has(final @NotNull String name) {
|
||||
return name.equals(NAME) || name.equals(NAME_1);
|
||||
}
|
||||
}
|
||||
|
||||
final class Translation implements Examinable {
|
||||
private final String key;
|
||||
private final Map<Locale, String> formats;
|
||||
|
||||
Translation(final @NotNull String key) {
|
||||
this.key = requireNonNull(key, "translation key");
|
||||
this.formats = new ConcurrentHashMap<>();
|
||||
}
|
||||
|
||||
void register(final @NotNull Locale locale, final @NotNull String format) {
|
||||
if (this.formats.putIfAbsent(requireNonNull(locale, "locale"), requireNonNull(format, "message format")) != null) {
|
||||
throw new IllegalArgumentException(String.format("Translation already exists: %s for %s", this.key, locale));
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable String translate(final @NotNull Locale locale) {
|
||||
String format = this.formats.get(requireNonNull(locale, "locale"));
|
||||
if (format == null) {
|
||||
format = this.formats.get(new Locale(locale.getLanguage())); // try without country
|
||||
if (format == null) {
|
||||
format = this.formats.get(MiniMessageTranslationRegistryImpl.this.defaultLocale); // try local default locale
|
||||
}
|
||||
}
|
||||
return format;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Stream<? extends ExaminableProperty> examinableProperties() {
|
||||
return Stream.of(
|
||||
ExaminableProperty.of("key", this.key),
|
||||
ExaminableProperty.of("formats", this.formats)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object other) {
|
||||
if (this == other) return true;
|
||||
if (!(other instanceof Translation that)) return false;
|
||||
return this.key.equals(that.key) &&
|
||||
this.formats.equals(that.formats);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(this.key, this.formats);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return Internals.toString(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright (C) <2024> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customnameplates.common.locale;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.renderer.TranslatableComponentRenderer;
|
||||
import net.kyori.adventure.translation.Translator;
|
||||
import net.kyori.examination.Examinable;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public interface MiniMessageTranslator extends Translator, Examinable {
|
||||
|
||||
static @NotNull MiniMessageTranslator translator() {
|
||||
return MiniMessageTranslatorImpl.INSTANCE;
|
||||
}
|
||||
|
||||
static @NotNull TranslatableComponentRenderer<Locale> renderer() {
|
||||
return MiniMessageTranslatorImpl.INSTANCE.renderer;
|
||||
}
|
||||
|
||||
static @NotNull Component render(final @NotNull Component component, final @NotNull Locale locale) {
|
||||
return renderer().render(component, locale);
|
||||
}
|
||||
|
||||
@NotNull Iterable<? extends Translator> sources();
|
||||
|
||||
boolean addSource(final @NotNull Translator source);
|
||||
|
||||
boolean removeSource(final @NotNull Translator source);
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright (C) <2024> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customnameplates.common.locale;
|
||||
|
||||
import net.kyori.adventure.key.Key;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.TranslatableComponent;
|
||||
import net.kyori.adventure.text.renderer.TranslatableComponentRenderer;
|
||||
import net.kyori.adventure.translation.Translator;
|
||||
import net.kyori.adventure.util.TriState;
|
||||
import net.kyori.examination.ExaminableProperty;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.text.MessageFormat;
|
||||
import java.util.Collections;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class MiniMessageTranslatorImpl implements MiniMessageTranslator {
|
||||
|
||||
private static final Key NAME = Key.key("customfishing", "main");
|
||||
static final MiniMessageTranslatorImpl INSTANCE = new MiniMessageTranslatorImpl();
|
||||
final TranslatableComponentRenderer<Locale> renderer = TranslatableComponentRenderer.usingTranslationSource(this);
|
||||
private final Set<Translator> sources = Collections.newSetFromMap(new ConcurrentHashMap<>());
|
||||
|
||||
@Override
|
||||
public @NotNull Key name() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull TriState hasAnyTranslations() {
|
||||
if (!this.sources.isEmpty()) {
|
||||
return TriState.TRUE;
|
||||
}
|
||||
return TriState.FALSE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable MessageFormat translate(@NotNull String key, @NotNull Locale locale) {
|
||||
// No need to implement this method
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Component translate(@NotNull TranslatableComponent component, @NotNull Locale locale) {
|
||||
for (final Translator source : this.sources) {
|
||||
final Component translation = source.translate(component, locale);
|
||||
if (translation != null) return translation;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Iterable<? extends Translator> sources() {
|
||||
return Collections.unmodifiableSet(this.sources);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addSource(final @NotNull Translator source) {
|
||||
if (source == this) throw new IllegalArgumentException("MiniMessageTranslationSource");
|
||||
return this.sources.add(source);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeSource(final @NotNull Translator source) {
|
||||
return this.sources.remove(source);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Stream<? extends ExaminableProperty> examinableProperties() {
|
||||
return Stream.of(ExaminableProperty.of("sources", this.sources));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
/*
|
||||
* Copyright (C) <2024> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customnameplates.common.locale;
|
||||
|
||||
import dev.dejvokep.boostedyaml.YamlDocument;
|
||||
import net.kyori.adventure.key.Key;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import net.kyori.adventure.translation.Translator;
|
||||
import net.momirealms.customnameplates.common.plugin.NameplatesPlugin;
|
||||
import net.momirealms.customnameplates.common.util.Pair;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class TranslationManager {
|
||||
|
||||
private static final Locale DEFAULT_LOCALE = Locale.ENGLISH;
|
||||
private static Locale FORCE_LOCALE = null;
|
||||
private static final List<String> locales = List.of("en", "zh_cn");
|
||||
private static TranslationManager instance;
|
||||
|
||||
private final NameplatesPlugin plugin;
|
||||
private final Set<Locale> installed = ConcurrentHashMap.newKeySet();
|
||||
private MiniMessageTranslationRegistry registry;
|
||||
private final Path translationsDirectory;
|
||||
|
||||
public TranslationManager(NameplatesPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
this.translationsDirectory = this.plugin.getConfigDirectory().resolve("translations");
|
||||
instance = this;
|
||||
}
|
||||
|
||||
public static void forceLocale(Locale locale) {
|
||||
FORCE_LOCALE = locale;
|
||||
}
|
||||
|
||||
public void reload() {
|
||||
// remove any previous registry
|
||||
if (this.registry != null) {
|
||||
MiniMessageTranslator.translator().removeSource(this.registry);
|
||||
this.installed.clear();
|
||||
}
|
||||
for (String lang : locales) {
|
||||
this.plugin.getConfigManager().saveResource("translations/" + lang + ".yml");
|
||||
}
|
||||
|
||||
this.registry = MiniMessageTranslationRegistry.create(Key.key("customnameplates", "main"), MiniMessage.miniMessage());
|
||||
this.registry.defaultLocale(DEFAULT_LOCALE);
|
||||
this.loadFromFileSystem(this.translationsDirectory, false);
|
||||
MiniMessageTranslator.translator().addSource(this.registry);
|
||||
}
|
||||
|
||||
public static String miniMessageTranslation(String key) {
|
||||
return miniMessageTranslation(key, null);
|
||||
}
|
||||
|
||||
public static String miniMessageTranslation(String key, @Nullable Locale locale) {
|
||||
if (FORCE_LOCALE != null) {
|
||||
return instance.registry.miniMessageTranslation(key, FORCE_LOCALE);
|
||||
}
|
||||
if (locale == null) {
|
||||
locale = Locale.getDefault();
|
||||
if (locale == null) {
|
||||
locale = DEFAULT_LOCALE;
|
||||
}
|
||||
}
|
||||
return instance.registry.miniMessageTranslation(key, locale);
|
||||
}
|
||||
|
||||
public static Component render(Component component) {
|
||||
return render(component, null);
|
||||
}
|
||||
|
||||
public static Component render(Component component, @Nullable Locale locale) {
|
||||
if (FORCE_LOCALE != null) {
|
||||
return MiniMessageTranslator.render(component, FORCE_LOCALE);
|
||||
}
|
||||
if (locale == null) {
|
||||
locale = Locale.getDefault();
|
||||
if (locale == null) {
|
||||
locale = DEFAULT_LOCALE;
|
||||
}
|
||||
}
|
||||
return MiniMessageTranslator.render(component, locale);
|
||||
}
|
||||
|
||||
public void loadFromFileSystem(Path directory, boolean suppressDuplicatesError) {
|
||||
List<Path> translationFiles;
|
||||
try (Stream<Path> stream = Files.list(directory)) {
|
||||
translationFiles = stream.filter(TranslationManager::isTranslationFile).collect(Collectors.toList());
|
||||
} catch (IOException e) {
|
||||
translationFiles = Collections.emptyList();
|
||||
}
|
||||
|
||||
if (translationFiles.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Map<Locale, Map<String, String>> loaded = new HashMap<>();
|
||||
for (Path translationFile : translationFiles) {
|
||||
try {
|
||||
Pair<Locale, Map<String, String>> result = loadTranslationFile(translationFile);
|
||||
loaded.put(result.left(), result.right());
|
||||
} catch (Exception e) {
|
||||
if (!suppressDuplicatesError || !isAdventureDuplicatesException(e)) {
|
||||
this.plugin.getPluginLogger().warn("Error loading locale file: " + translationFile.getFileName(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// try registering the locale without a country code - if we don't already have a registration for that
|
||||
loaded.forEach((locale, bundle) -> {
|
||||
Locale localeWithoutCountry = new Locale(locale.getLanguage());
|
||||
if (!locale.equals(localeWithoutCountry) && !localeWithoutCountry.equals(DEFAULT_LOCALE) && this.installed.add(localeWithoutCountry)) {
|
||||
try {
|
||||
this.registry.registerAll(localeWithoutCountry, bundle);
|
||||
} catch (IllegalArgumentException e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Locale localLocale = Locale.getDefault();
|
||||
if (!this.installed.contains(localLocale) && FORCE_LOCALE == null) {
|
||||
plugin.getPluginLogger().warn(localLocale.toString().toLowerCase(Locale.ENGLISH) + ".yml not exists, using " + DEFAULT_LOCALE.toString().toLowerCase(Locale.ENGLISH) + ".yml as default locale.");
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isTranslationFile(Path path) {
|
||||
return path.getFileName().toString().endsWith(".yml");
|
||||
}
|
||||
|
||||
private static boolean isAdventureDuplicatesException(Exception e) {
|
||||
return e instanceof IllegalArgumentException && (e.getMessage().startsWith("Invalid key") || e.getMessage().startsWith("Translation already exists"));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Pair<Locale, Map<String, String>> loadTranslationFile(Path translationFile) {
|
||||
String fileName = translationFile.getFileName().toString();
|
||||
String localeString = fileName.substring(0, fileName.length() - ".yml".length());
|
||||
Locale locale = parseLocale(localeString);
|
||||
|
||||
if (locale == null) {
|
||||
throw new IllegalStateException("Unknown locale '" + localeString + "' - unable to register.");
|
||||
}
|
||||
|
||||
Map<String, String> bundle = new HashMap<>();
|
||||
YamlDocument document = plugin.getConfigManager().loadConfig("translations" + File.separator + translationFile.getFileName(), '@');
|
||||
try {
|
||||
document.save(new File(plugin.getDataDirectory().toFile(), "translations" + File.separator + translationFile.getFileName()));
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException("Could not update translation file: " + translationFile.getFileName(), e);
|
||||
}
|
||||
Map<String, Object> map = document.getStringRouteMappedValues(false);
|
||||
map.remove("config-version");
|
||||
for (Map.Entry<String, Object> entry : map.entrySet()) {
|
||||
if (entry.getValue() instanceof List<?> list) {
|
||||
List<String> strList = (List<String>) list;
|
||||
StringJoiner stringJoiner = new StringJoiner("<reset><newline>");
|
||||
for (String str : strList) {
|
||||
stringJoiner.add(str);
|
||||
}
|
||||
bundle.put(entry.getKey(), stringJoiner.toString());
|
||||
} else if (entry.getValue() instanceof String str) {
|
||||
bundle.put(entry.getKey(), str);
|
||||
}
|
||||
}
|
||||
|
||||
this.registry.registerAll(locale, bundle);
|
||||
this.installed.add(locale);
|
||||
|
||||
return Pair.of(locale, bundle);
|
||||
}
|
||||
|
||||
public static @Nullable Locale parseLocale(@Nullable String locale) {
|
||||
return locale == null || locale.isEmpty() ? null : Translator.parseLocale(locale);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright (C) <2024> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customnameplates.common.plugin;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
public class CustomNameplatesProperties {
|
||||
|
||||
private final HashMap<String, String> propertyMap;
|
||||
|
||||
private CustomNameplatesProperties(HashMap<String, String> propertyMap) {
|
||||
this.propertyMap = propertyMap;
|
||||
}
|
||||
|
||||
public static String getValue(String key) {
|
||||
if (!SingletonHolder.INSTANCE.propertyMap.containsKey(key)) {
|
||||
throw new RuntimeException("Unknown key: " + key);
|
||||
}
|
||||
return SingletonHolder.INSTANCE.propertyMap.get(key);
|
||||
}
|
||||
|
||||
private static class SingletonHolder {
|
||||
|
||||
private static final CustomNameplatesProperties INSTANCE = getInstance();
|
||||
|
||||
private static CustomNameplatesProperties getInstance() {
|
||||
try (InputStream inputStream = CustomNameplatesProperties.class.getClassLoader().getResourceAsStream("custom-nameplates.properties")) {
|
||||
HashMap<String, String> versionMap = new HashMap<>();
|
||||
Properties properties = new Properties();
|
||||
properties.load(inputStream);
|
||||
for (Map.Entry<Object, Object> entry : properties.entrySet()) {
|
||||
if (entry.getKey() instanceof String key && entry.getValue() instanceof String value) {
|
||||
versionMap.put(key, value);
|
||||
}
|
||||
}
|
||||
return new CustomNameplatesProperties(versionMap);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
/*
|
||||
* Copyright (C) <2024> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customnameplates.common.plugin;
|
||||
|
||||
import net.momirealms.customnameplates.common.dependency.DependencyManager;
|
||||
import net.momirealms.customnameplates.common.locale.TranslationManager;
|
||||
import net.momirealms.customnameplates.common.plugin.classpath.ClassPathAppender;
|
||||
import net.momirealms.customnameplates.common.plugin.config.ConfigLoader;
|
||||
import net.momirealms.customnameplates.common.plugin.logging.PluginLogger;
|
||||
import net.momirealms.customnameplates.common.plugin.scheduler.SchedulerAdapter;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Path;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Interface representing the main CustomFishing plugin.
|
||||
*/
|
||||
public interface NameplatesPlugin {
|
||||
|
||||
/**
|
||||
* Retrieves an input stream for a resource file within the plugin.
|
||||
*
|
||||
* @param filePath the path to the resource file
|
||||
* @return an {@link InputStream} for the resource file
|
||||
*/
|
||||
InputStream getResourceStream(String filePath);
|
||||
|
||||
/**
|
||||
* Retrieves the plugin logger.
|
||||
*
|
||||
* @return the {@link PluginLogger} instance
|
||||
*/
|
||||
PluginLogger getPluginLogger();
|
||||
|
||||
/**
|
||||
* Retrieves the class path appender.
|
||||
*
|
||||
* @return the {@link ClassPathAppender} instance
|
||||
*/
|
||||
ClassPathAppender getClassPathAppender();
|
||||
|
||||
/**
|
||||
* Retrieves the scheduler adapter.
|
||||
*
|
||||
* @return the {@link SchedulerAdapter} instance
|
||||
*/
|
||||
SchedulerAdapter<?> getScheduler();
|
||||
|
||||
/**
|
||||
* Retrieves the data directory path.
|
||||
*
|
||||
* @return the {@link Path} to the data directory
|
||||
*/
|
||||
Path getDataDirectory();
|
||||
|
||||
/**
|
||||
* Retrieves the configuration directory path.
|
||||
* By default, this is the same as the data directory.
|
||||
*
|
||||
* @return the {@link Path} to the configuration directory
|
||||
*/
|
||||
default Path getConfigDirectory() {
|
||||
return getDataDirectory();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the dependency manager.
|
||||
*
|
||||
* @return the {@link DependencyManager} instance
|
||||
*/
|
||||
DependencyManager getDependencyManager();
|
||||
|
||||
/**
|
||||
* Retrieves the translation manager.
|
||||
*
|
||||
* @return the {@link TranslationManager} instance
|
||||
*/
|
||||
TranslationManager getTranslationManager();
|
||||
|
||||
/**
|
||||
* Retrieves the configuration manager.
|
||||
*
|
||||
* @return the {@link ConfigLoader} instance
|
||||
*/
|
||||
ConfigLoader getConfigManager();
|
||||
|
||||
/**
|
||||
* Retrieves the server version.
|
||||
*
|
||||
* @return the server version as a string
|
||||
*/
|
||||
String getServerVersion();
|
||||
|
||||
/**
|
||||
* Retrieves the plugin version.
|
||||
*
|
||||
* @return the plugin version as a string
|
||||
*/
|
||||
String getPluginVersion();
|
||||
|
||||
/**
|
||||
* Loads the plugin.
|
||||
* This method is called during the plugin's loading phase.
|
||||
*/
|
||||
void load();
|
||||
|
||||
/**
|
||||
* Enables the plugin.
|
||||
* This method is called during the plugin's enabling phase.
|
||||
*/
|
||||
void enable();
|
||||
|
||||
/**
|
||||
* Disables the plugin.
|
||||
* This method is called during the plugin's disabling phase.
|
||||
*/
|
||||
void disable();
|
||||
|
||||
/**
|
||||
* Reloads the plugin.
|
||||
*/
|
||||
void reload();
|
||||
|
||||
/**
|
||||
* Debug
|
||||
*
|
||||
* @param supplier debug message supplier
|
||||
*/
|
||||
void debug(Supplier<String> supplier);
|
||||
|
||||
boolean isUpToDate();
|
||||
|
||||
default File getDataFolder() {
|
||||
return getDataDirectory().toFile();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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 net.momirealms.customnameplates.common.plugin.classpath;
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
||||
/**
|
||||
* Interface which allows access to add URLs to the plugin classpath at runtime.
|
||||
*/
|
||||
public interface ClassPathAppender extends AutoCloseable {
|
||||
|
||||
void addJarToClasspath(Path file);
|
||||
|
||||
@Override
|
||||
default void close() {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* 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 net.momirealms.customnameplates.common.plugin.classpath;
|
||||
|
||||
import net.momirealms.customnameplates.common.plugin.NameplatesPlugin;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URLClassLoader;
|
||||
import java.nio.file.Path;
|
||||
|
||||
public class ReflectionClassPathAppender implements ClassPathAppender {
|
||||
private final URLClassLoaderAccess classLoaderAccess;
|
||||
|
||||
public ReflectionClassPathAppender(ClassLoader classLoader) throws IllegalStateException {
|
||||
if (classLoader instanceof URLClassLoader) {
|
||||
this.classLoaderAccess = URLClassLoaderAccess.create((URLClassLoader) classLoader);
|
||||
} else {
|
||||
throw new IllegalStateException("ClassLoader is not instance of URLClassLoader");
|
||||
}
|
||||
}
|
||||
|
||||
public ReflectionClassPathAppender(NameplatesPlugin plugin) throws IllegalStateException {
|
||||
this(plugin.getClass().getClassLoader());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addJarToClasspath(Path file) {
|
||||
try {
|
||||
this.classLoaderAccess.addURL(file.toUri().toURL());
|
||||
} catch (MalformedURLException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
/*
|
||||
* 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 net.momirealms.customnameplates.common.plugin.classpath;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* Provides access to {@link URLClassLoader}#addURL.
|
||||
*/
|
||||
public abstract class URLClassLoaderAccess {
|
||||
|
||||
/**
|
||||
* Creates a {@link URLClassLoaderAccess} for the given class loader.
|
||||
*
|
||||
* @param classLoader the class loader
|
||||
* @return the access object
|
||||
*/
|
||||
public static URLClassLoaderAccess create(URLClassLoader classLoader) {
|
||||
if (Reflection.isSupported()) {
|
||||
return new Reflection(classLoader);
|
||||
} else if (Unsafe.isSupported()) {
|
||||
return new Unsafe(classLoader);
|
||||
} else {
|
||||
return Noop.INSTANCE;
|
||||
}
|
||||
}
|
||||
|
||||
private final URLClassLoader classLoader;
|
||||
|
||||
protected URLClassLoaderAccess(URLClassLoader classLoader) {
|
||||
this.classLoader = classLoader;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds the given URL to the class loader.
|
||||
*
|
||||
* @param url the URL to add
|
||||
*/
|
||||
public abstract void addURL(@NotNull URL url);
|
||||
|
||||
private static void throwError(Throwable cause) throws UnsupportedOperationException {
|
||||
throw new UnsupportedOperationException("CustomNameplates is unable to inject into the plugin URLClassLoader.\n" +
|
||||
"You may be able to fix this problem by adding the following command-line argument " +
|
||||
"directly after the 'java' command in your start script: \n'--add-opens java.base/java.lang=ALL-UNNAMED'", cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* Accesses using reflection, not supported on Java 9+.
|
||||
*/
|
||||
private static class Reflection extends URLClassLoaderAccess {
|
||||
private static final Method ADD_URL_METHOD;
|
||||
|
||||
static {
|
||||
Method addUrlMethod;
|
||||
try {
|
||||
addUrlMethod = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
|
||||
addUrlMethod.setAccessible(true);
|
||||
} catch (Exception e) {
|
||||
addUrlMethod = null;
|
||||
}
|
||||
ADD_URL_METHOD = addUrlMethod;
|
||||
}
|
||||
|
||||
private static boolean isSupported() {
|
||||
return ADD_URL_METHOD != null;
|
||||
}
|
||||
|
||||
Reflection(URLClassLoader classLoader) {
|
||||
super(classLoader);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addURL(@NotNull URL url) {
|
||||
try {
|
||||
ADD_URL_METHOD.invoke(super.classLoader, url);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
URLClassLoaderAccess.throwError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Accesses using sun.misc.Unsafe, supported on Java 9+.
|
||||
*
|
||||
* @author Vaishnav Anil (https://github.com/slimjar/slimjar)
|
||||
*/
|
||||
private static class Unsafe extends URLClassLoaderAccess {
|
||||
private static final sun.misc.Unsafe UNSAFE;
|
||||
|
||||
static {
|
||||
sun.misc.Unsafe unsafe;
|
||||
try {
|
||||
Field unsafeField = sun.misc.Unsafe.class.getDeclaredField("theUnsafe");
|
||||
unsafeField.setAccessible(true);
|
||||
unsafe = (sun.misc.Unsafe) unsafeField.get(null);
|
||||
} catch (Throwable t) {
|
||||
unsafe = null;
|
||||
}
|
||||
UNSAFE = unsafe;
|
||||
}
|
||||
|
||||
private static boolean isSupported() {
|
||||
return UNSAFE != null;
|
||||
}
|
||||
|
||||
private final Collection<URL> unopenedURLs;
|
||||
private final Collection<URL> pathURLs;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Unsafe(URLClassLoader classLoader) {
|
||||
super(classLoader);
|
||||
|
||||
Collection<URL> unopenedURLs;
|
||||
Collection<URL> pathURLs;
|
||||
try {
|
||||
Object ucp = fetchField(URLClassLoader.class, classLoader, "ucp");
|
||||
unopenedURLs = (Collection<URL>) fetchField(ucp.getClass(), ucp, "unopenedUrls");
|
||||
pathURLs = (Collection<URL>) fetchField(ucp.getClass(), ucp, "path");
|
||||
} catch (Throwable e) {
|
||||
unopenedURLs = null;
|
||||
pathURLs = null;
|
||||
}
|
||||
|
||||
this.unopenedURLs = unopenedURLs;
|
||||
this.pathURLs = pathURLs;
|
||||
}
|
||||
|
||||
private static Object fetchField(final Class<?> clazz, final Object object, final String name) throws NoSuchFieldException {
|
||||
Field field = clazz.getDeclaredField(name);
|
||||
long offset = UNSAFE.objectFieldOffset(field);
|
||||
return UNSAFE.getObject(object, offset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addURL(@NotNull URL url) {
|
||||
if (this.unopenedURLs == null || this.pathURLs == null) {
|
||||
URLClassLoaderAccess.throwError(new NullPointerException("unopenedURLs or pathURLs"));
|
||||
}
|
||||
|
||||
synchronized (this.unopenedURLs) {
|
||||
this.unopenedURLs.add(url);
|
||||
this.pathURLs.add(url);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class Noop extends URLClassLoaderAccess {
|
||||
private static final Noop INSTANCE = new Noop();
|
||||
|
||||
private Noop() {
|
||||
super(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addURL(@NotNull URL url) {
|
||||
URLClassLoaderAccess.throwError(null);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright (C) <2024> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customnameplates.common.plugin.config;
|
||||
|
||||
import dev.dejvokep.boostedyaml.YamlDocument;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* Interface for loading and managing configuration files.
|
||||
*/
|
||||
public interface ConfigLoader {
|
||||
|
||||
/**
|
||||
* Loads a YAML configuration file from the specified file path.
|
||||
*
|
||||
* @param filePath the path to the configuration file
|
||||
* @return the loaded {@link YamlDocument}
|
||||
*/
|
||||
YamlDocument loadConfig(String filePath);
|
||||
|
||||
/**
|
||||
* Loads a YAML configuration file from the specified file path with a custom route separator.
|
||||
*
|
||||
* @param filePath the path to the configuration file
|
||||
* @param routeSeparator the custom route separator character
|
||||
* @return the loaded {@link YamlDocument}
|
||||
*/
|
||||
YamlDocument loadConfig(String filePath, char routeSeparator);
|
||||
|
||||
/**
|
||||
* Loads a YAML data file.
|
||||
*
|
||||
* @param file the {@link File} object representing the data file
|
||||
* @return the loaded {@link YamlDocument}
|
||||
*/
|
||||
YamlDocument loadData(File file);
|
||||
|
||||
/**
|
||||
* Saves a resource file from the plugin's jar to the specified file path.
|
||||
*
|
||||
* @param filePath the path where the resource file will be saved
|
||||
*/
|
||||
void saveResource(String filePath);
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
* Copyright (C) <2024> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -15,32 +15,22 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customnameplates.common.team;
|
||||
package net.momirealms.customnameplates.common.plugin.feature;
|
||||
|
||||
import java.util.Locale;
|
||||
public interface Reloadable {
|
||||
|
||||
public enum TeamColor {
|
||||
default void reload() {
|
||||
unload();
|
||||
load();
|
||||
}
|
||||
|
||||
BLACK,
|
||||
DARK_BLUE,
|
||||
DARK_GREEN,
|
||||
DARK_AQUA,
|
||||
DARK_RED,
|
||||
DARK_PURPLE,
|
||||
GOLD,
|
||||
GRAY,
|
||||
DARK_GRAY,
|
||||
BLUE,
|
||||
GREEN,
|
||||
AQUA,
|
||||
RED,
|
||||
LIGHT_PURPLE,
|
||||
YELLOW,
|
||||
WHITE,
|
||||
NONE,
|
||||
CUSTOM;
|
||||
default void unload() {
|
||||
}
|
||||
|
||||
public TeamColor getById(String id) throws IllegalArgumentException {
|
||||
return valueOf(id.toUpperCase(Locale.ENGLISH));
|
||||
default void load() {
|
||||
}
|
||||
|
||||
default void disable() {
|
||||
unload();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* 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 net.momirealms.customnameplates.common.plugin.logging;
|
||||
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public class JavaPluginLogger implements PluginLogger {
|
||||
private final Logger logger;
|
||||
|
||||
public JavaPluginLogger(Logger logger) {
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void info(String s) {
|
||||
this.logger.info(s);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warn(String s) {
|
||||
this.logger.warning(s);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warn(String s, Throwable t) {
|
||||
this.logger.log(Level.WARNING, s, t);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void severe(String s) {
|
||||
this.logger.severe(s);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void severe(String s, Throwable t) {
|
||||
this.logger.log(Level.SEVERE, s, t);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* 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 net.momirealms.customnameplates.common.plugin.logging;
|
||||
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
public class Log4jPluginLogger implements PluginLogger {
|
||||
private final Logger logger;
|
||||
|
||||
public Log4jPluginLogger(Logger logger) {
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void info(String s) {
|
||||
this.logger.info(s);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warn(String s) {
|
||||
this.logger.warn(s);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warn(String s, Throwable t) {
|
||||
this.logger.warn(s, t);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void severe(String s) {
|
||||
this.logger.error(s);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void severe(String s, Throwable t) {
|
||||
this.logger.error(s, t);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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 net.momirealms.customnameplates.common.plugin.logging;
|
||||
|
||||
/**
|
||||
* Represents the logger instance being used by CustomFishing on the platform.
|
||||
*
|
||||
* <p>Messages sent using the logger are sent prefixed with the CustomFishing tag,
|
||||
* and on some implementations will be colored depending on the message type.</p>
|
||||
*/
|
||||
public interface PluginLogger {
|
||||
|
||||
void info(String s);
|
||||
|
||||
void warn(String s);
|
||||
|
||||
void warn(String s, Throwable t);
|
||||
|
||||
void severe(String s);
|
||||
|
||||
void severe(String s, Throwable t);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* 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 net.momirealms.customnameplates.common.plugin.logging;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
|
||||
public class Slf4jPluginLogger implements PluginLogger {
|
||||
private final Logger logger;
|
||||
|
||||
public Slf4jPluginLogger(Logger logger) {
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void info(String s) {
|
||||
this.logger.info(s);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warn(String s) {
|
||||
this.logger.warn(s);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warn(String s, Throwable t) {
|
||||
this.logger.warn(s, t);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void severe(String s) {
|
||||
this.logger.error(s);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void severe(String s, Throwable t) {
|
||||
this.logger.error(s, t);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
* 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 net.momirealms.customnameplates.common.plugin.scheduler;
|
||||
|
||||
import net.momirealms.customnameplates.common.plugin.NameplatesPlugin;
|
||||
|
||||
import java.lang.Thread.UncaughtExceptionHandler;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Abstract implementation of {@link SchedulerAdapter} using a {@link ScheduledExecutorService}.
|
||||
*/
|
||||
public abstract class AbstractJavaScheduler<T> implements SchedulerAdapter<T> {
|
||||
private static final int PARALLELISM = 16;
|
||||
|
||||
private final NameplatesPlugin plugin;
|
||||
|
||||
private final ScheduledThreadPoolExecutor scheduler;
|
||||
private final ForkJoinPool worker;
|
||||
|
||||
public AbstractJavaScheduler(NameplatesPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
|
||||
this.scheduler = new ScheduledThreadPoolExecutor(4, r -> {
|
||||
Thread thread = Executors.defaultThreadFactory().newThread(r);
|
||||
thread.setName("nameplates-scheduler");
|
||||
return thread;
|
||||
});
|
||||
this.scheduler.setRemoveOnCancelPolicy(true);
|
||||
this.scheduler.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);
|
||||
this.worker = new ForkJoinPool(PARALLELISM, new WorkerThreadFactory(), new ExceptionHandler(), false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Executor async() {
|
||||
return this.worker;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SchedulerTask asyncLater(Runnable task, long delay, TimeUnit unit) {
|
||||
if (delay == 0) {
|
||||
async().execute(task);
|
||||
return new DummyTask();
|
||||
}
|
||||
ScheduledFuture<?> future = this.scheduler.schedule(() -> this.worker.execute(task), delay, unit);
|
||||
return new AsyncTask(future);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SchedulerTask asyncRepeating(Runnable task, long delay, long interval, TimeUnit unit) {
|
||||
ScheduledFuture<?> future = this.scheduler.scheduleAtFixedRate(() -> this.worker.execute(task), delay, interval, unit);
|
||||
return new AsyncTask(future);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdownScheduler() {
|
||||
this.scheduler.shutdown();
|
||||
try {
|
||||
if (!this.scheduler.awaitTermination(1, TimeUnit.MINUTES)) {
|
||||
this.plugin.getPluginLogger().severe("Timed out waiting for the CustomFishing scheduler to terminate");
|
||||
reportRunningTasks(thread -> thread.getName().equals("customfishing-scheduler"));
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdownExecutor() {
|
||||
this.worker.shutdown();
|
||||
try {
|
||||
if (!this.worker.awaitTermination(1, TimeUnit.MINUTES)) {
|
||||
this.plugin.getPluginLogger().severe("Timed out waiting for the CustomFishing worker thread pool to terminate");
|
||||
reportRunningTasks(thread -> thread.getName().startsWith("nameplates-worker-"));
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void reportRunningTasks(Predicate<Thread> predicate) {
|
||||
Thread.getAllStackTraces().forEach((thread, stack) -> {
|
||||
if (predicate.test(thread)) {
|
||||
this.plugin.getPluginLogger().warn("Thread " + thread.getName() + " is blocked, and may be the reason for the slow shutdown!\n" +
|
||||
Arrays.stream(stack).map(el -> " " + el).collect(Collectors.joining("\n"))
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static final class WorkerThreadFactory implements ForkJoinPool.ForkJoinWorkerThreadFactory {
|
||||
private static final AtomicInteger COUNT = new AtomicInteger(0);
|
||||
|
||||
@Override
|
||||
public ForkJoinWorkerThread newThread(ForkJoinPool pool) {
|
||||
ForkJoinWorkerThread thread = ForkJoinPool.defaultForkJoinWorkerThreadFactory.newThread(pool);
|
||||
thread.setDaemon(true);
|
||||
thread.setName("nameplates-worker-" + COUNT.getAndIncrement());
|
||||
return thread;
|
||||
}
|
||||
}
|
||||
|
||||
private final class ExceptionHandler implements UncaughtExceptionHandler {
|
||||
@Override
|
||||
public void uncaughtException(Thread t, Throwable e) {
|
||||
AbstractJavaScheduler.this.plugin.getPluginLogger().warn("Thread " + t.getName() + " threw an uncaught exception", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
* Copyright (C) <2024> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -15,34 +15,25 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customnameplates.common.team;
|
||||
package net.momirealms.customnameplates.common.plugin.scheduler;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
|
||||
import java.util.Arrays;
|
||||
public class AsyncTask implements SchedulerTask {
|
||||
|
||||
public enum TeamCollisionRule {
|
||||
private final ScheduledFuture<?> future;
|
||||
|
||||
ALWAYS("always"),
|
||||
NEVER("never"),
|
||||
PUSH_OTHER_TEAMS("pushOtherTeams"),
|
||||
PUSH_OWN_TEAM("pushOwnTeam");
|
||||
|
||||
private final String id;
|
||||
|
||||
TeamCollisionRule(@NotNull String id) {
|
||||
this.id = id;
|
||||
public AsyncTask(ScheduledFuture<?> future) {
|
||||
this.future = future;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
@Override
|
||||
public void cancel() {
|
||||
future.cancel(false);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static TeamCollisionRule byId(String id) {
|
||||
return Arrays.stream(values())
|
||||
.filter(mode -> mode.id.equals(id))
|
||||
.findFirst()
|
||||
.orElse(ALWAYS);
|
||||
@Override
|
||||
public boolean cancelled() {
|
||||
return future.isCancelled();
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
* Copyright (C) <2024> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -15,11 +15,17 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customnameplates.common.message;
|
||||
package net.momirealms.customnameplates.common.plugin.scheduler;
|
||||
|
||||
public class MessageType {
|
||||
public class DummyTask implements SchedulerTask {
|
||||
|
||||
public static final String CREATE = "0";
|
||||
public static final String REMOVE = "1";
|
||||
public static final String UPDATE = "2";
|
||||
}
|
||||
@Override
|
||||
public void cancel() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean cancelled() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright (C) <2024> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customnameplates.common.plugin.scheduler;
|
||||
|
||||
public interface RegionExecutor<T> {
|
||||
|
||||
void run(Runnable r, T l);
|
||||
|
||||
SchedulerTask runLater(Runnable r, long delayTicks, T l);
|
||||
|
||||
SchedulerTask runRepeating(Runnable r, long delayTicks, long period, T l);
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* 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 net.momirealms.customnameplates.common.plugin.scheduler;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* A scheduler for running tasks using the systems provided by the platform
|
||||
*/
|
||||
public interface SchedulerAdapter<T> {
|
||||
|
||||
/**
|
||||
* Gets an async executor instance
|
||||
*
|
||||
* @return an async executor instance
|
||||
*/
|
||||
Executor async();
|
||||
|
||||
/**
|
||||
* Gets a sync executor instance
|
||||
*
|
||||
* @return a sync executor instance
|
||||
*/
|
||||
RegionExecutor<T> sync();
|
||||
|
||||
/**
|
||||
* Executes a task async
|
||||
*
|
||||
* @param task the task
|
||||
*/
|
||||
default void executeAsync(Runnable task) {
|
||||
async().execute(task);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a task sync
|
||||
*
|
||||
* @param task the task
|
||||
*/
|
||||
default void executeSync(Runnable task, T location) {
|
||||
sync().run(task, location);
|
||||
}
|
||||
|
||||
default void executeSync(Runnable task) {
|
||||
sync().run(task, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the given task with a delay.
|
||||
*
|
||||
* @param task the task
|
||||
* @param delay the delay
|
||||
* @param unit the unit of delay
|
||||
* @return the resultant task instance
|
||||
*/
|
||||
SchedulerTask asyncLater(Runnable task, long delay, TimeUnit unit);
|
||||
|
||||
/**
|
||||
* Executes the given task repeatedly at a given interval.
|
||||
*
|
||||
* @param task the task
|
||||
* @param interval the interval
|
||||
* @param unit the unit of interval
|
||||
* @return the resultant task instance
|
||||
*/
|
||||
SchedulerTask asyncRepeating(Runnable task, long delay, long interval, TimeUnit unit);
|
||||
|
||||
/**
|
||||
* Shuts down the scheduler instance.
|
||||
*
|
||||
* <p>{@link #asyncLater(Runnable, long, TimeUnit)} and {@link #asyncRepeating(Runnable, long, long, TimeUnit)}.</p>
|
||||
*/
|
||||
void shutdownScheduler();
|
||||
|
||||
/**
|
||||
* Shuts down the executor instance.
|
||||
*
|
||||
* <p>{@link #async()} and {@link #executeAsync(Runnable)}.</p>
|
||||
*/
|
||||
void shutdownExecutor();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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 net.momirealms.customnameplates.common.plugin.scheduler;
|
||||
|
||||
/**
|
||||
* Represents a scheduled task
|
||||
*/
|
||||
public interface SchedulerTask {
|
||||
|
||||
/**
|
||||
* Cancels the task.
|
||||
*/
|
||||
void cancel();
|
||||
|
||||
boolean cancelled();
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* 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 net.momirealms.customnameplates.common.sender;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.momirealms.customnameplates.common.plugin.NameplatesPlugin;
|
||||
import net.momirealms.customnameplates.common.util.Tristate;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Simple implementation of {@link Sender} using a {@link SenderFactory}
|
||||
*
|
||||
* @param <T> the command sender type
|
||||
*/
|
||||
public final class AbstractSender<T> implements Sender {
|
||||
private final NameplatesPlugin plugin;
|
||||
private final SenderFactory<?, T> factory;
|
||||
private final T sender;
|
||||
|
||||
private final UUID uniqueId;
|
||||
private final String name;
|
||||
private final boolean isConsole;
|
||||
|
||||
AbstractSender(NameplatesPlugin plugin, SenderFactory<?, T> factory, T sender) {
|
||||
this.plugin = plugin;
|
||||
this.factory = factory;
|
||||
this.sender = sender;
|
||||
this.uniqueId = factory.getUniqueId(this.sender);
|
||||
this.name = factory.getName(this.sender);
|
||||
this.isConsole = this.factory.isConsole(this.sender);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NameplatesPlugin getPlugin() {
|
||||
return this.plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID getUniqueId() {
|
||||
return this.uniqueId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(Component message) {
|
||||
this.factory.sendMessage(this.sender, message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(Component message, boolean ignoreEmpty) {
|
||||
if (ignoreEmpty && message.equals(Component.empty())) {
|
||||
return;
|
||||
}
|
||||
sendMessage(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Tristate getPermissionValue(String permission) {
|
||||
return (isConsole() && this.factory.consoleHasAllPermissions()) ? Tristate.TRUE : this.factory.getPermissionValue(this.sender, permission);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPermission(String permission) {
|
||||
return (isConsole() && this.factory.consoleHasAllPermissions()) || this.factory.hasPermission(this.sender, permission);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void performCommand(String commandLine) {
|
||||
this.factory.performCommand(this.sender, commandLine);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConsole() {
|
||||
return this.isConsole;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o == this) return true;
|
||||
if (!(o instanceof AbstractSender<?> that)) return false;
|
||||
return this.getUniqueId().equals(that.getUniqueId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.uniqueId.hashCode();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* 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 net.momirealms.customnameplates.common.sender;
|
||||
|
||||
import net.momirealms.customnameplates.common.plugin.NameplatesPlugin;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public abstract class DummyConsoleSender implements Sender {
|
||||
|
||||
private final NameplatesPlugin platform;
|
||||
|
||||
public DummyConsoleSender(NameplatesPlugin plugin) {
|
||||
this.platform = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void performCommand(String commandLine) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConsole() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NameplatesPlugin getPlugin() {
|
||||
return this.platform;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID getUniqueId() {
|
||||
return Sender.CONSOLE_UUID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return Sender.CONSOLE_NAME;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* 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 net.momirealms.customnameplates.common.sender;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.momirealms.customnameplates.common.plugin.NameplatesPlugin;
|
||||
import net.momirealms.customnameplates.common.util.Tristate;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Wrapper interface to represent a CommandSender/CommandSource within the common command implementations.
|
||||
*/
|
||||
public interface Sender {
|
||||
|
||||
/** The uuid used by the console sender. */
|
||||
UUID CONSOLE_UUID = new UUID(0, 0); // 00000000-0000-0000-0000-000000000000
|
||||
/** The name used by the console sender. */
|
||||
String CONSOLE_NAME = "Console";
|
||||
|
||||
/**
|
||||
* Gets the plugin instance the sender is from.
|
||||
*
|
||||
* @return the plugin
|
||||
*/
|
||||
NameplatesPlugin getPlugin();
|
||||
|
||||
/**
|
||||
* Gets the sender's username
|
||||
*
|
||||
* @return a friendly username for the sender
|
||||
*/
|
||||
String getName();
|
||||
|
||||
/**
|
||||
* Gets the sender's unique id.
|
||||
*
|
||||
* <p>See {@link #CONSOLE_UUID} for the console's UUID representation.</p>
|
||||
*
|
||||
* @return the sender's uuid
|
||||
*/
|
||||
UUID getUniqueId();
|
||||
|
||||
/**
|
||||
* Send a json message to the Sender.
|
||||
*
|
||||
* @param message the message to send.
|
||||
*/
|
||||
void sendMessage(Component message);
|
||||
|
||||
/**
|
||||
* Send a json message to the Sender.
|
||||
*
|
||||
* @param message the message to send.
|
||||
* @param ignoreEmpty whether to ignore empty component
|
||||
*/
|
||||
void sendMessage(Component message, boolean ignoreEmpty);
|
||||
|
||||
/**
|
||||
* Gets the tristate a permission is set to.
|
||||
*
|
||||
* @param permission the permission to check for
|
||||
* @return a tristate
|
||||
*/
|
||||
Tristate getPermissionValue(String permission);
|
||||
|
||||
/**
|
||||
* Check if the Sender has a permission.
|
||||
*
|
||||
* @param permission the permission to check for
|
||||
* @return true if the sender has the permission
|
||||
*/
|
||||
boolean hasPermission(String permission);
|
||||
|
||||
/**
|
||||
* Makes the sender perform a command.
|
||||
*
|
||||
* @param commandLine the command
|
||||
*/
|
||||
void performCommand(String commandLine);
|
||||
|
||||
/**
|
||||
* Gets whether this sender is the console
|
||||
*
|
||||
* @return if the sender is the console
|
||||
*/
|
||||
boolean isConsole();
|
||||
|
||||
/**
|
||||
* Gets whether this sender is still valid & receiving messages.
|
||||
*
|
||||
* @return if this sender is valid
|
||||
*/
|
||||
default boolean isValid() {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* 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 net.momirealms.customnameplates.common.sender;
|
||||
|
||||
import net.kyori.adventure.audience.Audience;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.momirealms.customnameplates.common.plugin.NameplatesPlugin;
|
||||
import net.momirealms.customnameplates.common.util.Tristate;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Factory class to make a thread-safe sender instance
|
||||
*
|
||||
* @param <P> the plugin type
|
||||
* @param <T> the command sender type
|
||||
*/
|
||||
public abstract class SenderFactory<P extends NameplatesPlugin, T> implements AutoCloseable {
|
||||
private final P plugin;
|
||||
|
||||
public SenderFactory(P plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
protected P getPlugin() {
|
||||
return this.plugin;
|
||||
}
|
||||
|
||||
protected abstract UUID getUniqueId(T sender);
|
||||
|
||||
protected abstract String getName(T sender);
|
||||
|
||||
public abstract Audience getAudience(T sender);
|
||||
|
||||
protected abstract void sendMessage(T sender, Component message);
|
||||
|
||||
protected abstract Tristate getPermissionValue(T sender, String node);
|
||||
|
||||
protected abstract boolean hasPermission(T sender, String node);
|
||||
|
||||
protected abstract void performCommand(T sender, String command);
|
||||
|
||||
protected abstract boolean isConsole(T sender);
|
||||
|
||||
protected boolean consoleHasAllPermissions() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public final Sender wrap(T sender) {
|
||||
Objects.requireNonNull(sender, "sender");
|
||||
return new AbstractSender<>(this.plugin, this, sender);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customnameplates.common.team;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public enum TeamTagVisibility {
|
||||
|
||||
ALWAYS("always"),
|
||||
HIDE_FOR_OTHER_TEAMS("hideForOtherTeams"),
|
||||
HIDE_FOR_OWN_TEAM("hideForOwnTeam"),
|
||||
NEVER("never");
|
||||
|
||||
private final String id;
|
||||
|
||||
TeamTagVisibility(@NotNull String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static TeamTagVisibility byId(String id) {
|
||||
return Arrays.stream(values())
|
||||
.filter(mode -> mode.id.equals(id))
|
||||
.findFirst()
|
||||
.orElse(ALWAYS);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright (C) <2024> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customnameplates.common.util;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Utility class for handling operations with arrays.
|
||||
*/
|
||||
public class ArrayUtils {
|
||||
|
||||
private ArrayUtils() {}
|
||||
|
||||
/**
|
||||
* Creates a subarray from the specified array starting from the given index.
|
||||
*
|
||||
* @param array the original array
|
||||
* @param index the starting index for the subarray
|
||||
* @param <T> the type of the elements in the array
|
||||
* @return the subarray starting from the given index
|
||||
* @throws IllegalArgumentException if the index is less than 0
|
||||
*/
|
||||
public static <T> T[] subArray(T[] array, int index) {
|
||||
if (index < 0) {
|
||||
throw new IllegalArgumentException("Index should be a value no lower than 0");
|
||||
}
|
||||
if (array.length <= index) {
|
||||
@SuppressWarnings("unchecked")
|
||||
T[] emptyArray = (T[]) Array.newInstance(array.getClass().getComponentType(), 0);
|
||||
return emptyArray;
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
T[] subArray = (T[]) Array.newInstance(array.getClass().getComponentType(), array.length - index);
|
||||
System.arraycopy(array, index, subArray, 0, array.length - index);
|
||||
return subArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits the specified array into a list of subarrays, each with the specified chunk size.
|
||||
*
|
||||
* @param array the original array
|
||||
* @param chunkSize the size of each chunk
|
||||
* @param <T> the type of the elements in the array
|
||||
* @return a list of subarrays
|
||||
*/
|
||||
public static <T> List<T[]> splitArray(T[] array, int chunkSize) {
|
||||
List<T[]> result = new ArrayList<>();
|
||||
for (int i = 0; i < array.length; i += chunkSize) {
|
||||
int end = Math.min(array.length, i + chunkSize);
|
||||
@SuppressWarnings("unchecked")
|
||||
T[] chunk = (T[]) Array.newInstance(array.getClass().getComponentType(), end - i);
|
||||
System.arraycopy(array, i, chunk, 0, end - i);
|
||||
result.add(chunk);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends an element to the specified array.
|
||||
*
|
||||
* @param array the original array
|
||||
* @param element the element to append
|
||||
* @param <T> the type of the elements in the array
|
||||
* @return a new array with the appended element
|
||||
*/
|
||||
public static <T> T[] appendElementToArray(T[] array, T element) {
|
||||
T[] newArray = Arrays.copyOf(array, array.length + 1);
|
||||
newArray[array.length] = element;
|
||||
return newArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits a string value into an array of substrings based on comma separation.
|
||||
* The input string is expected to be in the format "[value1, value2, ...]".
|
||||
*
|
||||
* @param value the string value to split
|
||||
* @return an array of substrings
|
||||
*/
|
||||
public static String[] splitValue(String value) {
|
||||
return value.substring(value.indexOf('[') + 1, value.lastIndexOf(']'))
|
||||
.replaceAll("\\s", "")
|
||||
.split(",");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright (C) <2024> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customnameplates.common.util;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarInputStream;
|
||||
|
||||
/**
|
||||
* Utility class for handling classes.
|
||||
*/
|
||||
public class ClassUtils {
|
||||
|
||||
private ClassUtils() {}
|
||||
|
||||
/**
|
||||
* Attempts to find a class within a JAR file that extends or implements a given class or interface.
|
||||
*
|
||||
* @param file The JAR file in which to search for the class.
|
||||
* @param clazz The base class or interface to match against.
|
||||
* @param <T> The type of the base class or interface.
|
||||
* @return A Class object representing the found class, or null if not found.
|
||||
* @throws IOException If there is an issue reading the JAR file.
|
||||
* @throws ClassNotFoundException If the specified class cannot be found.
|
||||
*/
|
||||
@Nullable
|
||||
public static <T> Class<? extends T> findClass(
|
||||
@NotNull File file,
|
||||
@NotNull Class<T> clazz
|
||||
) throws IOException, ClassNotFoundException {
|
||||
if (!file.exists()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
URL jar = file.toURI().toURL();
|
||||
URLClassLoader loader = new URLClassLoader(new URL[]{jar}, clazz.getClassLoader());
|
||||
List<String> matches = new ArrayList<>();
|
||||
List<Class<? extends T>> classes = new ArrayList<>();
|
||||
|
||||
try (JarInputStream stream = new JarInputStream(jar.openStream())) {
|
||||
JarEntry entry;
|
||||
while ((entry = stream.getNextJarEntry()) != null) {
|
||||
final String name = entry.getName();
|
||||
if (!name.endsWith(".class")) {
|
||||
continue;
|
||||
}
|
||||
matches.add(name.substring(0, name.lastIndexOf('.')).replace('/', '.'));
|
||||
}
|
||||
|
||||
for (String match : matches) {
|
||||
try {
|
||||
Class<?> loaded = loader.loadClass(match);
|
||||
if (clazz.isAssignableFrom(loaded)) {
|
||||
classes.add(loaded.asSubclass(clazz));
|
||||
}
|
||||
} catch (NoClassDefFoundError ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (classes.isEmpty()) {
|
||||
loader.close();
|
||||
return null;
|
||||
}
|
||||
return classes.get(0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* Copyright (C) <2024> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customnameplates.common.util;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
/**
|
||||
* Interface representing a value that can be either of two types, primary or fallback.
|
||||
* Provides methods to create instances of either type and to map between them.
|
||||
*
|
||||
* @param <U> the type of the primary value
|
||||
* @param <V> the type of the fallback value
|
||||
*/
|
||||
public interface Either<U, V> {
|
||||
|
||||
/**
|
||||
* Creates an {@link Either} instance with a primary value.
|
||||
*
|
||||
* @param value the primary value
|
||||
* @param <U> the type of the primary value
|
||||
* @param <V> the type of the fallback value
|
||||
* @return an {@link Either} instance with the primary value
|
||||
*/
|
||||
static <U, V> @NotNull Either<U, V> ofPrimary(final @NotNull U value) {
|
||||
return EitherImpl.of(requireNonNull(value, "value"), null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an {@link Either} instance with a fallback value.
|
||||
*
|
||||
* @param value the fallback value
|
||||
* @param <U> the type of the primary value
|
||||
* @param <V> the type of the fallback value
|
||||
* @return an {@link Either} instance with the fallback value
|
||||
*/
|
||||
static <U, V> @NotNull Either<U, V> ofFallback(final @NotNull V value) {
|
||||
return EitherImpl.of(null, requireNonNull(value, "value"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the primary value, if present.
|
||||
*
|
||||
* @return an {@link Optional} containing the primary value, or empty if not present
|
||||
*/
|
||||
@NotNull
|
||||
Optional<U> primary();
|
||||
|
||||
/**
|
||||
* Retrieves the fallback value, if present.
|
||||
*
|
||||
* @return an {@link Optional} containing the fallback value, or empty if not present
|
||||
*/
|
||||
@NotNull
|
||||
Optional<V> fallback();
|
||||
|
||||
/**
|
||||
* Retrieves the primary value, or maps the fallback value to the primary type if the primary is not present.
|
||||
*
|
||||
* @param mapFallback a function to map the fallback value to the primary type
|
||||
* @return the primary value, or the mapped fallback value if the primary is not present
|
||||
*/
|
||||
default @Nullable U primaryOrMapFallback(final @NotNull Function<V, U> mapFallback) {
|
||||
return this.primary().orElseGet(() -> mapFallback.apply(this.fallback().get()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the fallback value, or maps the primary value to the fallback type if the fallback is not present.
|
||||
*
|
||||
* @param mapPrimary a function to map the primary value to the fallback type
|
||||
* @return the fallback value, or the mapped primary value if the fallback is not present
|
||||
*/
|
||||
default @Nullable V fallbackOrMapPrimary(final @NotNull Function<U, V> mapPrimary) {
|
||||
return this.fallback().orElseGet(() -> mapPrimary.apply(this.primary().get()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps either the primary or fallback value to a new type.
|
||||
*
|
||||
* @param mapPrimary a function to map the primary value
|
||||
* @param mapFallback a function to map the fallback value
|
||||
* @param <R> the type of the result
|
||||
* @return the mapped result
|
||||
*/
|
||||
default @NotNull <R> R mapEither(
|
||||
final @NotNull Function<U, R> mapPrimary,
|
||||
final @NotNull Function<V, R> mapFallback
|
||||
) {
|
||||
return this.primary()
|
||||
.map(mapPrimary)
|
||||
.orElseGet(() -> this.fallback().map(mapFallback).get());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* Copyright (C) <2024> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customnameplates.common.util;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
final class EitherImpl<U, V> implements Either<U, V> {
|
||||
private final @Nullable U primary;
|
||||
private final @Nullable V fallback;
|
||||
|
||||
private EitherImpl(Optional<? extends U> primary, Optional<? extends V> fallback) {
|
||||
this.primary = primary.orElse(null);
|
||||
this.fallback = fallback.orElse(null);
|
||||
}
|
||||
|
||||
private EitherImpl(@Nullable U primary, @Nullable V fallback) {
|
||||
this.primary = primary;
|
||||
this.fallback = fallback;
|
||||
}
|
||||
|
||||
private EitherImpl(
|
||||
EitherImpl<U, V> original,
|
||||
@Nullable U primary,
|
||||
@Nullable V fallback
|
||||
) {
|
||||
this.primary = primary;
|
||||
this.fallback = fallback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Optional<U> primary() {
|
||||
return Optional.ofNullable(primary);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Optional<V> fallback() {
|
||||
return Optional.ofNullable(fallback);
|
||||
}
|
||||
|
||||
public final EitherImpl<U, V> withPrimary(@Nullable U value) {
|
||||
@Nullable U newValue = value;
|
||||
if (this.primary == newValue) return this;
|
||||
return new EitherImpl<>(this, newValue, this.fallback);
|
||||
}
|
||||
|
||||
public EitherImpl<U, V> withPrimary(Optional<? extends U> optional) {
|
||||
@Nullable U value = optional.orElse(null);
|
||||
if (this.primary == value) return this;
|
||||
return new EitherImpl<>(this, value, this.fallback);
|
||||
}
|
||||
|
||||
public EitherImpl<U, V> withFallback(@Nullable V value) {
|
||||
@Nullable V newValue = value;
|
||||
if (this.fallback == newValue) return this;
|
||||
return new EitherImpl<>(this, this.primary, newValue);
|
||||
}
|
||||
|
||||
public EitherImpl<U, V> withFallback(Optional<? extends V> optional) {
|
||||
@Nullable V value = optional.orElse(null);
|
||||
if (this.fallback == value) return this;
|
||||
return new EitherImpl<>(this, this.primary, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object another) {
|
||||
if (this == another) return true;
|
||||
return another instanceof EitherImpl<?, ?>
|
||||
&& equalTo((EitherImpl<?, ?>) another);
|
||||
}
|
||||
|
||||
private boolean equalTo(EitherImpl<?, ?> another) {
|
||||
return Objects.equals(primary, another.primary)
|
||||
&& Objects.equals(fallback, another.fallback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int h = 5381;
|
||||
h += (h << 5) + Objects.hashCode(primary);
|
||||
h += (h << 5) + Objects.hashCode(fallback);
|
||||
return h;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder builder = new StringBuilder("Either{");
|
||||
if (primary != null) {
|
||||
builder.append("primary=").append(primary);
|
||||
}
|
||||
if (fallback != null) {
|
||||
if (builder.length() > 7) builder.append(", ");
|
||||
builder.append("fallback=").append(fallback);
|
||||
}
|
||||
return builder.append("}").toString();
|
||||
}
|
||||
|
||||
public static <U, V> EitherImpl<U, V> of(Optional<? extends U> primary, Optional<? extends V> fallback) {
|
||||
return new EitherImpl<>(primary, fallback);
|
||||
}
|
||||
|
||||
public static <U, V> EitherImpl<U, V> of(@Nullable U primary, @Nullable V fallback) {
|
||||
return new EitherImpl<>(primary, fallback);
|
||||
}
|
||||
|
||||
public static <U, V> EitherImpl<U, V> copyOf(Either<U, V> instance) {
|
||||
if (instance instanceof EitherImpl<?, ?>) {
|
||||
return (EitherImpl<U, V>) instance;
|
||||
}
|
||||
return EitherImpl.of(instance.primary(), instance.fallback());
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
* Copyright (C) <2024> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -15,19 +15,39 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customnameplates.common;
|
||||
package net.momirealms.customnameplates.common.util;
|
||||
|
||||
/**
|
||||
* Represents a key consisting of a namespace and a value.
|
||||
* This class provides methods for creating and manipulating keys.
|
||||
*/
|
||||
public record Key(String namespace, String value) {
|
||||
|
||||
/**
|
||||
* Creates a new {@link Key} instance with the specified namespace and value.
|
||||
*
|
||||
* @param namespace the namespace of the key
|
||||
* @param value the value of the key
|
||||
* @return a new {@link Key} instance
|
||||
*/
|
||||
public static Key of(String namespace, String value) {
|
||||
return new Key(namespace, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link Key} instance from a string in the format "namespace:value".
|
||||
*
|
||||
* @param key the string representation of the key
|
||||
* @return a new {@link Key} instance
|
||||
*/
|
||||
public static Key fromString(String key) {
|
||||
String[] split = key.split(":", 2);
|
||||
return of(split[0], split[1]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = this.namespace.hashCode();
|
||||
result = (31 * result) + this.value.hashCode();
|
||||
return result;
|
||||
return toString().hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -43,4 +63,4 @@ public record Key(String namespace, String value) {
|
||||
public String toString() {
|
||||
return namespace + ":" + value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright (C) <2024> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customnameplates.common.util;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Utility class for handling operations related to lists.
|
||||
*/
|
||||
public class ListUtils {
|
||||
|
||||
private ListUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an object to a list of strings.
|
||||
* If the object is a string, it returns a list containing the string.
|
||||
* If the object is a list, it casts and returns the list as a list of strings.
|
||||
*
|
||||
* @param obj the object to convert
|
||||
* @return the resulting list of strings
|
||||
* @throws IllegalArgumentException if the object cannot be converted to a list of strings
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static List<String> toList(final Object obj) {
|
||||
if (obj instanceof String s) {
|
||||
return List.of(s);
|
||||
} else if (obj instanceof List<?> list) {
|
||||
return (List<String>) list;
|
||||
}
|
||||
throw new IllegalArgumentException("Cannot convert " + obj + " to a list");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (C) <2024> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customnameplates.common.util;
|
||||
|
||||
public class OSUtils {
|
||||
|
||||
public static String getOSName() {
|
||||
String os = System.getProperty("os.name").toLowerCase();
|
||||
if (os.contains("win")) {
|
||||
return "windows";
|
||||
} else if (os.contains("mac")) {
|
||||
return "macos";
|
||||
} else if (os.contains("nux") || os.contains("nix")) {
|
||||
return "linux";
|
||||
} else if (os.contains("freebsd")) {
|
||||
return "freebsd";
|
||||
} else {
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright (C) <2024> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customnameplates.common.util;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* A generic class representing a pair of values.
|
||||
* This class provides methods to create and access pairs of values.
|
||||
*
|
||||
* @param <L> the type of the left value
|
||||
* @param <R> the type of the right value
|
||||
*/
|
||||
public record Pair<L, R>(L left, R right) {
|
||||
|
||||
/**
|
||||
* Creates a new {@link Pair} with the specified left and right values.
|
||||
*
|
||||
* @param left the left value
|
||||
* @param right the right value
|
||||
* @param <L> the type of the left value
|
||||
* @param <R> the type of the right value
|
||||
* @return a new {@link Pair} with the specified values
|
||||
*/
|
||||
public static <L, R> Pair<L, R> of(final L left, final R right) {
|
||||
return new Pair<>(left, right);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object object) {
|
||||
if (this == object) return true;
|
||||
if (object == null || getClass() != object.getClass()) return false;
|
||||
Pair<?, ?> pair = (Pair<?, ?>) object;
|
||||
return Objects.equals(left, pair.left) && Objects.equals(right, pair.right);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(this.left, this.right);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
* Copyright (C) <2024> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customnameplates.common.util;
|
||||
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
/**
|
||||
* Utility class for generating random values.
|
||||
*/
|
||||
public class RandomUtils {
|
||||
|
||||
private final Random random;
|
||||
|
||||
private RandomUtils() {
|
||||
random = ThreadLocalRandom.current();
|
||||
}
|
||||
|
||||
/**
|
||||
* Static inner class to hold the singleton instance of RandomUtils.
|
||||
*/
|
||||
private static class SingletonHolder {
|
||||
private static final RandomUtils INSTANCE = new RandomUtils();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the singleton instance of RandomUtils.
|
||||
*
|
||||
* @return the singleton instance
|
||||
*/
|
||||
private static RandomUtils getInstance() {
|
||||
return SingletonHolder.INSTANCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a random integer between the specified minimum and maximum values (inclusive).
|
||||
*
|
||||
* @param min the minimum value
|
||||
* @param max the maximum value
|
||||
* @return a random integer between min and max (inclusive)
|
||||
*/
|
||||
public static int generateRandomInt(int min, int max) {
|
||||
return getInstance().random.nextInt(max - min + 1) + min;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a random double between the specified minimum and maximum values (inclusive).
|
||||
*
|
||||
* @param min the minimum value
|
||||
* @param max the maximum value
|
||||
* @return a random double between min and max (inclusive)
|
||||
*/
|
||||
public static double generateRandomDouble(double min, double max) {
|
||||
return min + (max - min) * getInstance().random.nextDouble();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a random float between the specified minimum and maximum values (inclusive).
|
||||
*
|
||||
* @param min the minimum value
|
||||
* @param max the maximum value
|
||||
* @return a random float between min and max (inclusive)
|
||||
*/
|
||||
public static float generateRandomFloat(float min, float max) {
|
||||
return min + (max - min) * getInstance().random.nextFloat();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a random boolean value.
|
||||
*
|
||||
* @return a random boolean value
|
||||
*/
|
||||
public static boolean generateRandomBoolean() {
|
||||
return getInstance().random.nextBoolean();
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects a random element from the specified array.
|
||||
*
|
||||
* @param array the array to select a random element from
|
||||
* @param <T> the type of the elements in the array
|
||||
* @return a random element from the array
|
||||
*/
|
||||
public static <T> T getRandomElementFromArray(T[] array) {
|
||||
int index = getInstance().random.nextInt(array.length);
|
||||
return array[index];
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a random value based on a triangular distribution.
|
||||
*
|
||||
* @param mode the mode (peak) of the distribution
|
||||
* @param deviation the deviation from the mode
|
||||
* @return a random value based on a triangular distribution
|
||||
*/
|
||||
public static double triangle(double mode, double deviation) {
|
||||
return mode + deviation * (generateRandomDouble(0,1) - generateRandomDouble(0,1));
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects a specified number of random elements from the given array.
|
||||
*
|
||||
* @param array the array to select random elements from
|
||||
* @param count the number of random elements to select
|
||||
* @param <T> the type of the elements in the array
|
||||
* @return an array containing the selected random elements
|
||||
* @throws IllegalArgumentException if the count is greater than the array length
|
||||
*/
|
||||
public static <T> T[] getRandomElementsFromArray(T[] array, int count) {
|
||||
if (count > array.length) {
|
||||
throw new IllegalArgumentException("Count cannot be greater than array length");
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
T[] result = (T[]) new Object[count];
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
int index = getInstance().random.nextInt(array.length - i);
|
||||
result[i] = array[index];
|
||||
array[index] = array[array.length - i - 1];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,323 @@
|
||||
package net.momirealms.customnameplates.common.util;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class ReflectionUtils {
|
||||
|
||||
public static Class<?> getClazz(String... classes) {
|
||||
for (String className : classes) {
|
||||
Class<?> clazz = getClazz(className);
|
||||
if (clazz != null) {
|
||||
return clazz;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Class<?> getClazz(String clazz) {
|
||||
try {
|
||||
return Class.forName(clazz);
|
||||
} catch (Throwable e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean classExists(@NotNull final String clazz) {
|
||||
try {
|
||||
Class.forName(clazz);
|
||||
return true;
|
||||
} catch (Throwable e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean methodExists(@NotNull final Class<?> clazz, @NotNull final String method, @NotNull final Class<?>... parameterTypes) {
|
||||
try {
|
||||
clazz.getMethod(method, parameterTypes);
|
||||
return true;
|
||||
} catch (NoSuchMethodException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static Field getDeclaredField(final Class<?> clazz, final String field) {
|
||||
try {
|
||||
return setAccessible(clazz.getDeclaredField(field));
|
||||
} catch (NoSuchFieldException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static Field getDeclaredField(@NotNull Class<?> clazz, @NotNull String... possibleNames) {
|
||||
List<String> possibleNameList = Arrays.asList(possibleNames);
|
||||
for (Field field : clazz.getDeclaredFields()) {
|
||||
if (possibleNameList.contains(field.getName())) {
|
||||
return field;
|
||||
}
|
||||
}
|
||||
throw new RuntimeException("Class " + clazz.getName() + " does not contain a field with possible names " + Arrays.toString(possibleNames));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static Field getDeclaredField(final Class<?> clazz, final int index) {
|
||||
int i = 0;
|
||||
for (final Field field : clazz.getDeclaredFields()) {
|
||||
if (index == i) {
|
||||
return setAccessible(field);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static Field getInstanceDeclaredField(final Class<?> clazz, final int index) {
|
||||
int i = 0;
|
||||
for (final Field field : clazz.getDeclaredFields()) {
|
||||
if (!Modifier.isStatic(field.getModifiers())) {
|
||||
if (index == i) {
|
||||
return setAccessible(field);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static Field getDeclaredField(final Class<?> clazz, final Class<?> type, int index) {
|
||||
int i = 0;
|
||||
for (final Field field : clazz.getDeclaredFields()) {
|
||||
if (field.getType() == type) {
|
||||
if (index == i) {
|
||||
return setAccessible(field);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static Field getInstanceDeclaredField(@NotNull Class<?> clazz, final Class<?> type, int index) {
|
||||
int i = 0;
|
||||
for (final Field field : clazz.getDeclaredFields()) {
|
||||
if (field.getType() == type && !Modifier.isStatic(field.getModifiers())) {
|
||||
if (index == i) {
|
||||
return setAccessible(field);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static List<Field> getDeclaredFields(final Class<?> clazz) {
|
||||
List<Field> fields = new ArrayList<>();
|
||||
for (Field field : clazz.getDeclaredFields()) {
|
||||
fields.add(setAccessible(field));
|
||||
}
|
||||
return fields;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static List<Field> getInstanceDeclaredFields(@NotNull Class<?> clazz) {
|
||||
List<Field> list = new ArrayList<>();
|
||||
for (Field field : clazz.getDeclaredFields()) {
|
||||
if (!Modifier.isStatic(field.getModifiers())) {
|
||||
list.add(setAccessible(field));
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static List<Field> getDeclaredFields(@NotNull final Class<?> clazz, @NotNull final Class<?> type) {
|
||||
List<Field> fields = new ArrayList<>();
|
||||
for (Field field : clazz.getDeclaredFields()) {
|
||||
if (field.getType() == type) {
|
||||
fields.add(setAccessible(field));
|
||||
}
|
||||
}
|
||||
return fields;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static List<Field> getInstanceDeclaredFields(@NotNull Class<?> clazz, @NotNull Class<?> type) {
|
||||
List<Field> list = new ArrayList<>();
|
||||
for (Field field : clazz.getDeclaredFields()) {
|
||||
if (field.getType() == type && !Modifier.isStatic(field.getModifiers())) {
|
||||
list.add(setAccessible(field));
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static Method getMethod(final Class<?> clazz, final String[] possibleMethodNames, final Class<?>... parameterTypes) {
|
||||
outer:
|
||||
for (Method method : clazz.getMethods()) {
|
||||
if (method.getParameterCount() != parameterTypes.length) {
|
||||
continue;
|
||||
}
|
||||
Class<?>[] types = method.getParameterTypes();
|
||||
for (int i = 0; i < types.length; i++) {
|
||||
if (types[i] != parameterTypes[i]) {
|
||||
continue outer;
|
||||
}
|
||||
}
|
||||
for (String name : possibleMethodNames) {
|
||||
if (name.equals(method.getName())) return method;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static Method getMethod(final Class<?> clazz, Class<?> returnType, final Class<?>... parameterTypes) {
|
||||
outer:
|
||||
for (Method method : clazz.getMethods()) {
|
||||
if (method.getParameterCount() != parameterTypes.length) {
|
||||
continue;
|
||||
}
|
||||
Class<?>[] types = method.getParameterTypes();
|
||||
for (int i = 0; i < types.length; i++) {
|
||||
if (types[i] != parameterTypes[i]) {
|
||||
continue outer;
|
||||
}
|
||||
}
|
||||
if (returnType.isAssignableFrom(method.getReturnType())) return method;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static Method getStaticMethod(final Class<?> clazz, Class<?> returnType, final Class<?>... parameterTypes) {
|
||||
outer:
|
||||
for (Method method : clazz.getMethods()) {
|
||||
if (method.getParameterCount() != parameterTypes.length) {
|
||||
continue;
|
||||
}
|
||||
if (!Modifier.isStatic(method.getModifiers())) {
|
||||
continue;
|
||||
}
|
||||
Class<?>[] types = method.getParameterTypes();
|
||||
for (int i = 0; i < types.length; i++) {
|
||||
if (types[i] != parameterTypes[i]) {
|
||||
continue outer;
|
||||
}
|
||||
}
|
||||
if (returnType.isAssignableFrom(method.getReturnType())) return method;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Method getStaticMethod(final Class<?> clazz, int index) {
|
||||
int i = 0;
|
||||
for (Method method : clazz.getMethods()) {
|
||||
if (Modifier.isStatic(method.getModifiers())) {
|
||||
if (i == index) {
|
||||
return setAccessible(method);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static Method getMethod(final Class<?> clazz, int index) {
|
||||
int i = 0;
|
||||
for (Method method : clazz.getMethods()) {
|
||||
if (i == index) {
|
||||
return setAccessible(method);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Method getMethodOrElseThrow(final Class<?> clazz, final String[] possibleMethodNames, final Class<?>[] parameterTypes) throws NoSuchMethodException {
|
||||
Method method = getMethod(clazz, possibleMethodNames, parameterTypes);
|
||||
if (method == null) {
|
||||
throw new NoSuchMethodException("No method found with possible names " + Arrays.toString(possibleMethodNames) + " with parameters " +
|
||||
Arrays.toString(parameterTypes) + " in class " + clazz.getName());
|
||||
}
|
||||
return method;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static List<Method> getMethods(@NotNull Class<?> clazz, @NotNull Class<?> returnType, @NotNull Class<?>... parameterTypes) {
|
||||
List<Method> list = new ArrayList<>();
|
||||
for (Method method : clazz.getMethods()) {
|
||||
if (!returnType.isAssignableFrom(method.getReturnType()) // check type
|
||||
|| method.getParameterCount() != parameterTypes.length // check length
|
||||
) continue;
|
||||
Class<?>[] types = method.getParameterTypes();
|
||||
outer: {
|
||||
for (int i = 0; i < types.length; i++) {
|
||||
if (types[i] != parameterTypes[i]) {
|
||||
break outer;
|
||||
}
|
||||
}
|
||||
list.add(method);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static <T extends AccessibleObject> T setAccessible(@NotNull final T o) {
|
||||
o.setAccessible(true);
|
||||
return o;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static Constructor<?> getConstructor(Class<?> clazz, Class<?>... parameterTypes) {
|
||||
try {
|
||||
return clazz.getConstructor(parameterTypes);
|
||||
} catch (NoSuchMethodException | SecurityException ignore) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static Constructor<?> getDeclaredConstructor(Class<?> clazz, Class<?>... parameterTypes) {
|
||||
try {
|
||||
return setAccessible(clazz.getDeclaredConstructor(parameterTypes));
|
||||
} catch (NoSuchMethodException | SecurityException ignore) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static Constructor<?> getConstructor(Class<?> clazz, int index) {
|
||||
try {
|
||||
Constructor<?>[] constructors = clazz.getDeclaredConstructors();
|
||||
if (index < 0 || index >= constructors.length) {
|
||||
throw new IndexOutOfBoundsException("Invalid constructor index: " + index);
|
||||
}
|
||||
return constructors[index];
|
||||
} catch (SecurityException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static Constructor<?> getTheOnlyConstructor(Class<?> clazz) {
|
||||
Constructor<?>[] constructors = clazz.getConstructors();
|
||||
if (constructors.length != 1) {
|
||||
throw new RuntimeException("This class is expected to have only one constructor but it has " + constructors.length);
|
||||
}
|
||||
return constructors[0];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright (C) <2024> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customnameplates.common.util;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
public class SerializationUtils {
|
||||
|
||||
private SerializationUtils() {
|
||||
}
|
||||
|
||||
static class ClassLoaderAwareObjectInputStream extends ObjectInputStream {
|
||||
private static final Map<String, Class<?>> PRIMITIVE_TYPES = new HashMap<>();
|
||||
|
||||
static {
|
||||
PRIMITIVE_TYPES.put("byte", byte.class);
|
||||
PRIMITIVE_TYPES.put("short", short.class);
|
||||
PRIMITIVE_TYPES.put("int", int.class);
|
||||
PRIMITIVE_TYPES.put("long", long.class);
|
||||
PRIMITIVE_TYPES.put("float", float.class);
|
||||
PRIMITIVE_TYPES.put("double", double.class);
|
||||
PRIMITIVE_TYPES.put("boolean", boolean.class);
|
||||
PRIMITIVE_TYPES.put("char", char.class);
|
||||
PRIMITIVE_TYPES.put("void", void.class);
|
||||
}
|
||||
|
||||
private final ClassLoader classLoader;
|
||||
|
||||
ClassLoaderAwareObjectInputStream(InputStream in, ClassLoader classLoader) throws IOException {
|
||||
super(in);
|
||||
this.classLoader = classLoader;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
|
||||
String className = desc.getName();
|
||||
try {
|
||||
return Class.forName(className, false, classLoader);
|
||||
} catch (ClassNotFoundException e) {
|
||||
Class<?> primitiveClass = PRIMITIVE_TYPES.get(className);
|
||||
if (primitiveClass != null) {
|
||||
return primitiveClass;
|
||||
}
|
||||
return super.resolveClass(desc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static <T extends Serializable> T clone(T object) {
|
||||
if (object == null) {
|
||||
return null;
|
||||
}
|
||||
byte[] objectData = serialize(object);
|
||||
try (ByteArrayInputStream bais = new ByteArrayInputStream(objectData);
|
||||
ClassLoaderAwareObjectInputStream ois = new ClassLoaderAwareObjectInputStream(bais, object.getClass().getClassLoader())) {
|
||||
@SuppressWarnings("unchecked")
|
||||
T clonedObject = (T) ois.readObject();
|
||||
return clonedObject;
|
||||
} catch (IOException | ClassNotFoundException e) {
|
||||
throw new RuntimeException("Exception occurred while cloning object", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> T deserialize(byte[] objectData) {
|
||||
Objects.requireNonNull(objectData);
|
||||
return deserialize(new ByteArrayInputStream(objectData));
|
||||
}
|
||||
|
||||
public static <T> T deserialize(InputStream inputStream) {
|
||||
Objects.requireNonNull(inputStream);
|
||||
try (ObjectInputStream ois = new ObjectInputStream(inputStream)) {
|
||||
@SuppressWarnings("unchecked")
|
||||
T obj = (T) ois.readObject();
|
||||
return obj;
|
||||
} catch (IOException | ClassNotFoundException e) {
|
||||
throw new RuntimeException("Exception occurred while deserializing object", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] serialize(Serializable obj) {
|
||||
try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos)) {
|
||||
oos.writeObject(obj);
|
||||
return baos.toByteArray();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Exception occurred while serializing object", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
* Copyright (C) <2024> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -15,11 +15,8 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customnameplates.common;
|
||||
package net.momirealms.customnameplates.common.util;
|
||||
|
||||
public record Pair<L, R>(L left, R right) {
|
||||
|
||||
public static <L, R> Pair<L, R> of(final L left, final R right) {
|
||||
return new Pair<>(left, right);
|
||||
}
|
||||
public interface TriConsumer<K, V, S> {
|
||||
void accept(K k, V v, S s);
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (C) <2024> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customnameplates.common.util;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.function.Function;
|
||||
|
||||
public interface TriFunction<T, U, V, R> {
|
||||
R apply(T var1, U var2, V var3);
|
||||
|
||||
default <W> TriFunction<T, U, V, W> andThen(Function<? super R, ? extends W> after) {
|
||||
Objects.requireNonNull(after);
|
||||
return (t, u, v) -> {
|
||||
return after.apply(this.apply(t, u, v));
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* 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 net.momirealms.customnameplates.common.util;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Represents three different states of a setting.
|
||||
*
|
||||
* <p>Possible values:</p>
|
||||
* <p></p>
|
||||
* <ul>
|
||||
* <li>{@link #TRUE} - a positive setting</li>
|
||||
* <li>{@link #FALSE} - a negative (negated) setting</li>
|
||||
* <li>{@link #UNDEFINED} - a non-existent setting</li>
|
||||
* </ul>
|
||||
*/
|
||||
public enum Tristate {
|
||||
|
||||
/**
|
||||
* A value indicating a positive setting
|
||||
*/
|
||||
TRUE(true),
|
||||
|
||||
/**
|
||||
* A value indicating a negative (negated) setting
|
||||
*/
|
||||
FALSE(false),
|
||||
|
||||
/**
|
||||
* A value indicating a non-existent setting
|
||||
*/
|
||||
UNDEFINED(false);
|
||||
|
||||
/**
|
||||
* Returns a {@link Tristate} from a boolean
|
||||
*
|
||||
* @param val the boolean value
|
||||
* @return {@link #TRUE} or {@link #FALSE}, if the value is <code>true</code> or <code>false</code>, respectively.
|
||||
*/
|
||||
public static @NotNull Tristate of(boolean val) {
|
||||
return val ? TRUE : FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link Tristate} from a nullable boolean.
|
||||
*
|
||||
* <p>Unlike {@link #of(boolean)}, this method returns {@link #UNDEFINED}
|
||||
* if the value is null.</p>
|
||||
*
|
||||
* @param val the boolean value
|
||||
* @return {@link #UNDEFINED}, {@link #TRUE} or {@link #FALSE}, if the value
|
||||
* is <code>null</code>, <code>true</code> or <code>false</code>, respectively.
|
||||
*/
|
||||
public static @NotNull Tristate of(Boolean val) {
|
||||
return val == null ? UNDEFINED : val ? TRUE : FALSE;
|
||||
}
|
||||
|
||||
private final boolean booleanValue;
|
||||
|
||||
Tristate(boolean booleanValue) {
|
||||
this.booleanValue = booleanValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the Tristate as a boolean.
|
||||
*
|
||||
* <p>A value of {@link #UNDEFINED} converts to false.</p>
|
||||
*
|
||||
* @return a boolean representation of the Tristate.
|
||||
*/
|
||||
public boolean asBoolean() {
|
||||
return this.booleanValue;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (C) <2024> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customnameplates.common.util;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* A generic class representing a tuple with three values.
|
||||
* This class provides methods for creating and accessing tuples with three values.
|
||||
*
|
||||
* @param <L> the type of the left value
|
||||
* @param <M> the type of the middle value
|
||||
* @param <R> the type of the right value
|
||||
*/
|
||||
public record Tuple<L, M, R>(L left, M mid, R right) {
|
||||
|
||||
/**
|
||||
* Creates a new {@link Tuple} with the specified left, middle, and right values.
|
||||
*
|
||||
* @param left the left value
|
||||
* @param mid the middle value
|
||||
* @param right the right value
|
||||
* @param <L> the type of the left value
|
||||
* @param <M> the type of the middle value
|
||||
* @param <R> the type of the right value
|
||||
* @return a new {@link Tuple} with the specified values
|
||||
*/
|
||||
public static <L, M, R> Tuple<L, M, R> of(final L left, final M mid, final R right) {
|
||||
return new Tuple<>(left, mid, right);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object object) {
|
||||
if (this == object) return true;
|
||||
if (object == null || getClass() != object.getClass()) return false;
|
||||
Tuple<?, ?, ?> tuple = (Tuple<?, ?, ?>) object;
|
||||
return Objects.equals(mid, tuple.mid) && Objects.equals(left, tuple.left) && Objects.equals(right, tuple.right);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(left, mid, right);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright (C) <2024> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customnameplates.common.util;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Utility class for handling operations related to UUIDs.
|
||||
* Provides methods for converting between different UUID formats and representations.
|
||||
*/
|
||||
public class UUIDUtils {
|
||||
|
||||
/**
|
||||
* Converts a UUID string without dashes to a {@link UUID} object.
|
||||
*
|
||||
* @param id the UUID string without dashes
|
||||
* @return the corresponding {@link UUID} object, or null if the input string is null
|
||||
*/
|
||||
public static UUID fromUnDashedUUID(String id) {
|
||||
return id == null ? null : new UUID(
|
||||
new BigInteger(id.substring(0, 16), 16).longValue(),
|
||||
new BigInteger(id.substring(16, 32), 16).longValue()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a {@link UUID} object to a string without dashes.
|
||||
*
|
||||
* @param uuid the {@link UUID} object
|
||||
* @return the UUID string without dashes
|
||||
*/
|
||||
public static String toUnDashedUUID(UUID uuid) {
|
||||
return uuid.toString().replace("-", "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an integer array to a {@link UUID} object.
|
||||
* The array must contain exactly four integers.
|
||||
*
|
||||
* @param array the integer array
|
||||
* @return the corresponding {@link UUID} object
|
||||
* @throws IllegalArgumentException if the array length is not four
|
||||
*/
|
||||
public static UUID uuidFromIntArray(int[] array) {
|
||||
return new UUID((long)array[0] << 32 | (long)array[1] & 4294967295L, (long)array[2] << 32 | (long)array[3] & 4294967295L);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a {@link UUID} object to an integer array.
|
||||
* The resulting array contains exactly four integers.
|
||||
*
|
||||
* @param uuid the {@link UUID} object
|
||||
* @return the integer array representation of the UUID
|
||||
*/
|
||||
public static int[] uuidToIntArray(UUID uuid) {
|
||||
long l = uuid.getMostSignificantBits();
|
||||
long m = uuid.getLeastSignificantBits();
|
||||
return leastMostToIntArray(l, m);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the most significant and least significant bits of a UUID to an integer array.
|
||||
*
|
||||
* @param uuidMost the most significant bits of the UUID
|
||||
* @param uuidLeast the least significant bits of the UUID
|
||||
* @return the integer array representation of the UUID bits
|
||||
*/
|
||||
private static int[] leastMostToIntArray(long uuidMost, long uuidLeast) {
|
||||
return new int[]{(int)(uuidMost >> 32), (int)uuidMost, (int)(uuidLeast >> 32), (int)uuidLeast};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Copyright (C) <2024> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customnameplates.common.util;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Utility class for selecting random items based on weights.
|
||||
*/
|
||||
@SuppressWarnings("DuplicatedCode")
|
||||
public class WeightUtils {
|
||||
|
||||
private WeightUtils() {}
|
||||
|
||||
/**
|
||||
* Get a random item from a list of pairs, each associated with a weight.
|
||||
*
|
||||
* @param pairs A list of pairs where the left element is the item and the right element is its weight.
|
||||
* @param <T> The type of items in the list.
|
||||
* @return A randomly selected item from the list, or null if no item was selected.
|
||||
*/
|
||||
public static <T> T getRandom(List<Pair<T, Double>> pairs) {
|
||||
List<T> available = new ArrayList<>();
|
||||
double[] weights = new double[pairs.size()];
|
||||
int index = 0;
|
||||
for (Pair<T, Double> pair : pairs){
|
||||
double weight = pair.right();
|
||||
T key = pair.left();
|
||||
if (weight <= 0) continue;
|
||||
available.add(key);
|
||||
weights[index++] = weight;
|
||||
}
|
||||
return getRandom(weights, available, index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a random item from a map where each entry is associated with a weight.
|
||||
*
|
||||
* @param map A map where each entry's key is an item, and the value is its weight.
|
||||
* @param <T> The type of items in the map.
|
||||
* @return A randomly selected item from the map, or null if no item was selected.
|
||||
*/
|
||||
public static <T> T getRandom(Map<T, Double> map) {
|
||||
List<T> available = new ArrayList<>();
|
||||
double[] weights = new double[map.size()];
|
||||
int index = 0;
|
||||
for (Map.Entry<T, Double> entry : map.entrySet()){
|
||||
double weight = entry.getValue();
|
||||
T key = entry.getKey();
|
||||
if (weight <= 0) continue;
|
||||
available.add(key);
|
||||
weights[index++] = weight;
|
||||
}
|
||||
return getRandom(weights, available, index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a random item from a list of items with associated weights.
|
||||
*
|
||||
* @param weights An array of weights corresponding to the available items.
|
||||
* @param available A list of available items.
|
||||
* @param effectiveSize The effective size of the array and list after filtering out items with non-positive weights.
|
||||
* @param <T> The type of items.
|
||||
* @return A randomly selected item from the list, or null if no item was selected.
|
||||
*/
|
||||
private static <T> T getRandom(double[] weights, List<T> available, int effectiveSize) {
|
||||
if (available.isEmpty()) return null;
|
||||
double total = Arrays.stream(weights).sum();
|
||||
double[] weightRatios = new double[effectiveSize];
|
||||
for (int i = 0; i < effectiveSize; i++){
|
||||
weightRatios[i] = weights[i]/total;
|
||||
}
|
||||
double[] weightRange = new double[effectiveSize];
|
||||
double startPos = 0;
|
||||
for (int i = 0; i < effectiveSize; i++) {
|
||||
weightRange[i] = startPos + weightRatios[i];
|
||||
startPos += weightRatios[i];
|
||||
}
|
||||
double random = Math.random();
|
||||
int pos = Arrays.binarySearch(weightRange, random);
|
||||
|
||||
if (pos < 0) {
|
||||
pos = -pos - 1;
|
||||
}
|
||||
if (pos < weightRange.length && random < weightRange[pos]) {
|
||||
return available.get(pos);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
31
common/src/main/resources/custom-nameplates.properties
Normal file
31
common/src/main/resources/custom-nameplates.properties
Normal file
@@ -0,0 +1,31 @@
|
||||
builder=${builder}
|
||||
git=${git_version}
|
||||
config=${config_version}
|
||||
asm=${asm_version}
|
||||
asm-commons=${asm_commons_version}
|
||||
jar-relocator=${jar_relocator_version}
|
||||
cloud-core=${cloud_core_version}
|
||||
cloud-brigadier=${cloud_brigadier_version}
|
||||
cloud-services=${cloud_services_version}
|
||||
cloud-bukkit=${cloud_bukkit_version}
|
||||
cloud-paper=${cloud_paper_version}
|
||||
cloud-minecraft-extras=${cloud_minecraft_extras_version}
|
||||
boosted-yaml=${boosted_yaml_version}
|
||||
bstats-base=${bstats_version}
|
||||
geantyref=${geantyref_version}
|
||||
gson=${gson_version}
|
||||
caffeine=${caffeine_version}
|
||||
exp4j=${exp4j_version}
|
||||
slf4j=${slf4j_version}
|
||||
mongodb-driver-core=${mongodb_driver_version}
|
||||
mariadb-java-client=${mariadb_driver_version}
|
||||
mysql-connector-j=${mysql_driver_version}
|
||||
hikari-cp=${hikari_version}
|
||||
commons-pool=${commons_pool_version}
|
||||
jedis=${jedis_version}
|
||||
h2-driver=${h2_driver_version}
|
||||
sqlite-driver=${sqlite_driver_version}
|
||||
fontbox=${fontbox_version}
|
||||
pdfbox-io=${pdfbox_io_version}
|
||||
byte-buddy=${byte_buddy_version}
|
||||
commons-io=${commons_io_version}
|
||||
80
common/src/main/resources/translations/en.yml
Normal file
80
common/src/main/resources/translations/en.yml
Normal file
@@ -0,0 +1,80 @@
|
||||
# Don't change this
|
||||
config-version: "36"
|
||||
|
||||
exception.invalid_syntax: "<red>Invalid syntax. Correct syntax: <white><arg:0></white></red>"
|
||||
exception.invalid_argument: "<red>Invalid argument. Reason: <white><arg:0></white></red>"
|
||||
exception.invalid_sender: "<red><arg:0> is not allowed to execute that command. Must be of type <arg:1></red>"
|
||||
exception.unexpected: "<red>An internal error occurred while attempting to perform this command</red>"
|
||||
exception.no_permission: "<red>I'm sorry, but you do not have permission to perform this command</red>"
|
||||
exception.no_such_command: "Unknown command."
|
||||
argument.entity.notfound.player: "<red><lang:argument.entity.notfound.player></red>"
|
||||
argument.entity.notfound.entity: "<red><lang:argument.entity.notfound.entity></red>"
|
||||
argument.parse.failure.time: "<red>'<arg:0>' is not a valid time format</red>"
|
||||
argument.parse.failure.material: "<red>'<arg:0>' is not a valid material name</red>"
|
||||
argument.parse.failure.enchantment: "<red>'<arg:0>' is not a valid enchantment</red>"
|
||||
argument.parse.failure.offlineplayer: "<red>No player found for input '<arg:0>'</red>"
|
||||
argument.parse.failure.player: "<red>No player found for input '<arg:0>'</red>"
|
||||
argument.parse.failure.world: "<red>'<arg:0>' is not a valid Minecraft world</red>"
|
||||
argument.parse.failure.location.invalid_format: "<red>'<arg:0>' is not a valid location. Required format is '<arg:1> <arg:2> <arg:3></red>"
|
||||
argument.parse.failure.location.mixed_local_absolute: "<red>Cannot mix local and absolute coordinates. (either all coordinates use '^' or none do)</red>"
|
||||
argument.parse.failure.namespacedkey.namespace: "<red>Invalid namespace '<arg:0>'. Must be [a-z0-9._-]</red>"
|
||||
argument.parse.failure.namespacedkey.key: "<red>Invalid key '<arg:0>'. Must be [a-z0-9/._-]</red>"
|
||||
argument.parse.failure.namespacedkey.need_namespace: "<red>Invalid input '<arg:0>', requires an explicit namespace</red>"
|
||||
argument.parse.failure.boolean: "<red>Could not parse boolean from '<arg:0>'</red>"
|
||||
argument.parse.failure.number: "<red>'<arg:0>' is not a valid number in the range <arg:1> to <arg:2></red>"
|
||||
argument.parse.failure.char: "<red>'<arg:0>' is not a valid character</red>"
|
||||
argument.parse.failure.string: "<red>'<arg:0>' is not a valid string of type <arg:1></red>"
|
||||
argument.parse.failure.uuid: "<red>'<arg:0>' is not a valid UUID</red>"
|
||||
argument.parse.failure.enum: "<red>'<arg:0>' is not one of the following: <arg:1></red>"
|
||||
argument.parse.failure.regex: "<red>'<arg:0>' does not match '<arg:1>'</red>"
|
||||
argument.parse.failure.flag.unknown: "<red>Unknown flag '<arg:0>'</red>"
|
||||
argument.parse.failure.flag.duplicate_flag: "<red>Duplicate flag '<arg:0>'</red>"
|
||||
argument.parse.failure.flag.no_flag_started: "<red>No flag started. Don't know what to do with '<arg:0>'</red>"
|
||||
argument.parse.failure.flag.missing_argument: "<red>Missing argument for '<arg:0>'</red>"
|
||||
argument.parse.failure.flag.no_permission: "<red>You don't have permission to use '<arg:0>'</red>"
|
||||
argument.parse.failure.color: "<red>'<arg:0>' is not a valid color</red>"
|
||||
argument.parse.failure.duration: "<red>'<arg:0>' is not a duration format</red>"
|
||||
argument.parse.failure.aggregate.missing: "<red>Missing component '<arg:0>'</red>"
|
||||
argument.parse.failure.aggregate.failure: "<red>Invalid component '<arg:0>': <arg:1></red>"
|
||||
argument.parse.failure.either: "<red>Could not resolve <arg:1> or <arg:2> from '<arg:0>'</red>"
|
||||
argument.parse.failure.namedtextcolor: "<red>'<arg:0>' is not a named text color</red>"
|
||||
command.reload.success: "<white>Reloaded. Took <green><arg:0></green> ms.</white>"
|
||||
command.reload.generating: "<white>Generating resource packs...</white>"
|
||||
command.reload.generated: "<white>Generated. Took <green><arg:0></green> ms.</white>"
|
||||
command.debug.performance:
|
||||
- "<white>Average thread load in the past minute: <arg:0>%</white> <gray>(<arg:1>/50ms)</gray>"
|
||||
- "<white>Check conditions:<arg:2>ms</white>"
|
||||
- "<white>Update placeholders:<arg:3>ms</white>"
|
||||
command.nameplates.equip.failure.not_exists: "<red><arg:0> doesn't exist</red>"
|
||||
command.nameplates.equip.failure.permission: "<red>You don't have permission to use this nameplate</red>"
|
||||
command.nameplates.equip.failure.no_change: "<red>Nothing changed as you have already equipped this nameplate</red>"
|
||||
command.nameplates.equip.success: "<white>Successfully equipped <arg:0> </white><yellow><click:run_command:/nameplates preview>[click to preview]</click></yellow>" # use <arg:1> for display name
|
||||
command.nameplates.unequip.failure.not_equip: "<red>You are not equipping any nameplate</red>"
|
||||
command.nameplates.unequip.success: "<white>You removed your nameplate</white>"
|
||||
command.nameplates.preview.failure.cooldown: "<red>Previewing is still Ongoing!</red>"
|
||||
command.nameplates.preview.success: "<white>Now previewing your nameplate (Go to third person to view)!</white>"
|
||||
command.nameplates.list.failure.none: "<white>You don't have any nameplate yet</white>"
|
||||
command.nameplates.list.success: "<white>Available nameplates: <arg:0></white>" # use <arg:1> for display names
|
||||
command.nameplates.list.delimiter: ", "
|
||||
command.nameplates.force_equip.failure.not_exists: "<red>Failed to equip nameplate for <arg:0> because <arg:1> doesn't exist</red>"
|
||||
command.nameplates.force_equip.success: "<white>Forced <arg:0> to equip <arg:1></white>" # use <arg:2> for display name
|
||||
command.nameplates.force_unequip.success: "<white>Removed <arg:0>'s nameplate</white>"
|
||||
command.nameplates.force_preview.success: "<white>Forced <arg:0> to preview nameplates</white>"
|
||||
command.bubbles.equip.failure.not_exists: "<red><arg:0> doesn't exist</red>"
|
||||
command.bubbles.equip.failure.permission: "<red>You don't have permission to use this bubble</red>"
|
||||
command.bubbles.equip.failure.no_change: "<red>Nothing changed as you have already equipped this bubble</red>"
|
||||
command.bubbles.equip.success: "<white>Successfully equipped <arg:0></white>" # use <arg:1> for display name
|
||||
command.bubbles.unequip.failure.not_equip: "<red>You don't have any bubble enabled</red>"
|
||||
command.bubbles.unequip.success: "<white>You removed your bubbles</white>"
|
||||
command.bubbles.list.failure.none: "<white>You don't have any bubble yet</white>"
|
||||
command.bubbles.list.success: "<white>Available bubbles: <arg:0></white>" # use <arg:1> for display names
|
||||
command.bubbles.list.delimiter: ", "
|
||||
command.bubbles.force_equip.failure.not_exists: "<red>Failed to apply bubbles to <arg:0> because <arg:1> doesn't exist</red>"
|
||||
command.bubbles.force_equip.success: "<white>Forced to apply <arg:1> to <arg:0></white>" # use <arg:2> for display name
|
||||
command.bubbles.force_unequip.success: "<white>Removed <arg:0>'s bubbles</white>"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
74
common/src/main/resources/translations/zh_cn.yml
Normal file
74
common/src/main/resources/translations/zh_cn.yml
Normal file
@@ -0,0 +1,74 @@
|
||||
# 别动这个
|
||||
config-version: "36"
|
||||
|
||||
exception.invalid_syntax: "<red>无效语法. 正确语法:<white><arg:0></white></red>"
|
||||
exception.invalid_argument: "<red>无效参数. 原因:<white><arg:0></white></red>"
|
||||
exception.invalid_sender: "<red><arg:0> 不允许执行该命令. 执行者必须是 <arg:1></red>"
|
||||
exception.unexpected: "<red>执行该命令时发生内部错误</red>"
|
||||
exception.no_permission: "<red>抱歉, 您没有权限执行该命令</red>"
|
||||
exception.no_such_command: "未知命令"
|
||||
argument.entity.notfound.player: "<red>找不到玩家 '<arg:0>'</red>"
|
||||
argument.entity.notfound.entity: "<red>找不到实体 '<arg:0>'</red>"
|
||||
argument.parse.failure.time: "<red>'<arg:0>' 不是有效的时间格式</red>"
|
||||
argument.parse.failure.material: "<red>'<arg:0>' 不是有效的材料</red>"
|
||||
argument.parse.failure.enchantment: "<red>'<arg:0>' 不是有效的魔咒</red>"
|
||||
argument.parse.failure.offlineplayer: "<red>输入的玩家 '<arg:0>' 已离线</red>"
|
||||
argument.parse.failure.player: "<red>找不到输入的玩家 '<arg:0>'</red>"
|
||||
argument.parse.failure.world: "<red>'<arg:0>' 不是有效的 Minecraft 世界名称</red>"
|
||||
argument.parse.failure.location.invalid_format: "<red>'<arg:0>' 不是有效的位置格式.必须格式为 '<arg:1> <arg:2> <arg:3>'</red>"
|
||||
argument.parse.failure.location.mixed_local_absolute: "<red>不能混用相对和绝对坐标.坐标要么全部使用 '^',要么全部不用</red>"
|
||||
argument.parse.failure.namespacedkey.namespace: "<red>无效的命名空间 '<arg:0>'.必须为 [a-z0-9._-]</red>"
|
||||
argument.parse.failure.namespacedkey.key: "<red>无效的键 '<arg:0>'.必须为 [a-z0-9/._-]</red>"
|
||||
argument.parse.failure.namespacedkey.need_namespace: "<red>无效的输入 '<arg:0>', 需要显式指定命名空间</red>"
|
||||
argument.parse.failure.boolean: "<red>无法解析布尔值 '<arg:0>'</red>"
|
||||
argument.parse.failure.number: "<red>'<arg:0>' 不是从 <arg:1> 到 <arg:2> 范围内的有效数字</red>"
|
||||
argument.parse.failure.char: "<red>'<arg:0>' 不是有效的字符</red>"
|
||||
argument.parse.failure.string: "<red>'<arg:0>' 不是类型为 <arg:1> 的有效字符串</red>"
|
||||
argument.parse.failure.uuid: "<red>'<arg:0>' 不是有效的 UUID</red>"
|
||||
argument.parse.failure.enum: "<red>'<arg:0>' 不是以下任何一种情况之一: <arg:1></red>"
|
||||
argument.parse.failure.regex: "<red>'<arg:0>' 不匹配 '<arg:1>'</red>"
|
||||
argument.parse.failure.flag.unknown: "<red>未知标志 '<arg:0>'</red>"
|
||||
argument.parse.failure.flag.duplicate_flag: "<red>重复的标志 '<arg:0>'</red>"
|
||||
argument.parse.failure.flag.no_flag_started: "<red>没有开始标志. 不知道如何处理 '<arg:0>'</red>"
|
||||
argument.parse.failure.flag.missing_argument: "<red>缺少 '<arg:0>' 参数</red>"
|
||||
argument.parse.failure.flag.no_permission: "<red>您没有权限使用 '<arg:0>'</red>"
|
||||
argument.parse.failure.color: "<red>'<arg:0>' 不是有效的颜色</red>"
|
||||
argument.parse.failure.duration: "<red>'<arg:0>' 不是有效的持续时间格式</red>"
|
||||
argument.parse.failure.aggregate.missing: "<red>缺少组件 '<arg:0>'</red>"
|
||||
argument.parse.failure.aggregate.failure: "<red>无效的组件 '<arg:0>': <arg:1></red>"
|
||||
argument.parse.failure.either: "<red>无法从 '<arg:0>' 解析 <arg:1> 或 <arg:2></red>"
|
||||
argument.parse.failure.namedtextcolor: "<red>'<arg:0>' 不是颜色代码</red>"
|
||||
command.reload.success: "<white>重新加载完成. 耗时 <green><arg:0></green> 毫秒</white>"
|
||||
command.reload.generating: "<white>生成资源包中...</white>"
|
||||
command.reload.generated: "<white>生成完毕. 耗时 <green><arg:0></green> 毫秒</white>"
|
||||
command.debug.performance:
|
||||
- "<white>过去一分钟的线程负载: <arg:0>%</white> <gray>(<arg:1>/50ms)</gray>"
|
||||
- "<white>刷新条件耗时:<arg:2>ms</white>"
|
||||
- "<white>更新变量耗时:<arg:3>ms</white>"
|
||||
command.nameplates.equip.failure.not_exists: "<red>铭牌 [<arg:0>] 不存在</red>"
|
||||
command.nameplates.equip.failure.permission: "<red>你尚未解锁这个铭牌</red>"
|
||||
command.nameplates.equip.failure.no_change: "<red>你已经佩戴这个铭牌了</red>"
|
||||
command.nameplates.equip.success: "<white>你佩戴上了 <arg:0></white> <yellow><click:run_command:/nameplates preview>[点击预览]</click></yellow>"
|
||||
command.nameplates.unequip.failure.not_equip: "<red>你当前未佩戴任何铭牌</red>"
|
||||
command.nameplates.unequip.success: "<white>你移除了你的铭牌</white>"
|
||||
command.nameplates.preview.failure.cooldown: "<red>上一轮预览尚未结束</red>"
|
||||
command.nameplates.preview.success: "<white>你正在预览你的铭牌 (前往第三人称查看)!</white>"
|
||||
command.nameplates.list.failure.none: "<white>你尚未拥有任何铭牌</white>"
|
||||
command.nameplates.list.success: "<white>可用的铭牌: <arg:0></white>"
|
||||
command.nameplates.list.delimiter: ", "
|
||||
command.nameplates.force_equip.failure.not_exists: "<red>由于 <arg:1> 不存在,无法为 <arg:0> 佩戴铭牌</red>"
|
||||
command.nameplates.force_equip.success: "<white>强制 <arg:0> 佩戴了铭牌 <arg:1></white>"
|
||||
command.nameplates.force_unequip.success: "<white>移除了 <arg:0> 的铭牌</white>"
|
||||
command.nameplates.force_preview.success: "<white>强制 <arg:0> 预览他的铭牌</white>"
|
||||
command.bubbles.equip.failure.not_exists: "<red>气泡 <arg:0> 不存在</red>"
|
||||
command.bubbles.equip.failure.permission: "<red>你尚未解锁这个气泡</red>"
|
||||
command.bubbles.equip.failure.no_change: "<red>你已经应用这个气泡了</red>"
|
||||
command.bubbles.equip.success: "<white>你正在使用气泡 <arg:0></white>"
|
||||
command.bubbles.unequip.failure.not_equip: "<red>你尚未应用气泡</red>"
|
||||
command.bubbles.unequip.success: "<white>你移除了你的气泡</white>"
|
||||
command.bubbles.list.failure.none: "<white>你尚未拥有任何气泡</white>"
|
||||
command.bubbles.list.success: "<white>可用的气泡: <arg:0></white>"
|
||||
command.bubbles.list.delimiter: ", "
|
||||
command.bubbles.force_equip.failure.not_exists: "<red>由于 <arg:0> 不存在,无法为 <arg:1> 应用气泡</red>"
|
||||
command.bubbles.force_equip.success: "<white>强制 <arg:0> 应用了气泡 <arg:1></white>"
|
||||
command.bubbles.force_unequip.success: "<white>移除了 <arg:0> 的气泡</white>"
|
||||
Reference in New Issue
Block a user