mirror of
https://github.com/Xiao-MoMi/Custom-Crops.git
synced 2025-12-26 02:19:28 +00:00
3.6
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
package net.momirealms.customcrops.common.annotation;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@ApiStatus.Internal
|
||||
@Target({ElementType.FIELD})
|
||||
public @interface DoNotUse {
|
||||
}
|
||||
@@ -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.customcrops.common.command;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.TranslatableComponent;
|
||||
import net.momirealms.customcrops.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 CustomCropsCommandManager<C> commandManager;
|
||||
protected CommandConfig<C> commandConfig;
|
||||
|
||||
public AbstractCommandFeature(CustomCropsCommandManager<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 CustomCropsCommandManager<C> getCustomCropsCommandManager() {
|
||||
return commandManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandConfig<C> getCommandConfig() {
|
||||
return commandConfig;
|
||||
}
|
||||
|
||||
public void setCommandConfig(CommandConfig<C> commandConfig) {
|
||||
this.commandConfig = commandConfig;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
/*
|
||||
* 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.customcrops.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.customcrops.common.locale.CustomCropsCaptionFormatter;
|
||||
import net.momirealms.customcrops.common.locale.CustomCropsCaptionProvider;
|
||||
import net.momirealms.customcrops.common.locale.TranslationManager;
|
||||
import net.momirealms.customcrops.common.plugin.CustomCropsPlugin;
|
||||
import net.momirealms.customcrops.common.sender.Sender;
|
||||
import net.momirealms.customcrops.common.util.ArrayUtils;
|
||||
import net.momirealms.customcrops.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 CustomCropsCommandManager<C> {
|
||||
|
||||
protected final HashSet<CommandComponent<C>> registeredRootCommandComponents = new HashSet<>();
|
||||
protected final HashSet<CommandFeature<C>> registeredFeatures = new HashSet<>();
|
||||
protected final CommandManager<C> commandManager;
|
||||
protected final CustomCropsPlugin plugin;
|
||||
private final CustomCropsCaptionFormatter<C> captionFormatter = new CustomCropsCaptionFormatter<C>();
|
||||
private final MinecraftExceptionHandler.Decorator<C> decorator = (formatter, ctx, msg) -> msg;
|
||||
|
||||
private TriConsumer<C, String, Component> feedbackConsumer;
|
||||
|
||||
public AbstractCommandManager(CustomCropsPlugin 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 CustomCropsCaptionProvider<>());
|
||||
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);
|
||||
}
|
||||
|
||||
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.customcrops.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.customcrops.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.customcrops.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);
|
||||
|
||||
CustomCropsCommandManager<C> getCustomCropsCommandManager();
|
||||
|
||||
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.customcrops.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.customcrops.common.util.TriConsumer;
|
||||
import org.incendo.cloud.Command;
|
||||
import org.incendo.cloud.CommandManager;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
public interface CustomCropsCommandManager<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,69 @@
|
||||
/*
|
||||
* 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.customcrops.common.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);
|
||||
|
||||
/**
|
||||
* Loads a YAML data file with a custom route separator.
|
||||
*
|
||||
* @param file the {@link File} object representing the data file
|
||||
* @param routeSeparator the custom route separator character
|
||||
* @return the loaded {@link YamlDocument}
|
||||
*/
|
||||
YamlDocument loadData(File file, char routeSeparator);
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
@@ -0,0 +1,268 @@
|
||||
/*
|
||||
* 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.customcrops.common.dependency;
|
||||
|
||||
import net.momirealms.customcrops.common.dependency.relocation.Relocation;
|
||||
import net.momirealms.customcrops.common.plugin.CustomCropsProperties;
|
||||
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 CustomCrops.
|
||||
*/
|
||||
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"
|
||||
),
|
||||
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")
|
||||
),
|
||||
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")
|
||||
),
|
||||
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"
|
||||
),
|
||||
ZSTD(
|
||||
"com.github.luben",
|
||||
"zstd-jni",
|
||||
"maven",
|
||||
"zstd-jni"
|
||||
);
|
||||
|
||||
private final List<Relocation> relocations;
|
||||
private final String repo;
|
||||
private final String groupId;
|
||||
private String rawArtifactId;
|
||||
private String customArtifactID;
|
||||
|
||||
private static final String MAVEN_FORMAT = "%s/%s/%s/%s-%s.jar";
|
||||
|
||||
Dependency(String groupId, String rawArtifactId, String repo, String customArtifactID) {
|
||||
this(groupId, rawArtifactId, repo, customArtifactID, new Relocation[0]);
|
||||
}
|
||||
|
||||
Dependency(String groupId, String rawArtifactId, String repo, String customArtifactID, Relocation... relocations) {
|
||||
this.rawArtifactId = rawArtifactId;
|
||||
this.groupId = groupId;
|
||||
this.relocations = new ArrayList<>(Arrays.stream(relocations).toList());
|
||||
this.repo = repo;
|
||||
this.customArtifactID = customArtifactID;
|
||||
}
|
||||
|
||||
public Dependency setCustomArtifactID(String customArtifactID) {
|
||||
this.customArtifactID = customArtifactID;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Dependency setRawArtifactID(String artifactId) {
|
||||
this.rawArtifactId = artifactId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getVersion() {
|
||||
return CustomCropsProperties.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(rawArtifactId),
|
||||
getVersion(),
|
||||
rewriteEscaping(rawArtifactId),
|
||||
getVersion()
|
||||
);
|
||||
}
|
||||
|
||||
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.customcrops.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.customcrops.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,227 @@
|
||||
/*
|
||||
* 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.customcrops.common.dependency;
|
||||
|
||||
import net.momirealms.customcrops.common.dependency.classloader.IsolatedClassLoader;
|
||||
import net.momirealms.customcrops.common.dependency.relocation.Relocation;
|
||||
import net.momirealms.customcrops.common.dependency.relocation.RelocationHandler;
|
||||
import net.momirealms.customcrops.common.plugin.CustomCropsPlugin;
|
||||
import net.momirealms.customcrops.common.plugin.classpath.ClassPathAppender;
|
||||
import net.momirealms.customcrops.common.util.FileUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
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 CustomCropsPlugin plugin;
|
||||
|
||||
public DependencyManagerImpl(CustomCropsPlugin 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(CustomCropsPlugin plugin) {
|
||||
Path cacheDirectory = plugin.getDataDirectory().resolve("libs");
|
||||
try {
|
||||
FileUtils.createDirectoriesIfNotExists(cacheDirectory);
|
||||
} 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.customcrops.common.dependency;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
|
||||
/**
|
||||
* Applies CustomCrops 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, ZSTD -> 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.customcrops.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 (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.customcrops.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 CustomCrops 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.customcrops.common.dependency.relocation;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public final class Relocation {
|
||||
private static final String RELOCATION_PREFIX = "net.momirealms.customcrops.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,90 @@
|
||||
/*
|
||||
* 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.customcrops.common.dependency.relocation;
|
||||
|
||||
import net.momirealms.customcrops.common.dependency.Dependency;
|
||||
import net.momirealms.customcrops.common.dependency.DependencyManager;
|
||||
import net.momirealms.customcrops.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.customcrops.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,285 @@
|
||||
/*
|
||||
* 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.customcrops.common.helper;
|
||||
|
||||
import com.github.benmanes.caffeine.cache.Cache;
|
||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
import net.kyori.adventure.audience.Audience;
|
||||
import net.kyori.adventure.key.Key;
|
||||
import net.kyori.adventure.sound.Sound;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
|
||||
import net.kyori.adventure.title.Title;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Helper class for handling Adventure components and related functionalities.
|
||||
*/
|
||||
public class AdventureHelper {
|
||||
|
||||
private final MiniMessage miniMessage;
|
||||
private final MiniMessage miniMessageStrict;
|
||||
private final GsonComponentSerializer gsonComponentSerializer;
|
||||
private final Cache<String, String> miniMessageToJsonCache = Caffeine.newBuilder().expireAfterWrite(5, TimeUnit.MINUTES).build();
|
||||
public static boolean legacySupport = false;
|
||||
|
||||
private AdventureHelper() {
|
||||
this.miniMessage = MiniMessage.builder().build();
|
||||
this.miniMessageStrict = MiniMessage.builder().strict(true).build();
|
||||
this.gsonComponentSerializer = GsonComponentSerializer.builder().build();
|
||||
}
|
||||
|
||||
private static class SingletonHolder {
|
||||
private static final AdventureHelper INSTANCE = new AdventureHelper();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the singleton instance of AdventureHelper.
|
||||
*
|
||||
* @return the singleton instance
|
||||
*/
|
||||
public static AdventureHelper getInstance() {
|
||||
return SingletonHolder.INSTANCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a MiniMessage string to a Component.
|
||||
*
|
||||
* @param text the MiniMessage string
|
||||
* @return the resulting Component
|
||||
*/
|
||||
public static Component miniMessage(String text) {
|
||||
if (legacySupport) {
|
||||
return getMiniMessage().deserialize(legacyToMiniMessage(text));
|
||||
} else {
|
||||
return getMiniMessage().deserialize(text);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the MiniMessage instance.
|
||||
*
|
||||
* @return the MiniMessage instance
|
||||
*/
|
||||
public static MiniMessage getMiniMessage() {
|
||||
return getInstance().miniMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the GsonComponentSerializer instance.
|
||||
*
|
||||
* @return the GsonComponentSerializer instance
|
||||
*/
|
||||
public static GsonComponentSerializer getGson() {
|
||||
return getInstance().gsonComponentSerializer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a MiniMessage string to a JSON string.
|
||||
*
|
||||
* @param miniMessage the MiniMessage string
|
||||
* @return the JSON string representation
|
||||
*/
|
||||
public static String miniMessageToJson(String miniMessage) {
|
||||
AdventureHelper instance = getInstance();
|
||||
return instance.miniMessageToJsonCache.get(miniMessage, (text) -> instance.gsonComponentSerializer.serialize(miniMessage(text)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a title to an audience.
|
||||
*
|
||||
* @param audience the audience to send the title to
|
||||
* @param title the title component
|
||||
* @param subtitle the subtitle component
|
||||
* @param fadeIn the fade-in duration in ticks
|
||||
* @param stay the stay duration in ticks
|
||||
* @param fadeOut the fade-out duration in ticks
|
||||
*/
|
||||
public static void sendTitle(Audience audience, Component title, Component subtitle, int fadeIn, int stay, int fadeOut) {
|
||||
audience.showTitle(Title.title(title, subtitle, Title.Times.times(Duration.ofMillis(fadeIn * 50L), Duration.ofMillis(stay * 50L), Duration.ofMillis(fadeOut * 50L))));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an action bar message to an audience.
|
||||
*
|
||||
* @param audience the audience to send the action bar message to
|
||||
* @param actionBar the action bar component
|
||||
*/
|
||||
public static void sendActionBar(Audience audience, Component actionBar) {
|
||||
audience.sendActionBar(actionBar);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a message to an audience.
|
||||
*
|
||||
* @param audience the audience to send the message to
|
||||
* @param message the message component
|
||||
*/
|
||||
public static void sendMessage(Audience audience, Component message) {
|
||||
audience.sendMessage(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Plays a sound for an audience.
|
||||
*
|
||||
* @param audience the audience to play the sound for
|
||||
* @param sound the sound to play
|
||||
*/
|
||||
public static void playSound(Audience audience, Sound sound) {
|
||||
audience.playSound(sound);
|
||||
}
|
||||
|
||||
/**
|
||||
* Surrounds text with a MiniMessage font tag.
|
||||
*
|
||||
* @param text the text to surround
|
||||
* @param font the font as a {@link Key}
|
||||
* @return the text surrounded by the MiniMessage font tag
|
||||
*/
|
||||
public static String surroundWithMiniMessageFont(String text, Key font) {
|
||||
return "<font:" + font.asString() + ">" + text + "</font>";
|
||||
}
|
||||
|
||||
/**
|
||||
* Surrounds text with a MiniMessage font tag.
|
||||
*
|
||||
* @param text the text to surround
|
||||
* @param font the font as a {@link String}
|
||||
* @return the text surrounded by the MiniMessage font tag
|
||||
*/
|
||||
public static String surroundWithMiniMessageFont(String text, String font) {
|
||||
return "<font:" + font + ">" + text + "</font>";
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a JSON string to a MiniMessage string.
|
||||
*
|
||||
* @param json the JSON string
|
||||
* @return the MiniMessage string representation
|
||||
*/
|
||||
public static String jsonToMiniMessage(String json) {
|
||||
return getInstance().miniMessageStrict.serialize(getInstance().gsonComponentSerializer.deserialize(json));
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a JSON string to a Component.
|
||||
*
|
||||
* @param json the JSON string
|
||||
* @return the resulting Component
|
||||
*/
|
||||
public static Component jsonToComponent(String json) {
|
||||
return getInstance().gsonComponentSerializer.deserialize(json);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a Component to a JSON string.
|
||||
*
|
||||
* @param component the Component to convert
|
||||
* @return the JSON string representation
|
||||
*/
|
||||
public static String componentToJson(Component component) {
|
||||
return getGson().serialize(component);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a character is a legacy color code.
|
||||
*
|
||||
* @param c the character to check
|
||||
* @return true if the character is a color code, false otherwise
|
||||
*/
|
||||
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||
public static boolean isLegacyColorCode(char c) {
|
||||
return c == '§' || c == '&';
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a legacy color code string to a MiniMessage string.
|
||||
*
|
||||
* @param legacy the legacy color code string
|
||||
* @return the MiniMessage string representation
|
||||
*/
|
||||
public static String legacyToMiniMessage(String legacy) {
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
char[] chars = legacy.toCharArray();
|
||||
for (int i = 0; i < chars.length; i++) {
|
||||
if (!isLegacyColorCode(chars[i])) {
|
||||
stringBuilder.append(chars[i]);
|
||||
continue;
|
||||
}
|
||||
if (i + 1 >= chars.length) {
|
||||
stringBuilder.append(chars[i]);
|
||||
continue;
|
||||
}
|
||||
switch (chars[i+1]) {
|
||||
case '0' -> stringBuilder.append("<black>");
|
||||
case '1' -> stringBuilder.append("<dark_blue>");
|
||||
case '2' -> stringBuilder.append("<dark_green>");
|
||||
case '3' -> stringBuilder.append("<dark_aqua>");
|
||||
case '4' -> stringBuilder.append("<dark_red>");
|
||||
case '5' -> stringBuilder.append("<dark_purple>");
|
||||
case '6' -> stringBuilder.append("<gold>");
|
||||
case '7' -> stringBuilder.append("<gray>");
|
||||
case '8' -> stringBuilder.append("<dark_gray>");
|
||||
case '9' -> stringBuilder.append("<blue>");
|
||||
case 'a' -> stringBuilder.append("<green>");
|
||||
case 'b' -> stringBuilder.append("<aqua>");
|
||||
case 'c' -> stringBuilder.append("<red>");
|
||||
case 'd' -> stringBuilder.append("<light_purple>");
|
||||
case 'e' -> stringBuilder.append("<yellow>");
|
||||
case 'f' -> stringBuilder.append("<white>");
|
||||
case 'r' -> stringBuilder.append("<r><!i>");
|
||||
case 'l' -> stringBuilder.append("<b>");
|
||||
case 'm' -> stringBuilder.append("<st>");
|
||||
case 'o' -> stringBuilder.append("<i>");
|
||||
case 'n' -> stringBuilder.append("<u>");
|
||||
case 'k' -> stringBuilder.append("<obf>");
|
||||
case 'x' -> {
|
||||
if (i + 13 >= chars.length
|
||||
|| !isLegacyColorCode(chars[i+2])
|
||||
|| !isLegacyColorCode(chars[i+4])
|
||||
|| !isLegacyColorCode(chars[i+6])
|
||||
|| !isLegacyColorCode(chars[i+8])
|
||||
|| !isLegacyColorCode(chars[i+10])
|
||||
|| !isLegacyColorCode(chars[i+12])) {
|
||||
stringBuilder.append(chars[i]);
|
||||
continue;
|
||||
}
|
||||
stringBuilder
|
||||
.append("<#")
|
||||
.append(chars[i+3])
|
||||
.append(chars[i+5])
|
||||
.append(chars[i+7])
|
||||
.append(chars[i+9])
|
||||
.append(chars[i+11])
|
||||
.append(chars[i+13])
|
||||
.append(">");
|
||||
i += 12;
|
||||
}
|
||||
default -> {
|
||||
stringBuilder.append(chars[i]);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
i++;
|
||||
}
|
||||
return stringBuilder.toString();
|
||||
}
|
||||
}
|
||||
@@ -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.customcrops.common.helper;
|
||||
|
||||
import net.objecthunter.exp4j.ExpressionBuilder;
|
||||
|
||||
/**
|
||||
* Helper class for evaluating mathematical expressions.
|
||||
*/
|
||||
public class ExpressionHelper {
|
||||
|
||||
/**
|
||||
* Evaluates a mathematical expression provided as a string.
|
||||
*
|
||||
* @param expression the mathematical expression to evaluate
|
||||
* @return the result of the evaluation as a double
|
||||
*/
|
||||
public static double evaluate(String expression) {
|
||||
return new ExpressionBuilder(expression).build().evaluate();
|
||||
}
|
||||
}
|
||||
@@ -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.customcrops.common.helper;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
/**
|
||||
* Helper class for managing Gson instances.
|
||||
*/
|
||||
public class GsonHelper {
|
||||
|
||||
private final Gson gson;
|
||||
|
||||
public GsonHelper() {
|
||||
this.gson = new GsonBuilder()
|
||||
.create();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the Gson instance.
|
||||
*
|
||||
* @return the Gson instance
|
||||
*/
|
||||
public Gson getGson() {
|
||||
return gson;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the singleton Gson instance from GsonHelper.
|
||||
*
|
||||
* @return the singleton Gson instance
|
||||
*/
|
||||
public static Gson get() {
|
||||
return SingletonHolder.INSTANCE.getGson();
|
||||
}
|
||||
|
||||
/**
|
||||
* Static inner class for holding the singleton instance of GsonHelper.
|
||||
*/
|
||||
private static class SingletonHolder {
|
||||
private static final GsonHelper INSTANCE = new GsonHelper();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
/*
|
||||
* 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.customcrops.common.helper;
|
||||
|
||||
import net.momirealms.customcrops.common.plugin.CustomCropsPlugin;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* This class implements the VersionManager interface and is responsible for managing version-related information.
|
||||
*/
|
||||
public class VersionHelper {
|
||||
|
||||
// Method to asynchronously check for plugin updates
|
||||
public static final Function<CustomCropsPlugin, CompletableFuture<Boolean>> UPDATE_CHECKER = (plugin) -> {
|
||||
CompletableFuture<Boolean> updateFuture = new CompletableFuture<>();
|
||||
plugin.getScheduler().async().execute(() -> {
|
||||
try {
|
||||
URL url = new URL("https://api.polymart.org/v1/getResourceInfoSimple/?resource_id=2625&key=version");
|
||||
URLConnection conn = url.openConnection();
|
||||
conn.setConnectTimeout(10000);
|
||||
conn.setReadTimeout(60000);
|
||||
InputStream inputStream = conn.getInputStream();
|
||||
String newest = new BufferedReader(new InputStreamReader(inputStream)).readLine();
|
||||
String current = plugin.getPluginVersion();
|
||||
inputStream.close();
|
||||
if (!compareVer(newest, current)) {
|
||||
updateFuture.complete(false);
|
||||
return;
|
||||
}
|
||||
updateFuture.complete(true);
|
||||
} catch (Exception exception) {
|
||||
plugin.getPluginLogger().warn("Error occurred when checking update.", exception);
|
||||
updateFuture.complete(false);
|
||||
}
|
||||
});
|
||||
return updateFuture;
|
||||
};
|
||||
|
||||
private static float version;
|
||||
private static boolean mojmap;
|
||||
private static boolean folia;
|
||||
|
||||
public static void init(String serverVersion) {
|
||||
String[] split = serverVersion.split("\\.");
|
||||
version = Float.parseFloat(split[1] + "." + (split.length == 3 ? split[2] : "0"));
|
||||
checkMojMap();
|
||||
checkFolia();
|
||||
}
|
||||
|
||||
private static void checkMojMap() {
|
||||
// Check if the server is Mojmap
|
||||
try {
|
||||
Class.forName("net.minecraft.network.protocol.game.ClientboundBossEventPacket");
|
||||
mojmap = true;
|
||||
} catch (ClassNotFoundException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
private static void checkFolia() {
|
||||
try {
|
||||
Class.forName("io.papermc.paper.threadedregions.RegionizedServer");
|
||||
folia = true;
|
||||
} catch (ClassNotFoundException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isVersionNewerThan1_18() {
|
||||
return version >= 18;
|
||||
}
|
||||
|
||||
public static boolean isVersionNewerThan1_19() {
|
||||
return version >= 19;
|
||||
}
|
||||
|
||||
public static boolean isVersionNewerThan1_19_4() {
|
||||
return version >= 19.4;
|
||||
}
|
||||
|
||||
public static boolean isVersionNewerThan1_19_3() {
|
||||
return version >= 19.3;
|
||||
}
|
||||
|
||||
public static boolean isVersionNewerThan1_20() {
|
||||
return version >= 20.0;
|
||||
}
|
||||
|
||||
public static boolean isVersionNewerThan1_20_5() {
|
||||
return version >= 20.5;
|
||||
}
|
||||
|
||||
public static boolean isFolia() {
|
||||
return folia;
|
||||
}
|
||||
|
||||
public static boolean isMojmap() {
|
||||
return mojmap;
|
||||
}
|
||||
|
||||
// Method to compare two version strings
|
||||
private static boolean compareVer(String newV, String currentV) {
|
||||
if (newV == null || currentV == null || newV.isEmpty() || currentV.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
String[] newVS = newV.split("\\.");
|
||||
String[] currentVS = currentV.split("\\.");
|
||||
int maxL = Math.min(newVS.length, currentVS.length);
|
||||
for (int i = 0; i < maxL; i++) {
|
||||
try {
|
||||
String[] newPart = newVS[i].split("-");
|
||||
String[] currentPart = currentVS[i].split("-");
|
||||
int newNum = Integer.parseInt(newPart[0]);
|
||||
int currentNum = Integer.parseInt(currentPart[0]);
|
||||
if (newNum > currentNum) {
|
||||
return true;
|
||||
} else if (newNum < currentNum) {
|
||||
return false;
|
||||
} else if (newPart.length > 1 && currentPart.length > 1) {
|
||||
String[] newHotfix = newPart[1].split("(?<=\\D)(?=\\d)|(?<=\\d)(?=\\D)");
|
||||
String[] currentHotfix = currentPart[1].split("(?<=\\D)(?=\\d)|(?<=\\d)(?=\\D)");
|
||||
if (newHotfix.length == 2 && currentHotfix.length == 1) return true;
|
||||
else if (newHotfix.length > 1 && currentHotfix.length > 1) {
|
||||
int newHotfixNum = Integer.parseInt(newHotfix[1]);
|
||||
int currentHotfixNum = Integer.parseInt(currentHotfix[1]);
|
||||
if (newHotfixNum > currentHotfixNum) {
|
||||
return true;
|
||||
} else if (newHotfixNum < currentHotfixNum) {
|
||||
return false;
|
||||
} else {
|
||||
return newHotfix[0].compareTo(currentHotfix[0]) > 0;
|
||||
}
|
||||
}
|
||||
} else if (newPart.length > 1) {
|
||||
return true;
|
||||
} else if (currentPart.length > 1) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (NumberFormatException ignored) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return newVS.length > currentVS.length;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* 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.customcrops.common.item;
|
||||
|
||||
import net.momirealms.customcrops.common.plugin.CustomCropsPlugin;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public class AbstractItem<R, I> implements Item<I> {
|
||||
|
||||
private final CustomCropsPlugin plugin;
|
||||
private final ItemFactory<?, R, I> factory;
|
||||
private final R item;
|
||||
|
||||
AbstractItem(CustomCropsPlugin plugin, ItemFactory<?, R, I> factory, R item) {
|
||||
this.plugin = plugin;
|
||||
this.factory = factory;
|
||||
this.item = item;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Item<I> damage(Integer data) {
|
||||
factory.damage(item, data);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Integer> damage() {
|
||||
return factory.damage(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Item<I> maxDamage(Integer data) {
|
||||
factory.maxDamage(item, data);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Integer> maxDamage() {
|
||||
return factory.maxDamage(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Item<I> customModelData(Integer data) {
|
||||
factory.customModelData(item, data);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Integer> customModelData() {
|
||||
return factory.customModelData(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Item<I> lore(List<String> lore) {
|
||||
factory.lore(item, lore);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<List<String>> lore() {
|
||||
return factory.lore(item);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean unbreakable() {
|
||||
return factory.unbreakable(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Object> getTag(Object... path) {
|
||||
return factory.getTag(item, path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Item<I> setTag(Object value, Object... path) {
|
||||
factory.setTag(item, value, path);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasTag(Object... path) {
|
||||
return factory.hasTag(item, path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeTag(Object... path) {
|
||||
return factory.removeTag(item, path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public I getItem() {
|
||||
return factory.getItem(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public I load() {
|
||||
return factory.load(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public I loadCopy() {
|
||||
return factory.loadCopy(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update() {
|
||||
factory.update(item);
|
||||
}
|
||||
|
||||
public R getRTagItem() {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
@@ -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.customcrops.common.item;
|
||||
|
||||
import net.kyori.adventure.key.Key;
|
||||
|
||||
public class ComponentKeys {
|
||||
|
||||
public static final String CUSTOM_MODEL_DATA = Key.key("minecraft", "custom_model_data").asString();
|
||||
public static final String CUSTOM_NAME = Key.key("minecraft", "custom_name").asString();
|
||||
public static final String LORE = Key.key("minecraft", "lore").asString();
|
||||
public static final String DAMAGE = Key.key("minecraft", "damage").asString();
|
||||
public static final String MAX_DAMAGE = Key.key("minecraft", "max_damage").asString();
|
||||
public static final String ENCHANTMENT_GLINT_OVERRIDE = Key.key("minecraft", "enchantment_glint_override").asString();
|
||||
public static final String ENCHANTMENTS = Key.key("minecraft", "enchantments").asString();
|
||||
public static final String STORED_ENCHANTMENTS = Key.key("minecraft", "stored_enchantments").asString();
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
* 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.customcrops.common.item;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Interface representing an item.
|
||||
* This interface provides methods for managing item properties such as custom model data,
|
||||
* damage, display name, lore, enchantments, and tags.
|
||||
*
|
||||
* @param <I> the type of the item implementation
|
||||
*/
|
||||
public interface Item<I> {
|
||||
|
||||
/**
|
||||
* Sets the custom model data for the item.
|
||||
*
|
||||
* @param data the custom model data to set
|
||||
* @return the current {@link Item} instance for method chaining
|
||||
*/
|
||||
Item<I> customModelData(Integer data);
|
||||
|
||||
/**
|
||||
* Retrieves the custom model data of the item.
|
||||
*
|
||||
* @return an {@link Optional} containing the custom model data, or empty if not set
|
||||
*/
|
||||
Optional<Integer> customModelData();
|
||||
|
||||
/**
|
||||
* Sets the damage value for the item.
|
||||
*
|
||||
* @param data the damage value to set
|
||||
* @return the current {@link Item} instance for method chaining
|
||||
*/
|
||||
Item<I> damage(Integer data);
|
||||
|
||||
/**
|
||||
* Retrieves the damage value of the item.
|
||||
*
|
||||
* @return an {@link Optional} containing the damage value, or empty if not set
|
||||
*/
|
||||
Optional<Integer> damage();
|
||||
|
||||
/**
|
||||
* Sets the maximum damage value for the item.
|
||||
*
|
||||
* @param data the maximum damage value to set
|
||||
* @return the current {@link Item} instance for method chaining
|
||||
*/
|
||||
Item<I> maxDamage(Integer data);
|
||||
|
||||
/**
|
||||
* Retrieves the maximum damage value of the item.
|
||||
*
|
||||
* @return an {@link Optional} containing the maximum damage value, or empty if not set
|
||||
*/
|
||||
Optional<Integer> maxDamage();
|
||||
|
||||
/**
|
||||
* Sets the lore for the item.
|
||||
*
|
||||
* @param lore the lore to set
|
||||
* @return the current {@link Item} instance for method chaining
|
||||
*/
|
||||
Item<I> lore(List<String> lore);
|
||||
|
||||
/**
|
||||
* Retrieves the lore of the item.
|
||||
*
|
||||
* @return an {@link Optional} containing the lore, or empty if not set
|
||||
*/
|
||||
Optional<List<String>> lore();
|
||||
|
||||
/**
|
||||
* Checks if the item is unbreakable.
|
||||
*
|
||||
* @return true if the item is unbreakable, false otherwise
|
||||
*/
|
||||
boolean unbreakable();
|
||||
|
||||
/**
|
||||
* Retrieves the tag value at the specified path.
|
||||
*
|
||||
* @param path the path to the tag value
|
||||
* @return an {@link Optional} containing the tag value, or empty if not found
|
||||
*/
|
||||
Optional<Object> getTag(Object... path);
|
||||
|
||||
/**
|
||||
* Sets the tag value at the specified path.
|
||||
*
|
||||
* @param value the value to set
|
||||
* @param path the path to the tag value
|
||||
* @return the current {@link Item} instance for method chaining
|
||||
*/
|
||||
Item<I> setTag(Object value, Object... path);
|
||||
|
||||
/**
|
||||
* Checks if the item has a tag value at the specified path.
|
||||
*
|
||||
* @param path the path to the tag value
|
||||
* @return true if the tag value exists, false otherwise
|
||||
*/
|
||||
boolean hasTag(Object... path);
|
||||
|
||||
/**
|
||||
* Removes the tag value at the specified path.
|
||||
*
|
||||
* @param path the path to the tag value
|
||||
* @return true if the tag was removed, false otherwise
|
||||
*/
|
||||
boolean removeTag(Object... path);
|
||||
|
||||
/**
|
||||
* Retrieves the underlying item implementation.
|
||||
*
|
||||
* @return the item implementation of type {@link I}
|
||||
*/
|
||||
I getItem();
|
||||
|
||||
/**
|
||||
* Loads changes to the item.
|
||||
*
|
||||
* @return the loaded item implementation of type {@link I}
|
||||
*/
|
||||
I load();
|
||||
|
||||
/**
|
||||
* Loads the changes and gets a copy of the item.
|
||||
*
|
||||
* @return a copy of the loaded item implementation of type {@link I}
|
||||
*/
|
||||
I loadCopy();
|
||||
|
||||
/**
|
||||
* Loads the {@link I}'s changes to the {@link Item} instance.
|
||||
*/
|
||||
void update();
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* 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.customcrops.common.item;
|
||||
|
||||
import net.momirealms.customcrops.common.plugin.CustomCropsPlugin;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
public abstract class ItemFactory<P extends CustomCropsPlugin, R, I> {
|
||||
|
||||
protected final P plugin;
|
||||
|
||||
protected ItemFactory(P plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
public Item<I> wrap(R item) {
|
||||
Objects.requireNonNull(item, "item");
|
||||
return new AbstractItem<>(this.plugin, this, item);
|
||||
}
|
||||
|
||||
protected abstract Optional<Object> getTag(R item, Object... path);
|
||||
|
||||
protected abstract void setTag(R item, Object value, Object... path);
|
||||
|
||||
protected abstract boolean hasTag(R item, Object... path);
|
||||
|
||||
protected abstract boolean removeTag(R item, Object... path);
|
||||
|
||||
protected abstract void update(R item);
|
||||
|
||||
protected abstract I load(R item);
|
||||
|
||||
protected abstract I getItem(R item);
|
||||
|
||||
protected abstract I loadCopy(R item);
|
||||
|
||||
protected abstract void customModelData(R item, Integer data);
|
||||
|
||||
protected abstract Optional<Integer> customModelData(R item);
|
||||
|
||||
protected abstract Optional<List<String>> lore(R item);
|
||||
|
||||
protected abstract void lore(R item, List<String> lore);
|
||||
|
||||
protected abstract Optional<Integer> damage(R item);
|
||||
|
||||
protected abstract void damage(R item, Integer damage);
|
||||
|
||||
protected abstract Optional<Integer> maxDamage(R item);
|
||||
|
||||
protected abstract void maxDamage(R item, Integer damage);
|
||||
|
||||
protected abstract boolean unbreakable(R item);
|
||||
}
|
||||
@@ -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.customcrops.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 CustomCropsCaptionFormatter<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.customcrops.common.locale;
|
||||
|
||||
import org.incendo.cloud.caption.Caption;
|
||||
|
||||
public final class CustomCropsCaptionKeys {
|
||||
|
||||
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.customcrops.common.locale;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.incendo.cloud.caption.CaptionProvider;
|
||||
import org.incendo.cloud.caption.DelegatingCaptionProvider;
|
||||
|
||||
public final class CustomCropsCaptionProvider<C> extends DelegatingCaptionProvider<C> {
|
||||
|
||||
private static final CaptionProvider<?> PROVIDER = CaptionProvider.constantProvider()
|
||||
.putCaption(CustomCropsCaptionKeys.ARGUMENT_PARSE_FAILURE_URL, "")
|
||||
.putCaption(CustomCropsCaptionKeys.ARGUMENT_PARSE_FAILURE_TIME, "")
|
||||
.putCaption(CustomCropsCaptionKeys.ARGUMENT_PARSE_FAILURE_NAMEDTEXTCOLOR, "")
|
||||
.build();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public @NonNull CaptionProvider<C> delegate() {
|
||||
return (CaptionProvider<C>) PROVIDER;
|
||||
}
|
||||
}
|
||||
@@ -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.customcrops.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 SEASON_SPRING = Component.translatable().key("season.spring");
|
||||
TranslatableComponent.Builder SEASON_SUMMER = Component.translatable().key("season.summer");
|
||||
TranslatableComponent.Builder SEASON_AUTUMN = Component.translatable().key("season.autumn");
|
||||
TranslatableComponent.Builder SEASON_WINTER = Component.translatable().key("season.winter");
|
||||
TranslatableComponent.Builder SEASON_DISABLE = Component.translatable().key("season.disable");
|
||||
|
||||
}
|
||||
@@ -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.customcrops.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.customcrops.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.customcrops.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.customcrops.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("customcrops", "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.customcrops.common.locale;
|
||||
|
||||
import dev.dejvokep.boostedyaml.YamlDocument;
|
||||
import net.kyori.adventure.key.Key;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.translation.Translator;
|
||||
import net.momirealms.customcrops.common.helper.AdventureHelper;
|
||||
import net.momirealms.customcrops.common.plugin.CustomCropsPlugin;
|
||||
import net.momirealms.customcrops.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 {
|
||||
|
||||
public static final Locale DEFAULT_LOCALE = Locale.ENGLISH;
|
||||
private static final List<String> locales = List.of("en", "zh_cn");
|
||||
private static TranslationManager instance;
|
||||
private static Locale FORCE_LOCALE = null;
|
||||
|
||||
private final CustomCropsPlugin plugin;
|
||||
private final Set<Locale> installed = ConcurrentHashMap.newKeySet();
|
||||
private MiniMessageTranslationRegistry registry;
|
||||
private final Path translationsDirectory;
|
||||
|
||||
public TranslationManager(CustomCropsPlugin 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("customcrops", "main"), AdventureHelper.getMiniMessage());
|
||||
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)) {
|
||||
plugin.getPluginLogger().warn(localLocale.toString().toLowerCase(Locale.ENGLISH) + ".yml not exists, using en.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" + "\\" + translationFile.getFileName(), '@');
|
||||
try {
|
||||
document.save(new File(plugin.getDataDirectory().toFile(), "translations" + "\\" + 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,138 @@
|
||||
/*
|
||||
* 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.customcrops.common.plugin;
|
||||
|
||||
import net.momirealms.customcrops.common.config.ConfigLoader;
|
||||
import net.momirealms.customcrops.common.dependency.DependencyManager;
|
||||
import net.momirealms.customcrops.common.locale.TranslationManager;
|
||||
import net.momirealms.customcrops.common.plugin.classpath.ClassPathAppender;
|
||||
import net.momirealms.customcrops.common.plugin.logging.PluginLogger;
|
||||
import net.momirealms.customcrops.common.plugin.scheduler.SchedulerAdapter;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Path;
|
||||
|
||||
/**
|
||||
* Interface representing the main CustomCrops plugin.
|
||||
*/
|
||||
public interface CustomCropsPlugin {
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
@@ -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.customcrops.common.plugin;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
public class CustomCropsProperties {
|
||||
|
||||
private final HashMap<String, String> propertyMap;
|
||||
|
||||
private CustomCropsProperties(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 CustomCropsProperties INSTANCE = getInstance();
|
||||
|
||||
private static CustomCropsProperties getInstance() {
|
||||
try (InputStream inputStream = CustomCropsProperties.class.getClassLoader().getResourceAsStream("custom-crops.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 CustomCropsProperties(versionMap);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.customcrops.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.customcrops.common.plugin.classpath;
|
||||
|
||||
import net.momirealms.customcrops.common.plugin.CustomCropsPlugin;
|
||||
|
||||
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(CustomCropsPlugin 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.customcrops.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("CustomCrops 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,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.customcrops.common.plugin.feature;
|
||||
|
||||
public interface Reloadable {
|
||||
|
||||
default void reload() {
|
||||
unload();
|
||||
load();
|
||||
}
|
||||
|
||||
default void unload() {
|
||||
}
|
||||
|
||||
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.customcrops.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.customcrops.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.customcrops.common.plugin.logging;
|
||||
|
||||
/**
|
||||
* Represents the logger instance being used by CustomCrops on the platform.
|
||||
*
|
||||
* <p>Messages sent using the logger are sent prefixed with the CustomCrops 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.customcrops.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,151 @@
|
||||
/*
|
||||
* 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.customcrops.common.plugin.scheduler;
|
||||
|
||||
import net.momirealms.customcrops.common.plugin.CustomCropsPlugin;
|
||||
|
||||
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, W> implements SchedulerAdapter<T, W> {
|
||||
private static final int PARALLELISM = 16;
|
||||
|
||||
private final CustomCropsPlugin plugin;
|
||||
|
||||
private final ScheduledThreadPoolExecutor scheduler;
|
||||
private final ForkJoinPool worker;
|
||||
|
||||
public AbstractJavaScheduler(CustomCropsPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
|
||||
this.scheduler = new ScheduledThreadPoolExecutor(4, r -> {
|
||||
Thread thread = Executors.defaultThreadFactory().newThread(r);
|
||||
thread.setName("customcrops-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) {
|
||||
ScheduledFuture<?> future = this.scheduler.schedule(() -> this.worker.execute(task), delay, unit);
|
||||
return new JavaCancellable(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 JavaCancellable(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 CustomCrops scheduler to terminate");
|
||||
reportRunningTasks(thread -> thread.getName().equals("customcrops-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 CustomCrops worker thread pool to terminate");
|
||||
reportRunningTasks(thread -> thread.getName().startsWith("customcrops-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("customcrops-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);
|
||||
}
|
||||
}
|
||||
|
||||
public static class JavaCancellable implements SchedulerTask {
|
||||
|
||||
private final ScheduledFuture<?> future;
|
||||
|
||||
public JavaCancellable(ScheduledFuture<?> future) {
|
||||
this.future = future;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
this.future.cancel(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelled() {
|
||||
return future.isCancelled();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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.customcrops.common.plugin.scheduler;
|
||||
|
||||
public interface RegionExecutor<T, W> {
|
||||
|
||||
void run(Runnable r, T l);
|
||||
|
||||
default void run(Runnable r) {
|
||||
run(r, null);
|
||||
}
|
||||
|
||||
void run(Runnable r, W world, int x, int z);
|
||||
|
||||
SchedulerTask runLater(Runnable r, long delayTicks, T l);
|
||||
|
||||
SchedulerTask runRepeating(Runnable r, long delayTicks, long period, T l);
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* 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.customcrops.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, W> {
|
||||
|
||||
/**
|
||||
* Gets an async executor instance
|
||||
*
|
||||
* @return an async executor instance
|
||||
*/
|
||||
Executor async();
|
||||
|
||||
/**
|
||||
* Gets a sync executor instance
|
||||
*
|
||||
* @return a sync executor instance
|
||||
*/
|
||||
RegionExecutor<T, W> 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a task sync
|
||||
*
|
||||
* @param task the task
|
||||
*/
|
||||
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.customcrops.common.plugin.scheduler;
|
||||
|
||||
/**
|
||||
* Represents a scheduled task
|
||||
*/
|
||||
public interface SchedulerTask {
|
||||
|
||||
/**
|
||||
* Cancels the task.
|
||||
*/
|
||||
void cancel();
|
||||
|
||||
boolean isCancelled();
|
||||
}
|
||||
@@ -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.customcrops.common.sender;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.momirealms.customcrops.common.plugin.CustomCropsPlugin;
|
||||
import net.momirealms.customcrops.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 CustomCropsPlugin plugin;
|
||||
private final SenderFactory<?, T> factory;
|
||||
private final T sender;
|
||||
|
||||
private final UUID uniqueId;
|
||||
private final String name;
|
||||
private final boolean isConsole;
|
||||
|
||||
AbstractSender(CustomCropsPlugin 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 CustomCropsPlugin 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,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.customcrops.common.sender;
|
||||
|
||||
import net.momirealms.customcrops.common.plugin.CustomCropsPlugin;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public abstract class DummyConsoleSender implements Sender {
|
||||
private final CustomCropsPlugin platform;
|
||||
|
||||
public DummyConsoleSender(CustomCropsPlugin plugin) {
|
||||
this.platform = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void performCommand(String commandLine) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConsole() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CustomCropsPlugin 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.customcrops.common.sender;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.momirealms.customcrops.common.plugin.CustomCropsPlugin;
|
||||
import net.momirealms.customcrops.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
|
||||
*/
|
||||
CustomCropsPlugin 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.customcrops.common.sender;
|
||||
|
||||
import net.kyori.adventure.audience.Audience;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.momirealms.customcrops.common.plugin.CustomCropsPlugin;
|
||||
import net.momirealms.customcrops.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 CustomCropsPlugin, 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() {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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.customcrops.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,88 @@
|
||||
/*
|
||||
* 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.customcrops.common.util;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
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() {}
|
||||
|
||||
@Nullable
|
||||
public static <T, C> Class<? extends T> findClass(
|
||||
@NotNull File file,
|
||||
@NotNull Class<T> clazz,
|
||||
@NotNull Class<C> type
|
||||
) throws IOException, ClassNotFoundException {
|
||||
if (!file.exists()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
URL jarUrl = file.toURI().toURL();
|
||||
List<Class<? extends T>> classes = new ArrayList<>();
|
||||
|
||||
try (URLClassLoader loader = new URLClassLoader(new URL[]{jarUrl}, clazz.getClassLoader());
|
||||
JarInputStream jarStream = new JarInputStream(jarUrl.openStream())) {
|
||||
|
||||
JarEntry entry;
|
||||
while ((entry = jarStream.getNextJarEntry()) != null) {
|
||||
String name = entry.getName();
|
||||
if (!name.endsWith(".class")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String className = name.substring(0, name.lastIndexOf('.')).replace('/', '.');
|
||||
|
||||
try {
|
||||
Class<?> loadedClass = loader.loadClass(className);
|
||||
if (clazz.isAssignableFrom(loadedClass)) {
|
||||
Type superclassType = loadedClass.getGenericSuperclass();
|
||||
if (superclassType instanceof ParameterizedType parameterizedType) {
|
||||
Type[] typeArguments = parameterizedType.getActualTypeArguments();
|
||||
if (typeArguments.length > 0 && typeArguments[0].equals(type)) {
|
||||
classes.add(loadedClass.asSubclass(clazz));
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (ClassNotFoundException | NoClassDefFoundError ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (classes.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return classes.get(0);
|
||||
}
|
||||
}
|
||||
@@ -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.customcrops.common.util;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Collector;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Utility class for handling operations with {@link CompletableFuture}.
|
||||
*/
|
||||
public class CompletableFutures {
|
||||
|
||||
private CompletableFutures() {}
|
||||
|
||||
/**
|
||||
* A collector for collecting a stream of CompletableFuture instances into a single CompletableFuture that completes
|
||||
* when all of the input CompletableFutures complete.
|
||||
*
|
||||
* @param <T> The type of CompletableFuture.
|
||||
* @return A collector for CompletableFuture instances.
|
||||
*/
|
||||
public static <T extends CompletableFuture<?>> Collector<T, ImmutableList.Builder<T>, CompletableFuture<Void>> collector() {
|
||||
return Collector.of(
|
||||
ImmutableList.Builder::new,
|
||||
ImmutableList.Builder::add,
|
||||
(l, r) -> l.addAll(r.build()),
|
||||
builder -> allOf(builder.build())
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Combines multiple CompletableFuture instances into a single CompletableFuture that completes when all of the input
|
||||
* CompletableFutures complete.
|
||||
*
|
||||
* @param futures A stream of CompletableFuture instances.
|
||||
* @return A CompletableFuture that completes when all input CompletableFutures complete.
|
||||
*/
|
||||
public static CompletableFuture<Void> allOf(Stream<? extends CompletableFuture<?>> futures) {
|
||||
CompletableFuture<?>[] arr = futures.toArray(CompletableFuture[]::new);
|
||||
return CompletableFuture.allOf(arr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Combines multiple CompletableFuture instances into a single CompletableFuture that completes when all of the input
|
||||
* CompletableFutures complete.
|
||||
*
|
||||
* @param futures A collection of CompletableFuture instances.
|
||||
* @return A CompletableFuture that completes when all input CompletableFutures complete.
|
||||
*/
|
||||
public static CompletableFuture<Void> allOf(Collection<? extends CompletableFuture<?>> futures) {
|
||||
CompletableFuture<?>[] arr = futures.toArray(new CompletableFuture[0]);
|
||||
return CompletableFuture.allOf(arr);
|
||||
}
|
||||
}
|
||||
@@ -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.customcrops.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.customcrops.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());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* 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.customcrops.common.util;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.DirectoryStream;
|
||||
import java.nio.file.FileAlreadyExistsException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
/**
|
||||
* Utility class for handling file and directory operations.
|
||||
*/
|
||||
public class FileUtils {
|
||||
|
||||
private FileUtils() {}
|
||||
|
||||
/**
|
||||
* Creates a file if it does not already exist.
|
||||
*
|
||||
* @param path the path to the file
|
||||
* @return the path to the file
|
||||
* @throws IOException if an I/O error occurs
|
||||
*/
|
||||
public static Path createFileIfNotExists(Path path) throws IOException {
|
||||
if (!Files.exists(path)) {
|
||||
Files.createFile(path);
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a directory if it does not already exist.
|
||||
*
|
||||
* @param path the path to the directory
|
||||
* @return the path to the directory
|
||||
* @throws IOException if an I/O error occurs
|
||||
*/
|
||||
public static Path createDirectoryIfNotExists(Path path) throws IOException {
|
||||
if (Files.exists(path) && (Files.isDirectory(path) || Files.isSymbolicLink(path))) {
|
||||
return path;
|
||||
}
|
||||
|
||||
try {
|
||||
Files.createDirectory(path);
|
||||
} catch (FileAlreadyExistsException e) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates directories if they do not already exist.
|
||||
*
|
||||
* @param path the path to the directories
|
||||
* @return the path to the directories
|
||||
* @throws IOException if an I/O error occurs
|
||||
*/
|
||||
public static Path createDirectoriesIfNotExists(Path path) throws IOException {
|
||||
if (Files.exists(path) && (Files.isDirectory(path) || Files.isSymbolicLink(path))) {
|
||||
return path;
|
||||
}
|
||||
|
||||
try {
|
||||
Files.createDirectories(path);
|
||||
} catch (FileAlreadyExistsException e) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a directory and all its contents.
|
||||
*
|
||||
* @param path the path to the directory
|
||||
* @throws IOException if an I/O error occurs
|
||||
*/
|
||||
public static void deleteDirectory(Path path) throws IOException {
|
||||
if (!Files.exists(path) || !Files.isDirectory(path)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try (DirectoryStream<Path> contents = Files.newDirectoryStream(path)) {
|
||||
for (Path file : contents) {
|
||||
if (Files.isDirectory(file)) {
|
||||
deleteDirectory(file);
|
||||
} else {
|
||||
Files.delete(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Files.delete(path);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package net.momirealms.customcrops.common.util;
|
||||
|
||||
public class Key {
|
||||
|
||||
private final String namespace;
|
||||
private final String value;
|
||||
|
||||
public Key(String namespace, String value) {
|
||||
this.namespace = namespace;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public static Key key(String namespace, String value) {
|
||||
return new Key(namespace, value);
|
||||
}
|
||||
|
||||
public static Key key(String key) {
|
||||
int index = key.indexOf(":");
|
||||
String namespace = index >= 1 ? key.substring(0, index) : "minecraft";
|
||||
String value = index >= 0 ? key.substring(index + 1) : key;
|
||||
return key(namespace, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
Key key = (Key) o;
|
||||
return namespace.equals(key.namespace) && value.equals(key.value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return asString().hashCode();
|
||||
}
|
||||
|
||||
public String asString() {
|
||||
return namespace + ":" + value;
|
||||
}
|
||||
|
||||
public String namespace() {
|
||||
return namespace;
|
||||
}
|
||||
|
||||
public String value() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Key{" +
|
||||
"namespace='" + namespace + '\'' +
|
||||
", value='" + value + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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.customcrops.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;
|
||||
} else {
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* 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.customcrops.common.util;
|
||||
|
||||
/**
|
||||
* 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 class Pair<L, R> {
|
||||
private L left;
|
||||
private R right;
|
||||
|
||||
/**
|
||||
* Constructs a new {@link Pair} with the specified left and right values.
|
||||
*
|
||||
* @param left the left value
|
||||
* @param right the right value
|
||||
*/
|
||||
public Pair(L left, R right) {
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the left value.
|
||||
*
|
||||
* @return the left value
|
||||
*/
|
||||
public L left() {
|
||||
return left;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the left value.
|
||||
*
|
||||
* @param left the new left value
|
||||
*/
|
||||
public void left(L left) {
|
||||
this.left = left;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the right value.
|
||||
*
|
||||
* @return the right value
|
||||
*/
|
||||
public R right() {
|
||||
return right;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the right value.
|
||||
*
|
||||
* @param right the new right value
|
||||
*/
|
||||
public void right(R right) {
|
||||
this.right = 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);
|
||||
}
|
||||
}
|
||||
@@ -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.customcrops.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,22 @@
|
||||
/*
|
||||
* 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.customcrops.common.util;
|
||||
|
||||
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.customcrops.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.customcrops.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,114 @@
|
||||
/*
|
||||
* 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.customcrops.common.util;
|
||||
|
||||
/**
|
||||
* 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 class Tuple<L, M, R> {
|
||||
private L left;
|
||||
private M mid;
|
||||
private R right;
|
||||
|
||||
/**
|
||||
* Constructs 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
|
||||
*/
|
||||
public Tuple(L left, M mid, R right) {
|
||||
this.left = left;
|
||||
this.mid = mid;
|
||||
this.right = right;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the left value.
|
||||
*
|
||||
* @return the left value
|
||||
*/
|
||||
public L left() {
|
||||
return left;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the left value.
|
||||
*
|
||||
* @param left the new left value
|
||||
*/
|
||||
public void left(L left) {
|
||||
this.left = left;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the middle value.
|
||||
*
|
||||
* @return the middle value
|
||||
*/
|
||||
public M mid() {
|
||||
return mid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the middle value.
|
||||
*
|
||||
* @param mid the new middle value
|
||||
*/
|
||||
public void mid(M mid) {
|
||||
this.mid = mid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the right value.
|
||||
*
|
||||
* @return the right value
|
||||
*/
|
||||
public R right() {
|
||||
return right;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the right value.
|
||||
*
|
||||
* @param right the new right value
|
||||
*/
|
||||
public void right(R right) {
|
||||
this.right = 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);
|
||||
}
|
||||
}
|
||||
@@ -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.customcrops.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.customcrops.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;
|
||||
}
|
||||
}
|
||||
20
common/src/main/resources/custom-crops.properties
Normal file
20
common/src/main/resources/custom-crops.properties
Normal file
@@ -0,0 +1,20 @@
|
||||
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}
|
||||
zstd-jni=${zstd_version}
|
||||
Reference in New Issue
Block a user