Archived
0

refactoring: swap modules

This commit is contained in:
2021-06-17 15:09:29 +03:00
parent e7f7b9654e
commit 696d18cf41
87 changed files with 3 additions and 5 deletions

View File

@@ -0,0 +1,111 @@
package mc.server;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.joran.JoranConfigurator;
import ch.qos.logback.core.joran.spi.JoranException;
import com.typesafe.config.Config;
import lombok.SneakyThrows;
import mc.cliparser.CommandLine;
import mc.cliparser.CommandLineParser;
import mc.cliparser.Option;
import mc.server.di.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nonnull;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Objects;
public class Main {
public static void main(String[] args) throws IOException {
//region Setup CommandLine
Option logconfigOption = Option.builder().longName("logconfig").hasArgs(true).build();
Option configOption = Option.builder().longName("config").hasArgs(true).build();
var cliParser = new CommandLineParser();
cliParser.addOption(logconfigOption);
cliParser.addOption(configOption);
CommandLine commandLine = cliParser.parse(args);
//endregion
//region Configuration Logback
Path logconfigPath;
if (commandLine.has(logconfigOption)) {
logconfigPath = Paths.get(logconfigOption.value());
} else {
logconfigPath = defaultLogbackConfigPath();
}
reconfigureLogback(logconfigPath);
//endregion
//region Setup config
ConfigModule configModule = new ConfigModule("./config-default.conf",
commandLine.has(configOption) ? Paths.get(configOption.value()) : null);
ConfigComponent configComponent = DaggerConfigComponent.builder()
.configModule(configModule).build();
//endregion
//region Debug log config
Logger log = LoggerFactory.getLogger("LAUNCHER");
if (log.isDebugEnabled()) {
Config config = configComponent.getConfig();
log.debug("Logback config path: {}", logconfigOption.value() == null ? "(default)" : logconfigOption.value());
log.debug("Config path: {}", configOption.value() == null ? "(default)" : configOption.value());
config.entrySet().stream()
.map(entry -> String.format("[CONFIG] %s = %s", entry.getKey(), entry.getValue().render()))
.sorted()
.forEach(log::debug);
}
//endregion
ServerComponent serverComponent = DaggerServerComponent.builder()
.processorModule(new ProcessorModule(configComponent.getConfig()))
.build();
serverComponent.getProcessors().forEach(processor -> processor.setup(serverComponent.getProtocolHandlersBus()));
NettyServer server = serverComponent.getNettyServer();
String host = configComponent.getConfig().getString("server.host");
int port = configComponent.getConfig().getInt("server.port");
log.info("Server starting: {}:{}", host, port);
server.start(host, port);
}
//TODO нужно продумать как этот метод сделать доступным для расширенных версий сервера
/**
* Перенастраиваем logback с учетом путей.
* <p>По-умолчанию, logback пытается искать свои конфиги по заранее зашитым путям.
* Здесь мы изменяем эти принципы.
*/
private static void reconfigureLogback(@Nonnull Path configPath) throws IOException {
if (Files.notExists(configPath)) {
throw new FileNotFoundException(configPath.toAbsolutePath().toString());
}
LoggerContext logbackContext = (LoggerContext) LoggerFactory.getILoggerFactory();
logbackContext.reset();
JoranConfigurator configurator = new JoranConfigurator();
try(InputStream in = Files.newInputStream(configPath)) {
configurator.setContext(logbackContext);
configurator.doConfigure(in);
} catch (JoranException e) {
throw new IOException(e);
}
}
@SneakyThrows
private static Path defaultLogbackConfigPath() {
URL url = Objects.requireNonNull(Main.class.getResource("/logback-default.xml"));
return Paths.get(url.toURI());
}
}

View File

