1
mirror of https://github.com/CarmJos/MineConfiguration.git synced 2026-06-04 13:55:03 +08:00

feat!(conf): Upgraded to latest EasyConfiguration with optimized.

This commit is contained in:
2025-02-17 18:45:42 +08:00
parent 87799d2c70
commit bba135e911
80 changed files with 1185 additions and 3465 deletions
+22 -1
View File
@@ -5,7 +5,7 @@
<parent>
<artifactId>mineconfiguration-parent</artifactId>
<groupId>cc.carm.lib</groupId>
<version>2.9.3</version>
<version>3.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<properties>
@@ -28,6 +28,27 @@
<scope>compile</scope>
</dependency>
<dependency>
<groupId>cc.carm.lib</groupId>
<artifactId>easyconfiguration-feature-commentable</artifactId>
<version>${deps.easyconfifuration.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>cc.carm.lib</groupId>
<artifactId>easyconfiguration-feature-file</artifactId>
<version>${deps.easyconfifuration.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>cc.carm.lib</groupId>
<artifactId>easyconfiguration-feature-text</artifactId>
<version>${deps.easyconfifuration.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>cc.carm.lib</groupId>
<artifactId>easyplugin-color</artifactId>
@@ -1,51 +1,51 @@
package cc.carm.lib.mineconfiguration.common;
import cc.carm.lib.configuration.core.Configuration;
import cc.carm.lib.configuration.core.source.ConfigurationProvider;
import cc.carm.lib.configuration.Configuration;
import cc.carm.lib.configuration.source.ConfigurationHolder;
import org.jetbrains.annotations.NotNull;
public abstract class AbstractConfiguration<P extends ConfigurationProvider<?>> {
public abstract class AbstractConfiguration<HOLDER extends ConfigurationHolder<?>> {
private final P configProvider;
private final P messageProvider;
private final HOLDER config;
private final HOLDER message;
protected AbstractConfiguration(@NotNull P configProvider, @NotNull P messageProvider) {
this.configProvider = configProvider;
this.messageProvider = messageProvider;
protected AbstractConfiguration(@NotNull HOLDER config, @NotNull HOLDER message) {
this.config = config;
this.message = message;
}
public void initializeConfig(@NotNull Configuration configRoot) {
this.configProvider.initialize(configRoot);
this.config.initialize(configRoot);
}
public void initializeMessage(@NotNull Configuration messageRoot) {
this.messageProvider.initialize(messageRoot);
this.message.initialize(messageRoot);
}
public void initializeConfig(@NotNull Class<? extends Configuration> configRoot) {
this.configProvider.initialize(configRoot);
this.config.initialize(configRoot);
}
public void initializeMessage(@NotNull Class<? extends Configuration> messageRoot) {
this.messageProvider.initialize(messageRoot);
this.message.initialize(messageRoot);
}
public P getConfigProvider() {
return configProvider;
public HOLDER getConfig() {
return config;
}
public P getMessageProvider() {
return messageProvider;
public HOLDER getMessage() {
return message;
}
public void reload() throws Exception {
getConfigProvider().reload();
getMessageProvider().reload();
getConfig().reload();
getMessage().reload();
}
public void save() throws Exception {
getConfigProvider().save();
getMessageProvider().save();
getConfig().save();
getMessage().save();
}
}
@@ -1,39 +0,0 @@
package cc.carm.lib.mineconfiguration.common.builder.message;
import cc.carm.lib.mineconfiguration.common.data.AbstractText;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.function.BiFunction;
public abstract class MessageConfigBuilder<R, T extends AbstractText<R>> {
protected final @NotNull Class<R> receiverClazz;
protected final @NotNull Class<T> textClazz;
public MessageConfigBuilder(@NotNull Class<R> receiverClazz,
@NotNull Class<T> textClazz) {
this.receiverClazz = receiverClazz;
this.textClazz = textClazz;
}
/**
* 以单条消息为目标,构建一个消息配置。
*
* @param parser 消息解析器,负责将String转换为目标消息类型。
* @param <M> 消息类型
* @return 单条消息构建器
*/
public abstract <M> @NotNull MessageValueBuilder<M, R, T, ?> asValue(@NotNull BiFunction<@Nullable R, @NotNull String, @Nullable M> parser);
/**
* 以多行消息为目标,构建一个消息配置。
*
* @param parser 消息解析器
* @param <M> 消息类型
* @return 多行消息构建器
*/
public abstract <M> @NotNull MessageListBuilder<M, R, T, ?> asList(@NotNull BiFunction<@Nullable R, @NotNull String, @Nullable M> parser);
}
@@ -1,79 +0,0 @@
package cc.carm.lib.mineconfiguration.common.builder.message;
import cc.carm.lib.configuration.core.builder.CommonConfigBuilder;
import cc.carm.lib.configuration.core.value.ValueManifest;
import cc.carm.lib.mineconfiguration.common.data.AbstractText;
import cc.carm.lib.mineconfiguration.common.utils.ParamsUtils;
import cc.carm.lib.mineconfiguration.common.value.ConfigMessageList;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Collectors;
public abstract class MessageListBuilder<M, R, T extends AbstractText<R>, B extends MessageListBuilder<M, R, T, B>>
extends CommonConfigBuilder<List<T>, B> {
protected final @NotNull Class<R> receiverClazz;
protected @NotNull String[] params;
protected @NotNull BiFunction<@Nullable R, @NotNull String, @Nullable M> messageParser;
protected @NotNull BiConsumer<@NotNull R, @NotNull List<M>> sendFunction;
protected @NotNull Function<@NotNull String, @NotNull String> paramFormatter;
protected final @NotNull Function<String, T> textBuilder;
public MessageListBuilder(@NotNull Class<R> receiverClazz,
@NotNull Function<String, T> textBuilder,
@NotNull BiFunction<@Nullable R, @NotNull String, @Nullable M> parser) {
this.receiverClazz = receiverClazz;
this.textBuilder = textBuilder;
this.params = new String[0];
this.messageParser = parser;
this.paramFormatter = ParamsUtils.DEFAULT_PARAM_FORMATTER;
this.sendFunction = (sender, M) -> {
};
}
public B defaults(@NotNull String... messages) {
return defaults(new ArrayList<>(Arrays.stream(messages).map(textBuilder).collect(Collectors.toList())));
}
public B params(@NotNull String... params) {
this.params = params;
return getThis();
}
public B params(@NotNull List<String> params) {
this.params = params.toArray(new String[0]);
return getThis();
}
public B formatParam(@NotNull Function<@NotNull String, @NotNull String> paramFormatter) {
this.paramFormatter = paramFormatter;
return getThis();
}
public B whenSend(@NotNull BiConsumer<@NotNull R, @NotNull List<M>> sendFunction) {
this.sendFunction = sendFunction;
return getThis();
}
@Override
public abstract @NotNull ConfigMessageList<M, T, R> build();
protected @NotNull ValueManifest<List<T>> buildManifest(@NotNull List<T> emptyValue) {
return new ValueManifest<>(
this.provider, this.path, this.headerComments, this.inlineComment,
Optional.ofNullable(this.defaultValue).orElse(emptyValue)
);
}
}
@@ -1,75 +0,0 @@
package cc.carm.lib.mineconfiguration.common.builder.message;
import cc.carm.lib.configuration.core.builder.CommonConfigBuilder;
import cc.carm.lib.configuration.core.value.ValueManifest;
import cc.carm.lib.mineconfiguration.common.data.AbstractText;
import cc.carm.lib.mineconfiguration.common.utils.ParamsUtils;
import cc.carm.lib.mineconfiguration.common.value.ConfigMessage;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Function;
public abstract class MessageValueBuilder<M, R, T extends AbstractText<R>, B extends MessageValueBuilder<M, R, T, B>>
extends CommonConfigBuilder<T, B> {
protected final @NotNull Class<R> receiverClazz;
protected @NotNull String[] params;
protected @NotNull BiFunction<@Nullable R, @NotNull String, @Nullable M> messageParser;
protected @NotNull BiConsumer<@NotNull R, @NotNull M> sendHandler;
protected @NotNull Function<@NotNull String, @NotNull String> paramFormatter;
protected final @NotNull Function<String, T> textBuilder;
public MessageValueBuilder(@NotNull Class<R> receiverClazz,
@NotNull Function<String, T> textBuilder,
@NotNull BiFunction<@Nullable R, @NotNull String, @Nullable M> parser) {
this.receiverClazz = receiverClazz;
this.params = new String[0];
this.paramFormatter = ParamsUtils.DEFAULT_PARAM_FORMATTER;
this.textBuilder = textBuilder;
this.messageParser = parser;
this.sendHandler = (receiver, M) -> {
};
}
public B defaults(@NotNull String message) {
return defaults(this.textBuilder.apply(message));
}
public B params(@NotNull String... params) {
this.params = params;
return getThis();
}
public B params(@NotNull List<String> params) {
this.params = params.toArray(new String[0]);
return getThis();
}
public B formatParam(@NotNull Function<@NotNull String, @NotNull String> paramFormatter) {
this.paramFormatter = paramFormatter;
return getThis();
}
public B whenSend(@NotNull BiConsumer<@NotNull R, @NotNull M> sendFunction) {
this.sendHandler = sendFunction;
return getThis();
}
@Override
public abstract @NotNull ConfigMessage<M, T, R> build();
protected @NotNull ValueManifest<T> buildManifest(@NotNull T emptyValue) {
return new ValueManifest<>(
this.provider, this.path, this.headerComments, this.inlineComment,
Optional.ofNullable(this.defaultValue).orElse(emptyValue)
);
}
}
@@ -1,42 +0,0 @@
package cc.carm.lib.mineconfiguration.common.data;
import cc.carm.lib.mineconfiguration.common.utils.ParamsUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Map;
import java.util.function.BiFunction;
/**
* @param <R> Receiver type
*/
public abstract class AbstractText<R> {
private final @NotNull Class<R> receiverClazz;
protected @NotNull String message;
public AbstractText(@NotNull Class<R> receiverClazz, @NotNull String message) {
this.receiverClazz = receiverClazz;
this.message = message;
}
public @NotNull Class<R> getReceiverClazz() {
return receiverClazz;
}
public @NotNull String getMessage() {
return this.message;
}
public <M> @NotNull M parse(@NotNull BiFunction<@Nullable R, @NotNull String, @NotNull M> parser,
@Nullable R receiver, @Nullable String[] params, @Nullable Object[] values) {
return parse(parser, receiver, ParamsUtils.buildParams(params, values));
}
public <M> @NotNull M parse(@NotNull BiFunction<@Nullable R, @NotNull String, @NotNull M> parser,
@Nullable R receiver, @NotNull Map<String, Object> placeholders) {
return parser.apply(receiver, ParamsUtils.setPlaceholders(message, placeholders));
}
}
@@ -1,43 +0,0 @@
package cc.carm.lib.mineconfiguration.common.utils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
public class ParamsUtils {
/**
* 默认的变量格式为 {@code %(变量名)}。
*/
public static Function<@NotNull String, @NotNull String> DEFAULT_PARAM_FORMATTER = (s) -> "%(" + s + ")";
public static String[] formatParams(@NotNull Function<String, String> formatter, @NotNull String[] params) {
return Arrays.stream(params).map(formatter).toArray(String[]::new);
}
public static Map<String, Object> buildParams(@Nullable String[] params, @Nullable Object[] values) {
Map<String, Object> map = new HashMap<>();
if (params == null || params.length == 0) return map;
for (int i = 0; i < params.length; i++) {
map.put(params[i], values.length > i ? values[i] : "?");
}
return map;
}
public static String setPlaceholders(@NotNull String messages, @NotNull Map<String, Object> placeholders) {
if (messages.isEmpty()) return messages;
String parsed = messages;
for (Map.Entry<String, Object> entry : placeholders.entrySet()) {
Object value = entry.getValue();
parsed = parsed.replace(entry.getKey(), value == null ? "" : value.toString());
}
return parsed;
}
}
@@ -1,157 +0,0 @@
package cc.carm.lib.mineconfiguration.common.value;
import cc.carm.lib.mineconfiguration.common.builder.message.MessageValueBuilder;
import cc.carm.lib.mineconfiguration.common.utils.ParamsUtils;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.Unmodifiable;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Predicate;
public interface BaseMessage<R, M> {
/**
* 得到所有的接收者
*
* @return 全部可能的接收者
*/
@Unmodifiable
@NotNull Iterable<R> getAllReceivers();
/**
* 得到消息中的通过 {@link MessageValueBuilder#params(String...)}已定变量名(按定义顺序)
*
* @return 已定变量
*/
@NotNull String[] getParams();
/**
* 向接收者发送消息的根方法。
*
* @param receiver 接收者
* @param message 消息内容
*/
@ApiStatus.OverrideOnly
void apply(@NotNull R receiver, @NotNull M message);
/**
* 填入变量值,返回一个准备好待发送的消息。
*
* @param values 变量值
* @return 准备好待发送的消息
*/
default @NotNull PreparedMessage<R, M> prepare(@NotNull Object... values) {
return new PreparedMessage<>(this, values);
}
/**
* 为某位接收者解析此消息。
*
* @param receiver 接收者
* @param placeholders 消息中的变量与对应参数
* @return 解析变量后的消息内容
*/
@Nullable M parse(@Nullable R receiver, @NotNull Map<String, Object> placeholders);
/**
* 为某位接收者解析此消息。
*
* @param receiver 接收者
* @param values 已定变量的对应参数
* @return 解析变量后的消息内容
*/
default @Nullable M parse(@Nullable R receiver, @Nullable Object... values) {
return parse(receiver, ParamsUtils.buildParams(getParams(), values));
}
/**
* 向某位接收者发送消息
*
* @param receiver 消息的接收者
* @param values 已定变量的对应参数
*/
default void send(@Nullable R receiver, @Nullable Object... values) {
send(receiver, ParamsUtils.buildParams(getParams(), values));
}
/**
* 向某位接收者发送消息
*
* @param receiver 消息的接收者
* @param placeholders 消息中的变量与对应参数
*/
default void send(@Nullable R receiver, @NotNull Map<String, Object> placeholders) {
if (receiver == null) return;
M parsed = parse(receiver, placeholders);
if (parsed == null) return;
apply(receiver, parsed);
}
/**
* 向全部接收者(包括后台)发送不同参数的消息
*
* @param eachValues 每位接收者将收到已定变量的对应参数(按定义顺序)
*/
default void sendToEach(@NotNull Function<@NotNull R, Object[]> eachValues) {
sendToEach(null, eachValues);
}
/**
* 向特定接收者发送不同参数的消息
*
* @param limiter 接收者限定器,为空则不限定接收者。
* @param eachValues 每位接收者将收到已定变量的对应参数(按定义顺序)
*/
default void sendToEach(@Nullable Predicate<R> limiter,
@NotNull Function<@NotNull R, Object[]> eachValues) {
Predicate<R> predicate = Optional.ofNullable(limiter).orElse(r -> true);
for (R r : getAllReceivers()) {
if (predicate.test(r)) {
send(r, ParamsUtils.buildParams(getParams(), eachValues.apply(r)));
}
}
}
/**
* 广播此消息(包括后台)
*
* @param values 已定变量的对应参数(按定义顺序)
*/
default void sendToAll(@Nullable Object... values) {
broadcast(values);
}
/**
* 广播此消息(包括后台)
*
* @param placeholders 消息中的变量与对应参数
*/
default void sendToAll(@NotNull Map<String, Object> placeholders) {
broadcast(placeholders);
}
/**
* 广播此消息(包括后台)
*
* @param values 已定变量的对应参数(按定义顺序)
*/
default void broadcast(@Nullable Object... values) {
broadcast(ParamsUtils.buildParams(getParams(), values));
}
/**
* 广播此消息(包括后台)
*
* @param placeholders 消息中的变量与对应参数
*/
default void broadcast(@NotNull Map<String, Object> placeholders) {
getAllReceivers().forEach(r -> send(r, placeholders));
}
}
@@ -1,99 +0,0 @@
package cc.carm.lib.mineconfiguration.common.value;
import cc.carm.lib.configuration.core.function.ConfigValueParser;
import cc.carm.lib.configuration.core.value.ValueManifest;
import cc.carm.lib.configuration.core.value.type.ConfiguredValue;
import cc.carm.lib.mineconfiguration.common.data.AbstractText;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Function;
public abstract class ConfigMessage<M, T extends AbstractText<R>, R>
extends ConfiguredValue<T> implements BaseMessage<R, M> {
protected final @NotNull String[] params;
protected final @NotNull BiFunction<@Nullable R, @NotNull String, @Nullable M> messageParser;
protected final @NotNull BiConsumer<@NotNull R, @NotNull M> sendFunction;
protected final @NotNull Function<String, T> textBuilder;
public ConfigMessage(@NotNull ValueManifest<T> manifest,
@NotNull Class<T> textClazz, @NotNull String[] params,
@NotNull BiFunction<@Nullable R, @NotNull String, @Nullable M> messageParser,
@NotNull BiConsumer<@NotNull R, @NotNull M> sendFunction,
@NotNull Function<String, T> textBuilder) {
super(
manifest, textClazz,
ConfigValueParser.castToString().andThen((s, d) -> textBuilder.apply(s)),
AbstractText::getMessage
);
this.params = params;
this.messageParser = messageParser;
this.sendFunction = sendFunction;
this.textBuilder = textBuilder;
}
@Override
public String[] getParams() {
return params;
}
@Override
public void apply(@NotNull R receiver, @NotNull M message) {
sendFunction.accept(receiver, message);
}
protected <N> @Nullable N parseTo(@Nullable R sender, @NotNull Map<String, Object> placeholders,
@NotNull BiFunction<@Nullable R, @NotNull String, @Nullable N> parser) {
T value = get();
if (value == null || value.getMessage().isEmpty()) return null;
else return value.parse(parser, sender, placeholders);
}
public @Nullable String parseString(@Nullable R sender, @NotNull Map<String, Object> placeholders) {
return parseTo(sender, placeholders, (r, s) -> s);
}
@Override
public @Nullable M parse(@Nullable R sender, @NotNull Map<String, Object> placeholders) {
return parseTo(sender, placeholders, this.messageParser);
}
public void set(@Nullable String value) {
this.set(value == null ? null : buildText(value));
}
protected T buildText(String value) {
return textBuilder.apply(value);
}
public abstract class PreparedMessage<P, N> {
protected final @NotNull Object[] values;
protected PreparedMessage(@NotNull Object[] values) {
this.values = values;
}
public Object[] getValues() {
return values;
}
public abstract void to(P receiver);
public void to(Collection<P> receivers) {
receivers.forEach(this::to);
}
public N get(P receiver) {
return null;
}
}
}
@@ -1,109 +0,0 @@
package cc.carm.lib.mineconfiguration.common.value;
import cc.carm.lib.configuration.core.function.ConfigDataFunction;
import cc.carm.lib.configuration.core.value.ValueManifest;
import cc.carm.lib.configuration.core.value.type.ConfiguredList;
import cc.carm.lib.mineconfiguration.common.data.AbstractText;
import cc.carm.lib.mineconfiguration.common.utils.ParamsUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Collectors;
public abstract class ConfigMessageList<M, T extends AbstractText<R>, R>
extends ConfiguredList<T> implements BaseMessage<R, List<M>> {
protected final @NotNull String[] params;
protected final @NotNull BiFunction<@Nullable R, @NotNull String, @Nullable M> messageParser;
protected final @NotNull BiConsumer<@NotNull R, @NotNull List<M>> sendFunction;
protected final @NotNull Function<String, T> textBuilder;
public ConfigMessageList(@NotNull ValueManifest<List<T>> manifest,
@NotNull Class<T> textClazz, @NotNull String[] params,
@NotNull BiFunction<@Nullable R, @NotNull String, @Nullable M> messageParser,
@NotNull BiConsumer<@NotNull R, @NotNull List<M>> sendFunction,
@NotNull Function<String, @NotNull T> textBuilder) {
super(
manifest, textClazz,
ConfigDataFunction.castToString().andThen(textBuilder::apply),
AbstractText::getMessage
);
this.params = params;
this.messageParser = messageParser;
this.sendFunction = sendFunction;
this.textBuilder = textBuilder;
}
@Override
public String[] getParams() {
return params;
}
@Override
public void apply(@NotNull R receiver, @NotNull List<M> message) {
sendFunction.accept(receiver, message);
}
/**
* 为某位接收者解析消息
*
* @param receiver 消息的接收者
* @param placeholders 消息中的变量与对应参数
*/
@Override
public @Nullable List<M> parse(@Nullable R receiver, @NotNull Map<String, Object> placeholders) {
List<T> list = get();
if (list.isEmpty()) return null;
List<String> messages = list.stream().map(T::getMessage).collect(Collectors.toList());
if (String.join("", messages).isEmpty()) return null;
return list.stream().map(value -> value.parse(this.messageParser, receiver, placeholders))
.collect(Collectors.toList());
}
public @Nullable M parseToLine(@Nullable R receiver, @NotNull Object... values) {
return parseToLine(receiver, "\n", ParamsUtils.buildParams(this.params, values));
}
public @Nullable M parseToLine(@Nullable R receiver, @NotNull Map<String, Object> placeholders) {
return parseToLine(receiver, "\n", placeholders);
}
public @Nullable M parseToLine(@Nullable R receiver, @NotNull String delimiter, @NotNull Map<String, Object> placeholders) {
List<T> list = get();
if (list.isEmpty()) return null;
List<String> messages = list.stream().map(T::getMessage).collect(Collectors.toList());
if (String.join("", messages).isEmpty()) return null;
String combined = String.join(delimiter, messages);
T text = textBuilder.apply(combined);
return text.parse(this.messageParser, receiver, placeholders);
}
public void setMessages(@NotNull String... values) {
setMessages(values.length == 0 ? null : Arrays.asList(values));
}
public void setMessages(@Nullable List<String> values) {
if (values == null) {
set(null);
} else {
set(buildText(values));
}
}
protected List<T> buildText(List<String> values) {
return values.stream().map(textBuilder).collect(Collectors.toList());
}
}
@@ -1,48 +0,0 @@
package cc.carm.lib.mineconfiguration.common.value;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class PreparedMessage<R, M> {
protected final @NotNull BaseMessage<R, M> message;
protected final @NotNull Object[] values;
protected PreparedMessage(@NotNull BaseMessage<R, M> message, @NotNull Object[] values) {
this.message = message;
this.values = values;
}
/**
* 为某位接收者解析此消息。
*
* @param receiver 接收者
* @return 解析变量后的消息内容
*/
public @Nullable M parse(@Nullable R receiver) {
return message.parse(receiver, values);
}
/**
* 向某位接收者发送消息
*
* @param receiver 消息的接收者
*/
public void to(@Nullable R receiver) {
message.send(receiver, values);
}
/**
* 向某位接收者发送消息
*
* @param receivers 消息的接收者们
*/
public void to(@NotNull Iterable<? extends R> receivers) {
receivers.forEach(this::to);
}
public void toAll() {
to(message.getAllReceivers());
}
}