diff --git a/logic.gradle b/logic.gradle index d00218d..c7a68fc 100644 --- a/logic.gradle +++ b/logic.gradle @@ -38,8 +38,6 @@ dependencies { testImplementation libs.test.junit5.api testImplementation libs.test.junit5.params testRuntimeOnly libs.test.junit5.engine - - testRuntimeOnly libs.test.logger } test { diff --git a/server-new/build.gradle b/server-new/build.gradle index 7c1af0e..62650da 100644 --- a/server-new/build.gradle +++ b/server-new/build.gradle @@ -23,8 +23,9 @@ application { } dependencies { - implementation project(':protocol-new') + implementation project(':cli-parser') + implementation libs.logger.logback implementation libs.hocon } diff --git a/server-new/src/main/java/mc/server/Main.java b/server-new/src/main/java/mc/server/Main.java new file mode 100644 index 0000000..8374a3e --- /dev/null +++ b/server-new/src/main/java/mc/server/Main.java @@ -0,0 +1,97 @@ +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.ConfigComponent; +import mc.server.di.ConfigModule; +import mc.server.di.DaggerConfigComponent; +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(); + Config config = configComponent.getConfig(); + //endregion + + Logger log = LoggerFactory.getLogger("LAUNCHER"); + if (log.isDebugEnabled()) { + 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); + } + } + + /** + * Перенастраиваем logback с учетом путей. + *

По-умолчанию, 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()); + } +} diff --git a/server-new/src/main/java/mc/server/di/ConfigModule.java b/server-new/src/main/java/mc/server/di/ConfigModule.java index 5253d52..e9cd35c 100644 --- a/server-new/src/main/java/mc/server/di/ConfigModule.java +++ b/server-new/src/main/java/mc/server/di/ConfigModule.java @@ -15,11 +15,22 @@ import java.nio.file.Path; @Slf4j public class ConfigModule { + private final String defaultResource; private final Path configPath; @Provides @Singleton Config provideConfig() { - return ConfigFactory.parseFile(configPath.toFile()).resolve(); + 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; } } diff --git a/server-new/src/main/resources/config-default.conf b/server-new/src/main/resources/config-default.conf new file mode 100644 index 0000000..5a91e83 --- /dev/null +++ b/server-new/src/main/resources/config-default.conf @@ -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 +} diff --git a/server/src/main/resources/logback-sample.xml b/server-new/src/main/resources/logback-default.xml similarity index 80% rename from server/src/main/resources/logback-sample.xml rename to server-new/src/main/resources/logback-default.xml index f72ea29..2e6a315 100644 --- a/server/src/main/resources/logback-sample.xml +++ b/server-new/src/main/resources/logback-default.xml @@ -12,9 +12,12 @@ - - --> \ No newline at end of file diff --git a/server-new/src/test/java/mc/server/di/ConfigModuleTest.java b/server-new/src/test/java/mc/server/di/ConfigModuleTest.java index 485eba4..7dbec46 100644 --- a/server-new/src/test/java/mc/server/di/ConfigModuleTest.java +++ b/server-new/src/test/java/mc/server/di/ConfigModuleTest.java @@ -12,10 +12,15 @@ 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(pathResource("/config-1.conf"))).build(); + .configModule(new ConfigModule(emptyConfig, pathResource("/config-1.conf"))).build(); Config config1 = component.getConfig(); Config config2 = component.getConfig(); @@ -23,10 +28,13 @@ class ConfigModuleTest { assertSame(config1, config2); } + /* + Корректная загрузка конфига + */ @Test void loadConfig() { ConfigComponent component = DaggerConfigComponent.builder() - .configModule(new ConfigModule(pathResource("/config-1.conf"))).build(); + .configModule(new ConfigModule(emptyConfig, pathResource("/config-1.conf"))).build(); Config config = component.getConfig(); assertEquals("value1", config.getString("key1")); @@ -36,10 +44,13 @@ class ConfigModuleTest { assertEquals("value5", config.getString("key5")); } + /* + Проверка include + */ @Test void includeTest() { ConfigComponent component = DaggerConfigComponent.builder() - .configModule(new ConfigModule(pathResource("/config-2.conf"))).build(); + .configModule(new ConfigModule(emptyConfig, pathResource("/config-2.conf"))).build(); Config config = component.getConfig(); assertEquals("value1", config.getString("key1")); @@ -49,6 +60,31 @@ class ConfigModuleTest { 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); diff --git a/server-new/src/test/resources/config-1.conf b/server-new/src/test/resources/config-1.conf index af008fc..02b8c73 100644 --- a/server-new/src/test/resources/config-1.conf +++ b/server-new/src/test/resources/config-1.conf @@ -9,4 +9,7 @@ key3 { "key4.somename": value4 variable: value5 -key5: ${variable} \ No newline at end of file +key5: ${variable} + +key6: """line1 +line2""" \ No newline at end of file diff --git a/server-new/src/test/resources/config-3.conf b/server-new/src/test/resources/config-3.conf new file mode 100644 index 0000000..35f7d6c --- /dev/null +++ b/server-new/src/test/resources/config-3.conf @@ -0,0 +1 @@ +key1: value1_merged \ No newline at end of file diff --git a/server-new/src/test/resources/config-empty.conf b/server-new/src/test/resources/config-empty.conf new file mode 100644 index 0000000..e69de29