@@ -0,0 +1,55 @@
package mc.server;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import mc.protocol.handler.ProtocolHandlersBus;
import mc.protocol.handler.ProtocolInboundHandler;
import mc.protocol.handler.codec.ProtocolDecoder;
import mc.protocol.handler.codec.ProtocolEncoder;
import mc.protocol.handler.codec.ProtocolSplitter;
import javax.annotation.Nonnull;
@Slf4j
@RequiredArgsConstructor
public class NettyServer {
private final ProtocolHandlersBus protocolHandlersBus;
public void start(String host, int port) {
try {
createServerBootstrap().bind(host, port).sync().channel().closeFuture().sync();
} catch (InterruptedException e) {
if (log.isTraceEnabled()) {
log.trace("{}: {}", e.getClass().getSimpleName(), e.getMessage(), e);
}
}
}
private ServerBootstrap createServerBootstrap() {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(new NioEventLoopGroup(1), new NioEventLoopGroup())
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(@Nonnull SocketChannel socketChannel) {
socketChannel.pipeline()
.addLast("packet_splitter", new ProtocolSplitter())
.addLast("logger", new LoggingHandler(LogLevel.DEBUG))
.addLast("packet_decoder", new ProtocolDecoder(false))
.addLast("packet_encoder", new ProtocolEncoder())
.addLast("packet_handler", new ProtocolInboundHandler(protocolHandlersBus));
}
});
return bootstrap;
}
}

View File

@@ -0,0 +1,20 @@
package mc.server;
import io.netty.channel.ChannelHandlerContext;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import mc.protocol.model.Location;
import mc.protocol.utils.GameMode;
import java.util.UUID;
@RequiredArgsConstructor
@Getter
public class Player {
private final ChannelHandlerContext ctx;
private final UUID uuid;
private final String name;
private final GameMode gameMode;
private final Location location;
}

View File

@@ -0,0 +1,27 @@
package mc.server;
import io.netty.channel.ChannelHandlerContext;
import mc.protocol.model.Location;
import mc.protocol.utils.GameMode;
import java.util.LinkedList;
import java.util.UUID;
public class PlayerManager {
private final LinkedList<Player> players = new LinkedList<>();
public Player create(ChannelHandlerContext ctx, String name, GameMode gameMode, Location location) {
Player player = new Player(ctx, UUID.randomUUID(), name, gameMode, location);
players.add(player);
return player;
}
public void remove(Player player) {
players.remove(player);
}
public int online() {
return players.size();
}
}

View File

@@ -0,0 +1,10 @@
package mc.server;
import io.netty.util.AttributeKey;
import lombok.experimental.UtilityClass;
@UtilityClass
public class ServetAttributes {
public static final AttributeKey<Player> PLAYER = AttributeKey.newInstance("PLAYER");
}

View File

@@ -0,0 +1,13 @@
package mc.server.di;
import com.typesafe.config.Config;
import dagger.Component;
import javax.inject.Singleton;
@Component(modules = ConfigModule.class)
@Singleton
public interface ConfigComponent {
Config getConfig();
}

View File

@@ -0,0 +1,36 @@
package mc.server.di;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
import dagger.Module;
import dagger.Provides;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import javax.inject.Singleton;
import java.nio.file.Path;
@Module
@RequiredArgsConstructor
@Slf4j
public class ConfigModule {
private final String defaultResource;
private final Path configPath;
@Provides
@Singleton
Config provideConfig() {
Config defaultConfig = ConfigFactory.parseResources(defaultResource);
Config config;
if (configPath != null) {
Config userConfig = ConfigFactory.parseFile(configPath.toFile());
config = userConfig.withFallback(defaultConfig).resolve();
} else {
config = defaultConfig.resolve();
}
return config;
}
}

View File

@@ -0,0 +1,17 @@
package mc.server.di;
import dagger.Module;
import dagger.Provides;
import mc.server.PlayerManager;
import javax.inject.Singleton;
@Module
public class PlayerManagerModule {
@Provides
@Singleton
PlayerManager providePlayerManager() {
return new PlayerManager();
}
}

View File

@@ -0,0 +1,48 @@
package mc.server.di;
import com.typesafe.config.Config;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.IntoSet;
import lombok.RequiredArgsConstructor;
import mc.protocol.world.World;
import mc.server.PlayerManager;
import mc.server.processor.*;
import javax.inject.Singleton;
import java.util.Random;
@Module
@RequiredArgsConstructor
public class ProcessorModule {
private final Config config;
@Provides
@IntoSet
@Singleton
PacketProcessor provideProcessorHadshake() {
return new ProcessorHandshake();
}
@Provides
@IntoSet
@Singleton
PacketProcessor provideProcessorStatus() {
return new ProcessorStatus(config);
}
@Provides
@IntoSet
@Singleton
PacketProcessor provideProcessorLogin(PlayerManager playerManager, World world) {
return new ProcessorLogin(playerManager, new Random(System.currentTimeMillis()), config, world);
}
@Provides
@IntoSet
@Singleton
PacketProcessor provideProcessorPlay() {
return new ProcessorPlay();
}
}

View File

@@ -0,0 +1,21 @@
package mc.server.di;
import dagger.Component;
import mc.protocol.handler.ProtocolHandlersBus;
import mc.server.NettyServer;
import mc.server.processor.PacketProcessor;
import javax.inject.Singleton;
import java.util.Set;
@Component(modules = {
ServerModule.class, ProcessorModule.class, PlayerManagerModule.class,
WorldModule.class
})
@Singleton
public interface ServerComponent {
ProtocolHandlersBus getProtocolHandlersBus();
NettyServer getNettyServer();
Set<PacketProcessor> getProcessors();
}

View File

@@ -0,0 +1,24 @@
package mc.server.di;
import dagger.Module;
import dagger.Provides;
import mc.protocol.handler.ProtocolHandlersBus;
import mc.server.NettyServer;
import javax.inject.Singleton;
@Module
public class ServerModule {
@Provides
@Singleton
ProtocolHandlersBus providePacketProcessor() {
return new ProtocolHandlersBus();
}
@Provides
@Singleton
NettyServer provideNettyServer(ProtocolHandlersBus protocolHandlersBus) {
return new NettyServer(protocolHandlersBus);
}
}

View File

@@ -0,0 +1,18 @@
package mc.server.di;
import dagger.Module;
import dagger.Provides;
import mc.protocol.world.World;
import mc.server.world.VoidWorld;
import javax.inject.Singleton;
@Module
public class WorldModule {
@Provides
@Singleton
World provideWorld() {
return new VoidWorld();
}
}

View File

@@ -0,0 +1,8 @@
package mc.server.processor;
import mc.protocol.handler.ProtocolHandlersBus;
public interface PacketProcessor {
void setup(ProtocolHandlersBus protocolHandlersBus);
}

View File

@@ -0,0 +1,19 @@
package mc.server.processor;
import io.netty.channel.ChannelHandlerContext;
import mc.protocol.ProtocolAttributes;
import mc.protocol.State;
import mc.protocol.handler.ProtocolHandlersBus;
import mc.protocol.packets.handshaking.client.HandshakePacket;
public class ProcessorHandshake implements PacketProcessor {
@Override
public void setup(ProtocolHandlersBus protocolHandlersBus) {
protocolHandlersBus.addHandler(State.HANDSHAKING, HandshakePacket.class, this::handshake);
}
private void handshake(ChannelHandlerContext ctx, HandshakePacket packet) {
ctx.channel().attr(ProtocolAttributes.STATE).set(packet.getNextState());
}
}

View File

@@ -0,0 +1,142 @@
package mc.server.processor;
import com.typesafe.config.Config;
import io.netty.channel.ChannelHandlerContext;
import lombok.RequiredArgsConstructor;
import mc.protocol.ProtocolAttributes;
import mc.protocol.State;
import mc.protocol.handler.ProtocolHandlersBus;
import mc.protocol.model.Location;
import mc.protocol.model.Look;
import mc.protocol.packets.KeepAlivePacket;
import mc.protocol.packets.login.client.LoginStartPacket;
import mc.protocol.packets.login.server.LoginSuccessPacket;
import mc.protocol.packets.play.server.*;
import mc.protocol.utils.Difficulty;
import mc.protocol.utils.GameMode;
import mc.protocol.world.Chunk;
import mc.protocol.world.World;
import mc.server.Player;
import mc.server.PlayerManager;
import mc.server.ServetAttributes;
import mc.server.util.LocationUtils;
import java.util.Random;
@RequiredArgsConstructor
public class ProcessorLogin implements PacketProcessor {
private final PlayerManager playerManager;
private final Random random;
private final Config config;
private final World world;
@Override
public void setup(ProtocolHandlersBus protocolHandlersBus) {
protocolHandlersBus.addHandler(State.LOGIN, LoginStartPacket.class, this::login);
}
private void login(ChannelHandlerContext ctx, LoginStartPacket packet) {
Player player = playerManager.create(ctx, packet.getName(), GameMode.SURVIVAL, world.getSpawn());
ctx.channel().attr(ServetAttributes.PLAYER).set(player);
sendLoginSuccess(player);
sendJoinGame(player);
sendSpawnPosition(player);
sendPlayerAbilities(player);
ctx.flush();
sendWorldData(player);
sendPlayerPositionAndLook(player);
sendKeepAlive(player);
ctx.flush();
}
private void sendLoginSuccess(Player player) {
var loginSuccessPacket = new LoginSuccessPacket();
loginSuccessPacket.setUuid(player.getUuid());
loginSuccessPacket.setName(player.getName());
player.getCtx().writeAndFlush(loginSuccessPacket);
player.getCtx().channel().attr(ProtocolAttributes.STATE).set(State.PLAY);
}
private void sendJoinGame(Player player) {
var joinGamePacket = new JoinGamePacket();
joinGamePacket.setEntityId(random.nextInt());
joinGamePacket.setGameMode(player.getGameMode());
joinGamePacket.setDimension(0/*Overworld*/);
joinGamePacket.setDifficulty(Difficulty.PEACEFUL);
joinGamePacket.setLevelType(world.getLevelType());
player.getCtx().write(joinGamePacket);
}
private void sendSpawnPosition(Player player) {
var spawnPositionPacket = new SpawnPositionPacket();
spawnPositionPacket.setSpawn(player.getLocation());
player.getCtx().write(spawnPositionPacket);
}
private void sendPlayerAbilities(Player player) {
var playerAbilitiesPacket = new PlayerAbilitiesPacket();
playerAbilitiesPacket.setCatFly(true);
playerAbilitiesPacket.setFlying(true);
playerAbilitiesPacket.setCreativeMode(false);
playerAbilitiesPacket.setInvulnerable(true);
playerAbilitiesPacket.setFieldOfView(0.0f);
playerAbilitiesPacket.setFlyingSpeed(0.05f);
player.getCtx().write(playerAbilitiesPacket);
}
@SuppressWarnings("java:S2589")
private void sendWorldData(Player player) {
Location chunkLocation = LocationUtils.toChunkXZ(player.getLocation());
Chunk chunk = world.getChunk((int) chunkLocation.getX(), (int) chunkLocation.getZ());
var chunkDataPacket = new ChunkDataPacket();
chunkDataPacket.setX(chunk.getX());
chunkDataPacket.setZ(chunk.getZ());
player.getCtx().write(chunkDataPacket);
for (int i = 1; i <= config.getInt("world.view-distance"); i++) {
int minX = (int) chunkLocation.getX() - i;
int minZ = (int) chunkLocation.getZ() - i;
int maxX = (int) chunkLocation.getX() + i;
int maxZ = (int) chunkLocation.getZ() + i;
for (int z = minZ; z <= maxZ; z++) {
for (int x = minX; x <= maxX; x++) {
if ((z == minZ || z == maxZ) || (x == minX || x == maxX)) {
chunkDataPacket = new ChunkDataPacket();
chunkDataPacket.setX(x);
chunkDataPacket.setZ(z);
player.getCtx().write(chunkDataPacket);
}
}
}
}
player.getCtx().flush();
}
private void sendPlayerPositionAndLook(Player player) {
var playerPositionAndLookPacket = new SPlayerPositionAndLookPacket();
playerPositionAndLookPacket.setPosition(player.getLocation());
playerPositionAndLookPacket.setLook(new Look().set(0f, 0f));
playerPositionAndLookPacket.setTeleportId(random.nextInt());
player.getCtx().write(playerPositionAndLookPacket);
}
private void sendKeepAlive(Player player) {
var keepAlivePacket = new KeepAlivePacket();
keepAlivePacket.setPayload(System.currentTimeMillis());
player.getCtx().write(keepAlivePacket);
}
}

View File

@@ -0,0 +1,30 @@
package mc.server.processor;
import io.netty.channel.ChannelHandlerContext;
import lombok.extern.slf4j.Slf4j;
import mc.protocol.State;
import mc.protocol.handler.ProtocolHandlersBus;
import mc.protocol.packets.KeepAlivePacket;
import java.util.concurrent.TimeUnit;
@Slf4j
public class ProcessorPlay implements PacketProcessor {
@Override
public void setup(ProtocolHandlersBus protocolHandlersBus) {
protocolHandlersBus
.addHandler(State.PLAY, KeepAlivePacket.class, this::keepAlive);
}
private void keepAlive(ChannelHandlerContext ctx, KeepAlivePacket packet) {
try {
TimeUnit.MILLISECONDS.sleep(50);
ctx.writeAndFlush(packet);
} catch (InterruptedException e) {
if (log.isTraceEnabled()) {
log.trace("{}", e.getMessage(), e);
}
}
}
}

View File

@@ -0,0 +1,49 @@
package mc.server.processor;
import com.typesafe.config.Config;
import io.netty.channel.ChannelHandlerContext;
import lombok.RequiredArgsConstructor;
import mc.protocol.ProtocolConstant;
import mc.protocol.State;
import mc.protocol.handler.ProtocolHandlersBus;
import mc.protocol.model.ServerInfo;
import mc.protocol.model.ServerInfoSerializer;
import mc.protocol.model.text.TextSerializer;
import mc.protocol.packets.KeepAlivePacket;
import mc.protocol.packets.status.client.StatusServerRequestPacket;
import mc.protocol.packets.status.server.StatusServerResponse;
import java.util.Collections;
@RequiredArgsConstructor
public class ProcessorStatus implements PacketProcessor {
private final Config config;
@Override
public void setup(ProtocolHandlersBus protocolHandlersBus) {
protocolHandlersBus
.addHandler(State.STATUS, KeepAlivePacket.class, this::keepAlive)
.addHandler(State.STATUS, StatusServerRequestPacket.class, this::statusRequest);
}
private void keepAlive(ChannelHandlerContext ctx, KeepAlivePacket packet) {
ctx.writeAndFlush(packet);
ctx.close();
}
private void statusRequest(ChannelHandlerContext ctx, StatusServerRequestPacket packet) {
ServerInfo serverInfo = new ServerInfo();
serverInfo.version().name(ProtocolConstant.PROTOCOL_NAME);
serverInfo.version().protocol(ProtocolConstant.PROTOCOL_NUMBER);
serverInfo.players().max(config.getInt("players.max-online"));
serverInfo.players().online(config.getInt("players.fake-online.value"));
serverInfo.players().sample(Collections.emptyList());
serverInfo.description(TextSerializer.fromPlain(config.getString("motd")));
StatusServerResponse response = new StatusServerResponse();
response.setInfo(ServerInfoSerializer.toStringPlain(serverInfo));
ctx.writeAndFlush(response);
}
}

View File

@@ -0,0 +1,12 @@
package mc.server.util;
import lombok.experimental.UtilityClass;
import mc.protocol.model.Location;
@UtilityClass
public class LocationUtils {
public Location toChunkXZ(Location location) {
return new Location().set((int) location.getX() >> 4, 0d, (int) location.getZ() >> 4);
}
}

View File

@@ -0,0 +1,11 @@
package mc.server.world;
import lombok.Data;
import mc.protocol.world.Chunk;
@Data
public class VoidChunk implements Chunk {
private final int x;
private final int z;
}

View File

@@ -0,0 +1,26 @@
package mc.server.world;
import mc.protocol.model.Location;
import mc.protocol.utils.LevelType;
import mc.protocol.world.Chunk;
import mc.protocol.world.World;
public class VoidWorld implements World {
private static final Location spawn = new Location().set(7d, 130d, 7d);
@Override
public LevelType getLevelType() {
return LevelType.FLAT;
}
@Override
public Location getSpawn() {
return VoidWorld.spawn;
}
@Override
public Chunk getChunk(int x, int z) {
return new VoidChunk(x, z);
}
}

View File

@@ -0,0 +1,27 @@
server {
host: "127.0.0.1"
port: 25565
}
motd: """&bmc-project &8:: &4ZERO
&8develop by &7DmitriyMX"""
disconnect-reason: "&4Server is not available."
players {
max-online: 0
fake-online {
enable: false
value: 0
}
}
# Размер значка: 64x64 px
icon {
enable: false
path: "favicon.png"
}
world {
view-distance: 1
}

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<statusListener class="ch.qos.logback.core.status.NopStatusListener" />
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<layout class="ch.qos.logback.classic.PatternLayout">
<Pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%35.35logger{34}] -- %msg%n</Pattern>
</layout>
</appender>
<root level="info">
<appender-ref ref="CONSOLE"/>
</root>
<logger name="LAUNCHER" level="debug" additivity="false">
<appender-ref ref="CONSOLE"/>
</logger>
<!-- раскоментировать для простотра дампа пакетов -->
<logger name="io.netty.handler.logging.LoggingHandler" level="debug" additivity="false">
<appender-ref ref="CONSOLE"/>
</logger>
</configuration>

View File

@@ -0,0 +1,95 @@
package mc.server.di;
import com.typesafe.config.Config;
import lombok.SneakyThrows;
import org.junit.jupiter.api.Test;
import java.net.URL;
import java.nio.file.Path;
import java.nio.file.Paths;
import static org.junit.jupiter.api.Assertions.*;
class ConfigModuleTest {
private static final String emptyConfig = "./config-empty.conf";
/*
Проверяем, что загруженный объект конфига является singleton
*/
@Test
void singleton() {
ConfigComponent component = DaggerConfigComponent.builder()
.configModule(new ConfigModule(emptyConfig, pathResource("/config-1.conf"))).build();
Config config1 = component.getConfig();
Config config2 = component.getConfig();
assertEquals(config1, config2);
assertSame(config1, config2);
}
/*
Корректная загрузка конфига
*/
@Test
void loadConfig() {
ConfigComponent component = DaggerConfigComponent.builder()
.configModule(new ConfigModule(emptyConfig, pathResource("/config-1.conf"))).build();
Config config = component.getConfig();
assertEquals("value1", config.getString("key1"));
assertEquals("value2", config.getString("key2.subkey1"));
assertEquals("value3", config.getString("key3.subkey1"));
assertEquals("value4", config.getString("\"key4.somename\""));
assertEquals("value5", config.getString("key5"));
}
/*
Проверка include
*/
@Test
void includeTest() {
ConfigComponent component = DaggerConfigComponent.builder()
.configModule(new ConfigModule(emptyConfig, pathResource("/config-2.conf"))).build();
Config config = component.getConfig();
assertEquals("value1", config.getString("key1"));
assertEquals("value2", config.getString("key2.subkey1"));
assertEquals("value3", config.getString("key3.subkey1"));
assertEquals("value4", config.getString("\"key4.somename\""));
assertEquals("value5", config.getString("key5"));
}
/*
Работа с многострочностью
*/
@Test
void multilineTest() {
ConfigComponent component = DaggerConfigComponent.builder()
.configModule(new ConfigModule(emptyConfig, pathResource("/config-1.conf"))).build();
Config config = component.getConfig();
assertEquals("line1\nline2", config.getString("key6"));
}
/*
Проверяем работу merge config
*/
@Test
void mergeConfigTest() {
ConfigComponent component = DaggerConfigComponent.builder()
.configModule(new ConfigModule("./config-1.conf", pathResource("/config-3.conf"))).build();
Config config = component.getConfig();
assertEquals("value1_merged", config.getString("key1"));
assertEquals("value2", config.getString("key2.subkey1"));
}
@SneakyThrows
private static Path pathResource(String resource) {
URL url = ConfigModuleTest.class.getResource(resource);
assertNotNull(url);
return Paths.get(url.toURI());
}
}

View File

@@ -0,0 +1,15 @@
key1: value1
key2.subkey1: value2
key3 {
subkey1: value3
}
"key4.somename": value4
variable: value5
key5: ${variable}
key6: """line1
line2"""

View File

@@ -0,0 +1 @@
include "config-1.conf"

View File

@@ -0,0 +1 @@
key1: value1_merged