diff --git a/.gitattributes b/.gitattributes index 72216f8..0f3666f 100644 --- a/.gitattributes +++ b/.gitattributes @@ -4,4 +4,4 @@ gradlew.bat text eol=crlf gradle/wrapper/gradle-wrapper.properties text eol=lf # Other -.gitattributes text eol=lf \ No newline at end of file +.gitattributes text eol=lf diff --git a/.gitignore b/.gitignore index 90ae4d1..6132566 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,3 @@ out/ # GRADLE # .gradle/ build/ -publish.gradle diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..ea343c2 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,11 @@ +image: gradle:6.7.0-jdk8 +variables: + GRADLE_OPTS: "-Dorg.gradle.daemon=false" +before_script: + - export GRADLE_USER_HOME=`pwd`/.gradle + +build: + stage: build + only: + - dev/ci + script: gradle --console=plain publish diff --git a/build.gradle b/build.gradle index bc0e549..663157d 100644 --- a/build.gradle +++ b/build.gradle @@ -1,79 +1,88 @@ +//file:noinspection GroovyAssignabilityCheck plugins { id 'java' + id 'maven-publish' + id 'jacoco' } -def publishScript = file(rootProject.getProjectDir().getPath() + '/publish.gradle') -if (publishScript.exists()) { - apply from: publishScript.path -} +project.group = 'ghast' +jar.archiveBaseName.set(project.name) +project.version = '1.12.2-SNAPSHOT' +def gitlab_projectid = 23328133 -project.group = projectGroup -project.version = projectVersion +compileJava { + sourceCompatibility = targetCompatibility = JavaVersion.VERSION_1_8 + options.encoding = 'UTF-8' +} repositories { - mavenLocal() mavenCentral() maven { url 'https://hub.spigotmc.org/nexus/content/groups/public' } - maven { url 'https://dmx-mc-project.gitlab.io/maven-repository/' } } -ext { - junitVersion = '5.5.2' - - libs = [ - bukkit: [lib: 'org.bukkit:bukkit:1.12.2-R0.1-SNAPSHOT', exclude: [ - 'com.google.code.gson:gson', - 'com.googlecode.json-simple:json-simple', - 'commons-lang:commons-lang', - 'org.yaml:snakeyaml' - ]], - commons_text: 'org.apache.commons:commons-text:1.9', - lombok: 'org.projectlombok:lombok:1.18.12', - reflection_object: 'ru.dmitriymx:reflection-object:1.2', - test: [ - junit5: [ - "org.junit.jupiter:junit-jupiter-api:$junitVersion", - "org.junit.jupiter:junit-jupiter-engine:$junitVersion" - ], - mock: ['org.mockito:mockito-core:1.10.19'], - h2db: 'com.h2database:h2:1.4.200' - ] - - ] -} - -def compileOnly2(library) { - dependencies.compileOnly library.lib, { - library.exclude.each { String excludeLibStr -> - String[] excludeLib = excludeLibStr.split(':') - exclude group: excludeLib[0], module: excludeLib[1] - } - } -} - -def testImplementation2(library) { - dependencies.testImplementation library.lib, { - library.exclude.each { String excludeLibStr -> - String[] excludeLib = excludeLibStr.split(':') - exclude group: excludeLib[0], module: excludeLib[1] - } - } -} +def lombokVersion = '1.18.22' +def junitVersion = '5.8.1' +def bukkitVersion = '1.12.2-R0.1-SNAPSHOT' dependencies { - compileOnly libs.lombok - annotationProcessor libs.lombok + annotationProcessor("org.projectlombok:lombok:$lombokVersion") + compileOnly("org.projectlombok:lombok:$lombokVersion") - compileOnly2 libs.bukkit - implementation libs.commons_text - implementation libs.reflection_object + compileOnly("org.bukkit:bukkit:$bukkitVersion") { + exclude(module: 'gson') + exclude(module: 'json-simple') + exclude(module: 'commons-lang') + exclude(module: 'snakeyaml') + } + implementation('org.apache.commons:commons-text:1.9') + implementation('org.jooq:joor-java-8:0.9.14') - testImplementation libs.test.junit5 - testImplementation libs.test.mock - testImplementation2 libs.bukkit - testImplementation libs.test.h2db + testImplementation("org.junit.jupiter:junit-jupiter-api:$junitVersion") + testImplementation("org.junit.jupiter:junit-jupiter-engine:$junitVersion") + testImplementation('org.mockito:mockito-core:4.0.0') + testImplementation('org.jooq:joor-java-8:0.9.14') + testImplementation("org.bukkit:bukkit:$bukkitVersion") { + exclude(module: 'gson') + exclude(module: 'json-simple') + exclude(module: 'commons-lang') + exclude(module: 'snakeyaml') + } + testImplementation('com.h2database:h2:1.4.200') } test { useJUnitPlatform() -} \ No newline at end of file +} + +jacoco { + toolVersion = '0.8.5' +} + +jacocoTestReport { + dependsOn test +} + +publishing { + publications { + mavenBinary(MavenPublication) { + groupId = project.group + artifactId = project.name + version = project.version + + from components.java + } + } + + repositories { + maven { + url "https://gitlab.com/api/v4/projects/${gitlab_projectid}/packages/maven" + credentials(HttpHeaderCredentials) { + name = 'Job-Token' + value = System.getenv('CI_JOB_TOKEN') + } + authentication { + header(HttpHeaderAuthentication) + } + } + } +} diff --git a/gradle.properties b/gradle.properties deleted file mode 100644 index 645012a..0000000 --- a/gradle.properties +++ /dev/null @@ -1,3 +0,0 @@ -projectGroup=ghast -projectName=ghast-tools -projectVersion=1.12.1 \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 14e30f7..a0f7639 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/settings.gradle b/settings.gradle index de4b82c..8159b24 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1 @@ -rootProject.name=projectName \ No newline at end of file +rootProject.name = 'ghast-tools' diff --git a/src/main/java/ghast/BuildHelper.java b/src/main/java/ghast/BuildHelper.java index 6e5d9fe..224dc15 100644 --- a/src/main/java/ghast/BuildHelper.java +++ b/src/main/java/ghast/BuildHelper.java @@ -8,13 +8,15 @@ import org.bukkit.block.Block; import org.bukkit.block.BlockFace; import org.bukkit.block.Sign; import org.bukkit.block.Skull; -import ru.dmitriymx.reflection.ReflectionClass; -import ru.dmitriymx.reflection.ReflectionObject; +import org.joor.Reflect; import java.nio.charset.StandardCharsets; import java.util.Base64; import java.util.UUID; +import static org.joor.Reflect.on; +import static org.joor.Reflect.onClass; + @UtilityClass @SuppressWarnings("unused") public class BuildHelper { @@ -96,16 +98,13 @@ public class BuildHelper { */ public static void setPlayerHeadSkin(Skull skull, String skinUrl) { //TODO заменить рефлексию на "фантомные" классы - ReflectionObject refobjBlockPosition = new ReflectionClass(CLASS_BLOCKPOSITION) - .constructor(double.class, double.class, double.class) - .newInstance(skull.getX(), skull.getY(), skull.getZ()); + Reflect reflectBlockPosition = onClass(CLASS_BLOCKPOSITION) + .create(skull.getX(), skull.getY(), skull.getZ()); - new ReflectionObject(skull.getWorld()) - .method("getHandle").invoke() - .method("getTileEntity", CLASS_BLOCKPOSITION) - .invoke(refobjBlockPosition.getOriginalObject()) - .method("setGameProfile", CLASS_GAMEPROFILE) - .invoke(getRefObjPlayerProfile(skinUrl).getOriginalObject()); + on(skull.getWorld()) + .call("getHandle") + .call("getTileEntity", reflectBlockPosition.get()) + .call("setGameProfile", getReflectPlayerProfile(skinUrl).get()); } public Sign placeSignWall(Location location, BlockFace face) { @@ -119,22 +118,18 @@ public class BuildHelper { return sign; } - private ReflectionObject getRefObjPlayerProfile(String url){ - ReflectionObject refobjProperty = new ReflectionClass( - getClassForName("com.mojang.authlib.properties.Property")) - .constructor(String.class, String.class) - .newInstance("textures", Base64.getEncoder() + private Reflect getReflectPlayerProfile(String url) { + Reflect reflectProperty = onClass("com.mojang.authlib.properties.Property") + .create("textures", Base64.getEncoder() .encodeToString(("{textures:{SKIN:{url:\"" + url + "\"}}}").getBytes(StandardCharsets.UTF_8))); - ReflectionObject refobjGameProfile = new ReflectionClass(CLASS_GAMEPROFILE) - .constructor(UUID.class, String.class) - .newInstance(UUID.randomUUID(), null); - refobjGameProfile - .method("getProperties").invoke() - .method("put", Object.class, Object.class) - .invoke("textures", refobjProperty.getOriginalObject()); + Reflect reflectGameProfile = onClass(CLASS_GAMEPROFILE) + .create(UUID.randomUUID(), null); - return refobjGameProfile; + reflectGameProfile.call("getProperties") + .call("put", "textures", reflectProperty.get()); + + return reflectGameProfile; } private Class getClassForName(String className) { diff --git a/src/main/java/ghast/HashMaps.java b/src/main/java/ghast/HashMaps.java new file mode 100644 index 0000000..1da8d7d --- /dev/null +++ b/src/main/java/ghast/HashMaps.java @@ -0,0 +1,171 @@ +package ghast; + +import lombok.*; + +import java.util.HashMap; +import java.util.Map; + +/** + * Создание Map как в Java 9+. + */ +@SuppressWarnings("DuplicatedCode") +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public final class HashMaps { + + public static Map of() { + return new HashMap<>(0); + } + + public static Map of(K k1, V v1) { + return new HashMap(1) {{ + put(k1, v1); + }}; + } + + public static Map of(K k1, V v1, K k2, V v2) { + return new HashMap(2) {{ + put(k1, v1); + put(k2, v2); + }}; + } + + public static Map of(K k1, V v1, K k2, V v2, K k3, V v3) { + return new HashMap(3) {{ + put(k1, v1); + put(k2, v2); + put(k3, v3); + }}; + } + + public static Map of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4) { + return new HashMap(4) {{ + put(k1, v1); + put(k2, v2); + put(k3, v3); + put(k4, v4); + }}; + } + + public static Map of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5) { + return new HashMap(5) {{ + put(k1, v1); + put(k2, v2); + put(k3, v3); + put(k4, v4); + put(k5, v5); + }}; + } + + public static Map of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5, + K k6, V v6) { + return new HashMap(6) {{ + put(k1, v1); + put(k2, v2); + put(k3, v3); + put(k4, v4); + put(k5, v5); + put(k6, v6); + }}; + } + + public static Map of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5, + K k6, V v6, K k7, V v7) { + return new HashMap(7) {{ + put(k1, v1); + put(k2, v2); + put(k3, v3); + put(k4, v4); + put(k5, v5); + put(k6, v6); + put(k7, v7); + }}; + } + + public static Map of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5, + K k6, V v6, K k7, V v7, K k8, V v8) { + return new HashMap(8) {{ + put(k1, v1); + put(k2, v2); + put(k3, v3); + put(k4, v4); + put(k5, v5); + put(k6, v6); + put(k7, v7); + put(k8, v8); + }}; + } + + public static Map of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5, + K k6, V v6, K k7, V v7, K k8, V v8, K k9, V v9) { + return new HashMap(9) {{ + put(k1, v1); + put(k2, v2); + put(k3, v3); + put(k4, v4); + put(k5, v5); + put(k6, v6); + put(k7, v7); + put(k8, v8); + put(k9, v9); + }}; + } + + public static Map of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5, + K k6, V v6, K k7, V v7, K k8, V v8, K k9, V v9, K k10, V v10) { + return new HashMap(10) {{ + put(k1, v1); + put(k2, v2); + put(k3, v3); + put(k4, v4); + put(k5, v5); + put(k6, v6); + put(k7, v7); + put(k8, v8); + put(k9, v9); + put(k10, v10); + }}; + } + + @SafeVarargs + public static Map ofEntries(Map.Entry... entries) { + if (entries == null || entries.length == 0) { + return of(); + } + + Map map = new HashMap<>(entries.length); + for (Map.Entry entry : entries) { + map.put(entry.getKey(), entry.getValue()); + } + + return map; + } + + public static Map.Entry entry(K k, V v) { + return new SimpleEntry<>(k, v); + } + + @AllArgsConstructor + @EqualsAndHashCode + private static class SimpleEntry implements Map.Entry { + + private final K key; + private V value; + + @Override + public K getKey() { + return key; + } + + @Override + public V getValue() { + return value; + } + + @Override + public V setValue(V value) { + V prev = this.value; + this.value = value; + return prev; + } + } +} diff --git a/src/main/java/ghast/Messages.java b/src/main/java/ghast/Messages.java index 9856f31..6869b9b 100644 --- a/src/main/java/ghast/Messages.java +++ b/src/main/java/ghast/Messages.java @@ -1,6 +1,7 @@ package ghast; -import lombok.experimental.UtilityClass; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; import org.apache.commons.text.StringSubstitutor; import java.io.BufferedReader; @@ -9,22 +10,24 @@ import java.io.Reader; import java.util.HashMap; import java.util.Map; import java.util.Properties; +import java.util.stream.Collectors; -@UtilityClass -public class Messages { +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public final class Messages { - private final Map MESSAGES_MAP = new HashMap<>(); + private static final Map MESSAGES_MAP = new HashMap<>(); //region Load messages - /** * Загрузка сообщений из {@link Properties} * * @param properties список сообщений и шаблонов */ - public void load(Properties properties) { - MESSAGES_MAP.clear(); - properties.forEach((key, value) -> MESSAGES_MAP.put(key.toString().trim().toLowerCase(), value.toString().trim())); + public static void load(Properties properties) { + load(properties.entrySet().stream().collect(Collectors.toMap( + entry -> (String) entry.getKey(), + entry -> (String) entry.getValue() + ))); } /** @@ -35,15 +38,15 @@ public class Messages { * * @param reader {@link Reader} со списоком сообщений и шаблонов */ - public void load(Reader reader) { - MESSAGES_MAP.clear(); - try { - BufferedReader bufferedReader = new BufferedReader(reader); - String line; - while ((line = bufferedReader.readLine()) != null) { - String[] split = line.split("=", 2); - MESSAGES_MAP.put(split[0].trim().toLowerCase(), split[1].trim()); - } + public static void load(Reader reader) { + try(BufferedReader bufferedReader = new BufferedReader(reader)) { + Map map = bufferedReader.lines() + .map(line -> line.split("=", 2)) + .collect(Collectors.toMap( + pair -> pair[0].trim().toLowerCase(), + pair -> pair[1].trim() + )); + load(map); } catch (IOException e) { //TODO заменить на специализированный Exception throw new RuntimeException("Error load messages: " + e.getMessage(), e); @@ -55,7 +58,7 @@ public class Messages { * * @param messages список сообщений и шаблонов */ - public void load(Map messages) { + public static void load(Map messages) { MESSAGES_MAP.clear(); MESSAGES_MAP.putAll(messages); } @@ -66,9 +69,9 @@ public class Messages { * Получить обычное сообщение по ключу/коду. * * @param key ключ/код - * @return сообщение, если таковое задано. Иначе - ключ + * @return Сообщение, если таковое задано. Иначе - ключ */ - public String get(String key) { + public static String get(String key) { String keyLc = key.toLowerCase(); return MESSAGES_MAP.getOrDefault(keyLc, keyLc); } @@ -78,9 +81,9 @@ public class Messages { * * @param key ключ/код * @param params список параметров - * @return сообщение, если таковое задано. Иначе - ключ + * @return Сообщение, если таковое задано. Иначе - ключ */ - public String get(String key, Map params) { + public static String get(String key, Map params) { String keyLc = key.toLowerCase(); if (MESSAGES_MAP.containsKey(keyLc)) { @@ -95,9 +98,14 @@ public class Messages { * * @param key ключ/код * @param params чередующийся по парный список параметров: {@link String (str)param_name}, {@link Object (obj)param_value} и т.д. - * @return сообщение, если таковое задано. Иначе - ключ + * @return Сообщение, если таковое задано. Иначе - ключ */ - public String get(String key, Object... params) { + @SuppressWarnings("unchecked") + public static String get(String key, Object... params) { + if (params.length == 1 && params[0] instanceof Map) { + return get(key, (Map) params[0]); + } + String keyLc = key.toLowerCase(); if (MESSAGES_MAP.containsKey(keyLc)) { @@ -116,7 +124,7 @@ public class Messages { * @param params параметры * @return сообщение */ - public String format(String format, Map params) { + public static String format(String format, Map params) { return StringSubstitutor.replace(format, params, "{", "}"); } @@ -127,12 +135,17 @@ public class Messages { * @param params параметры * @return сообщение */ - public String format(String format, Object... params) { + @SuppressWarnings("unchecked") + public static String format(String format, Object... params) { + if (params.length == 1 && params[0] instanceof Map) { + return format(format, (Map) params[0]); + } + return format(format, arrayParamsToMap(params)); } //endregion - private Map arrayParamsToMap(Object... params) { + private static Map arrayParamsToMap(Object... params) { int len; if ((params.length % 2) == 1) { len = params.length - 1; diff --git a/src/main/java/ghast/XLog.java b/src/main/java/ghast/XLog.java index b0f83fb..07b08cb 100644 --- a/src/main/java/ghast/XLog.java +++ b/src/main/java/ghast/XLog.java @@ -6,8 +6,12 @@ import java.util.logging.Level; import static java.text.MessageFormat.format; +/** + * @deprecated use {@link ghast.logger.BukkitLogger} + */ @UtilityClass @SuppressWarnings("unused") +@Deprecated public class XLog { //region Debug diff --git a/src/main/java/ghast/command/CommandManager.java b/src/main/java/ghast/command/CommandManager.java index b4c1c3a..2dc3656 100644 --- a/src/main/java/ghast/command/CommandManager.java +++ b/src/main/java/ghast/command/CommandManager.java @@ -4,8 +4,8 @@ import lombok.AccessLevel; import lombok.RequiredArgsConstructor; import lombok.experimental.UtilityClass; import org.bukkit.Bukkit; -import org.bukkit.command.Command; -import ru.dmitriymx.reflection.ReflectionObject; + +import static org.joor.Reflect.on; @UtilityClass @SuppressWarnings("unused") @@ -60,12 +60,10 @@ public class CommandManager { public void register() { //TODO для Paper такие "извращения" не требуются. Нужно продумать. - new ReflectionObject(Bukkit.getServer()) - .method("getCommandMap").invoke() - .method("register", String.class, Command.class).invoke( - name, new CommandWrapper(name, this.onlyPlayer, this.deniedMessage, - this.executer, this.errorConsumer) - ); + on(Bukkit.getServer()) + .call("getCommandMap") + .call("register", name, new CommandWrapper(name, this.onlyPlayer, this.deniedMessage, + this.executer, this.errorConsumer)); } } } diff --git a/src/main/java/ghast/logger/BukkitLogger.java b/src/main/java/ghast/logger/BukkitLogger.java new file mode 100644 index 0000000..e75728f --- /dev/null +++ b/src/main/java/ghast/logger/BukkitLogger.java @@ -0,0 +1,42 @@ +package ghast.logger; + +import lombok.RequiredArgsConstructor; + +import java.util.logging.Level; +import java.util.logging.Logger; + +@RequiredArgsConstructor +public class BukkitLogger extends LoggerAdapter { + + private final Logger originallLogger; + + @Override + public void debug(String message) { + originallLogger.log(Level.CONFIG, message); + } + + @Override + public void debug(String message, Throwable throwable) { + originallLogger.log(Level.CONFIG, message, throwable); + } + + @Override + public void info(String message) { + originallLogger.log(Level.INFO, message); + } + + @Override + public void warn(String message) { + originallLogger.log(Level.WARNING, message); + } + + @Override + public void error(String message) { + originallLogger.log(Level.SEVERE, message); + } + + @Override + public void error(String message, Throwable throwable) { + originallLogger.log(Level.SEVERE, message, throwable); + } +} diff --git a/src/main/java/ghast/logger/FormattingPair.java b/src/main/java/ghast/logger/FormattingPair.java new file mode 100644 index 0000000..26283e2 --- /dev/null +++ b/src/main/java/ghast/logger/FormattingPair.java @@ -0,0 +1,17 @@ +package ghast.logger; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Getter +public class FormattingPair { + + private final String message; + private final Throwable throwable; + + public FormattingPair(String message) { + this.message = message; + this.throwable = null; + } +} diff --git a/src/main/java/ghast/logger/LoggerAdapter.java b/src/main/java/ghast/logger/LoggerAdapter.java new file mode 100644 index 0000000..03c4685 --- /dev/null +++ b/src/main/java/ghast/logger/LoggerAdapter.java @@ -0,0 +1,37 @@ +package ghast.logger; + +public abstract class LoggerAdapter { + + public abstract void debug(String message); + public abstract void debug(String message, Throwable throwable); + public abstract void info(String message); + public abstract void warn(String message); + public abstract void error(String message); + public abstract void error(String message, Throwable throwable); + + public void debug(String pattern, Object... objects) { + FormattingPair formattingPair = LoggerFormatter.arrayFormat(pattern, objects); + if (formattingPair.getThrowable() != null) { + debug(formattingPair.getMessage(), formattingPair.getThrowable()); + } else { + debug(formattingPair.getMessage()); + } + } + + public void info(String pattern, Object... objects) { + info(StringFormatter.arrayFormat(pattern, objects)); + } + + public void warn(String pattern, Object... objects) { + warn(StringFormatter.arrayFormat(pattern, objects)); + } + + public void error(String pattern, Object... objects) { + FormattingPair formattingPair = LoggerFormatter.arrayFormat(pattern, objects); + if (formattingPair.getThrowable() != null) { + error(formattingPair.getMessage(), formattingPair.getThrowable()); + } else { + error(formattingPair.getMessage()); + } + } +} diff --git a/src/main/java/ghast/logger/LoggerFormatter.java b/src/main/java/ghast/logger/LoggerFormatter.java new file mode 100644 index 0000000..08ad31f --- /dev/null +++ b/src/main/java/ghast/logger/LoggerFormatter.java @@ -0,0 +1,55 @@ +package ghast.logger; + +/** + * Copy-Paste from org.slf4j.helpers.MessageFormatter + */ +public final class LoggerFormatter { + + public static FormattingPair arrayFormat(String messagePattern, Object[] argArray) { + Object[] args; + Throwable throwableCandidate = getThrowableCandidate(argArray); + if (throwableCandidate != null) { + args = trimmedCopy(argArray); + } else { + args = argArray; + } + + return arrayFormat(messagePattern, args, throwableCandidate); + } + + public static FormattingPair arrayFormat(String messagePattern, Object[] argArray, Throwable throwable) { + if (messagePattern == null) { + return new FormattingPair(null, throwable); + } + + if (argArray == null) { + return new FormattingPair(messagePattern); + } + + return new FormattingPair(StringFormatter.arrayFormat(messagePattern, argArray), throwable); + } + + private static Throwable getThrowableCandidate(Object[] argArray) { + if (argArray == null || argArray.length == 0) { + return null; + } + + Object lastEntry = argArray[argArray.length - 1]; + if (lastEntry instanceof Throwable) { + return (Throwable) lastEntry; + } + + return null; + } + + private static Object[] trimmedCopy(final Object[] argArray) { + int trimmedLen = argArray.length - 1; + Object[] trimmed = new Object[trimmedLen]; + + if (trimmedLen > 0) { + System.arraycopy(argArray, 0, trimmed, 0, trimmedLen); + } + + return trimmed; + } +} diff --git a/src/main/java/ghast/logger/StringFormatter.java b/src/main/java/ghast/logger/StringFormatter.java new file mode 100644 index 0000000..1e1f530 --- /dev/null +++ b/src/main/java/ghast/logger/StringFormatter.java @@ -0,0 +1,240 @@ +package ghast.logger; + +import java.util.HashMap; +import java.util.Map; + +/** + * Copy-Paste from org.slf4j.helpers.MessageFormatter + */ +public final class StringFormatter { + private static final String EMPTY = ""; + private static final char DELIM_START = '{'; + private static final String DELIM_STR = "{}"; + private static final char ESCAPE_CHAR = '\\'; + + public static String arrayFormat(String messagePattern, Object[] argArray) { + if (messagePattern == null || messagePattern.equals(EMPTY)) { + return EMPTY; + } else if (argArray == null) { + return messagePattern; + } + + StringBuilder sb = new StringBuilder(messagePattern.length() + 50); + + int k = 0; + for (int i = 0; i < argArray.length; i++) { + int idx = messagePattern.indexOf(DELIM_STR, k); + + if (idx == -1) { + // no more variables + if (k == 0) { // this is a simple string + return messagePattern; + } else { // add the tail string which contains no variables and return + // the result. + sb.append(messagePattern, k, messagePattern.length()); + return sb.toString(); + } + } else { + if (isEscapedDelimeter(messagePattern, idx)) { + if (!isDoubleEscaped(messagePattern, idx)) { + i--; // DELIM_START was escaped, thus should not be incremented + sb.append(messagePattern, k, idx - 1); + sb.append(DELIM_START); + k = idx + 1; + } else { + // The escape character preceding the delimiter start is + // itself escaped: "abc x:\\{}" + // we have to consume one backward slash + sb.append(messagePattern, k, idx - 1); + deeplyAppendParameter(sb, argArray[i], new HashMap<>()); + k = idx + 2; + } + } else { + sb.append(messagePattern, k, idx); + deeplyAppendParameter(sb, argArray[i], new HashMap<>()); + k = idx + 2; + } + } + } + // append the characters following the last {} pair. + sb.append(messagePattern, k, messagePattern.length()); + return sb.toString(); + } + + private static boolean isEscapedDelimeter(String messagePattern, int delimeterStartIndex) { + if (delimeterStartIndex == 0) { + return false; + } + char potentialEscape = messagePattern.charAt(delimeterStartIndex - 1); + return potentialEscape == ESCAPE_CHAR; + } + + private static boolean isDoubleEscaped(String messagePattern, int delimeterStartIndex) { + return delimeterStartIndex >= 2 && messagePattern.charAt(delimeterStartIndex - 2) == ESCAPE_CHAR; + } + + // special treatment of array values was suggested by 'lizongbo' + private static void deeplyAppendParameter(StringBuilder sbuf, Object o, Map seenMap) { + if (o == null) { + sbuf.append("null"); + return; + } + if (!o.getClass().isArray()) { + safeObjectAppend(sbuf, o); + } else { + // check for primitive array types because they + // unfortunately cannot be cast to Object[] + if (o instanceof boolean[]) { + booleanArrayAppend(sbuf, (boolean[]) o); + } else if (o instanceof byte[]) { + byteArrayAppend(sbuf, (byte[]) o); + } else if (o instanceof char[]) { + charArrayAppend(sbuf, (char[]) o); + } else if (o instanceof short[]) { + shortArrayAppend(sbuf, (short[]) o); + } else if (o instanceof int[]) { + intArrayAppend(sbuf, (int[]) o); + } else if (o instanceof long[]) { + longArrayAppend(sbuf, (long[]) o); + } else if (o instanceof float[]) { + floatArrayAppend(sbuf, (float[]) o); + } else if (o instanceof double[]) { + doubleArrayAppend(sbuf, (double[]) o); + } else { + objectArrayAppend(sbuf, (Object[]) o, seenMap); + } + } + } + + private static void safeObjectAppend(StringBuilder sbuf, Object o) { + try { + String oAsString = o.toString(); + sbuf.append(oAsString); + } catch (Throwable t) { + throw new RuntimeException("Failed toString() invocation on an object of type [" + o.getClass().getName() + "]", t); + } + } + + @SuppressWarnings("DuplicatedCode") + private static void booleanArrayAppend(StringBuilder sbuf, boolean[] a) { + sbuf.append('['); + int len = a.length; + for (int i = 0; i < len; i++) { + sbuf.append(a[i]); + if (i != len - 1) { + sbuf.append(", "); + } + } + sbuf.append(']'); + } + + @SuppressWarnings("DuplicatedCode") + private static void byteArrayAppend(StringBuilder sbuf, byte[] a) { + sbuf.append('['); + int len = a.length; + for (int i = 0; i < len; i++) { + sbuf.append(a[i]); + if (i != len - 1) { + sbuf.append(", "); + } + } + sbuf.append(']'); + } + + @SuppressWarnings("DuplicatedCode") + private static void charArrayAppend(StringBuilder sbuf, char[] a) { + sbuf.append('['); + int len = a.length; + for (int i = 0; i < len; i++) { + sbuf.append(a[i]); + if (i != len - 1) { + sbuf.append(", "); + } + } + sbuf.append(']'); + } + + @SuppressWarnings("DuplicatedCode") + private static void shortArrayAppend(StringBuilder sbuf, short[] a) { + sbuf.append('['); + int len = a.length; + for (int i = 0; i < len; i++) { + sbuf.append(a[i]); + if (i != len - 1) { + sbuf.append(", "); + } + } + sbuf.append(']'); + } + + @SuppressWarnings("DuplicatedCode") + private static void intArrayAppend(StringBuilder sbuf, int[] a) { + sbuf.append('['); + int len = a.length; + for (int i = 0; i < len; i++) { + sbuf.append(a[i]); + if (i != len - 1) { + sbuf.append(", "); + } + } + sbuf.append(']'); + } + + @SuppressWarnings("DuplicatedCode") + private static void longArrayAppend(StringBuilder sbuf, long[] a) { + sbuf.append('['); + int len = a.length; + for (int i = 0; i < len; i++) { + sbuf.append(a[i]); + if (i != len - 1) { + sbuf.append(", "); + } + } + sbuf.append(']'); + } + + @SuppressWarnings("DuplicatedCode") + private static void floatArrayAppend(StringBuilder sbuf, float[] a) { + sbuf.append('['); + int len = a.length; + for (int i = 0; i < len; i++) { + sbuf.append(a[i]); + if (i != len - 1) { + sbuf.append(", "); + } + } + sbuf.append(']'); + } + + @SuppressWarnings("DuplicatedCode") + private static void doubleArrayAppend(StringBuilder sbuf, double[] a) { + sbuf.append('['); + int len = a.length; + for (int i = 0; i < len; i++) { + sbuf.append(a[i]); + if (i != len - 1) { + sbuf.append(", "); + } + } + sbuf.append(']'); + } + + private static void objectArrayAppend(StringBuilder sbuf, Object[] a, Map seenMap) { + sbuf.append('['); + if (!seenMap.containsKey(a)) { + seenMap.put(a, null); + int len = a.length; + for (int i = 0; i < len; i++) { + deeplyAppendParameter(sbuf, a[i], seenMap); + if (i != len - 1) { + sbuf.append(", "); + } + } + // allow repeats in siblings + seenMap.remove(a); + } else { + sbuf.append("..."); + } + sbuf.append(']'); + } +} diff --git a/src/test/java/ghast/HashMapsTest.java b/src/test/java/ghast/HashMapsTest.java new file mode 100644 index 0000000..92d5e3e --- /dev/null +++ b/src/test/java/ghast/HashMapsTest.java @@ -0,0 +1,182 @@ +package ghast; + +import org.junit.jupiter.api.Test; + +import java.util.HashMap; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; + +class HashMapsTest { + + @Test + void of0() { + Map map = HashMaps.of(); + assertNotNull(map); + assertTrue(map instanceof HashMap); + assertTrue(map.isEmpty()); + } + + @Test + void of1() { + Map map = HashMaps.of("key1", "val1"); + assertNotNull(map); + assertTrue(map instanceof HashMap); + assertEquals(1, map.size()); + } + + @Test + void of2() { + Map map = HashMaps.of( + "key1", "val1", + "key2", "val2" + ); + assertNotNull(map); + assertTrue(map instanceof HashMap); + assertEquals(2, map.size()); + } + + @Test + void of3() { + Map map = HashMaps.of( + "key1", "val1", + "key2", "val2", + "key3", "val3" + ); + assertNotNull(map); + assertTrue(map instanceof HashMap); + assertEquals(3, map.size()); + } + + @Test + void of4() { + Map map = HashMaps.of( + "key1", "val1", + "key2", "val2", + "key3", "val3", + "key4", "val4" + ); + assertNotNull(map); + assertTrue(map instanceof HashMap); + assertEquals(4, map.size()); + } + + @Test + void of5() { + Map map = HashMaps.of( + "key1", "val1", + "key2", "val2", + "key3", "val3", + "key4", "val4", + "key5", "val5" + ); + assertNotNull(map); + assertTrue(map instanceof HashMap); + assertEquals(5, map.size()); + } + + @Test + void of6() { + Map map = HashMaps.of( + "key1", "val1", + "key2", "val2", + "key3", "val3", + "key4", "val4", + "key5", "val5", + "key6", "val6" + ); + assertNotNull(map); + assertTrue(map instanceof HashMap); + assertEquals(6, map.size()); + } + + @Test + void of7() { + Map map = HashMaps.of( + "key1", "val1", + "key2", "val2", + "key3", "val3", + "key4", "val4", + "key5", "val5", + "key6", "val6", + "key7", "val7" + ); + assertNotNull(map); + assertTrue(map instanceof HashMap); + assertEquals(7, map.size()); + } + + @Test + void of8() { + Map map = HashMaps.of( + "key1", "val1", + "key2", "val2", + "key3", "val3", + "key4", "val4", + "key5", "val5", + "key6", "val6", + "key7", "val7", + "key8", "val8" + ); + assertNotNull(map); + assertTrue(map instanceof HashMap); + assertEquals(8, map.size()); + } + + @Test + void of9() { + Map map = HashMaps.of( + "key1", "val1", + "key2", "val2", + "key3", "val3", + "key4", "val4", + "key5", "val5", + "key6", "val6", + "key7", "val7", + "key8", "val8", + "key9", "val9" + ); + assertNotNull(map); + assertTrue(map instanceof HashMap); + assertEquals(9, map.size()); + } + + @Test + void of10() { + Map map = HashMaps.of( + "key1", "val1", + "key2", "val2", + "key3", "val3", + "key4", "val4", + "key5", "val5", + "key6", "val6", + "key7", "val7", + "key8", "val8", + "key9", "val9", + "key10", "val10" + ); + assertNotNull(map); + assertTrue(map instanceof HashMap); + assertEquals(10, map.size()); + } + + @Test + void ofEntries() { + Map map = HashMaps.ofEntries( + HashMaps.entry("key1", "val1"), + HashMaps.entry("key2", "val2"), + HashMaps.entry("key3", "val3"), + HashMaps.entry("key4", "val4"), + HashMaps.entry("key5", "val5"), + HashMaps.entry("key6", "val6"), + HashMaps.entry("key7", "val7"), + HashMaps.entry("key8", "val8"), + HashMaps.entry("key9", "val9"), + HashMaps.entry("key10", "val10"), + HashMaps.entry("key11", "val11") + ); + assertNotNull(map); + assertTrue(map instanceof HashMap); + assertEquals(11, map.size()); + } +} \ No newline at end of file diff --git a/src/test/java/ghast/MessagesLoadTest.java b/src/test/java/ghast/MessagesLoadTest.java new file mode 100644 index 0000000..5f6cec1 --- /dev/null +++ b/src/test/java/ghast/MessagesLoadTest.java @@ -0,0 +1,59 @@ +package ghast; + +import org.joor.Reflect; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.StringReader; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; +import java.util.stream.Collectors; + +import static org.joor.Reflect.onClass; +import static org.junit.jupiter.api.Assertions.assertIterableEquals; + +class MessagesLoadTest { + + Map map; + + @BeforeEach + void before() { + map = new HashMap() {{ + put("key1", "value1"); + put("key2", "value2"); + }}; + } + + @Test + void loadMap() { + Messages.load(map); + + Reflect reflectMessagesMap = onClass(Messages.class).field("MESSAGES_MAP"); + assertIterableEquals(map.entrySet(), reflectMessagesMap.as(Map.class).entrySet()); + } + + @Test + void loadReader() { + String lines = map.entrySet().stream() + .map(entry -> entry.getKey() + "=" + entry.getValue()) + .collect(Collectors.joining("\n")); + StringReader reader = new StringReader(lines); + + Messages.load(reader); + + Reflect reflectMessagesMap = onClass(Messages.class).field("MESSAGES_MAP"); + assertIterableEquals(map.entrySet(), reflectMessagesMap.as(Map.class).entrySet()); + } + + @Test + void loadProperties() { + Properties properties = new Properties(); + properties.putAll(map); + + Messages.load(properties); + + Reflect reflectMessagesMap = onClass(Messages.class).field("MESSAGES_MAP"); + assertIterableEquals(map.entrySet(), reflectMessagesMap.as(Map.class).entrySet()); + } +} \ No newline at end of file diff --git a/src/test/java/ghast/MessagesTest.java b/src/test/java/ghast/MessagesTest.java new file mode 100644 index 0000000..7b8a43a --- /dev/null +++ b/src/test/java/ghast/MessagesTest.java @@ -0,0 +1,91 @@ +package ghast; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.HashMap; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class MessagesTest { + + @BeforeEach + void before() { + Map map1 = new HashMap() {{ + put("simple-message", "some message"); + put("parametrized-message", "Arg1: {key1}"); + }}; + Messages.load(map1); + } + + @Test + void getSimpleMessage() { + String string = Messages.get("simple-message"); + assertEquals("some message", string); + } + + @Test + void getSimpleMessageNotFound() { + String string = Messages.get("non-exists-key"); + assertEquals("non-exists-key", string); + } + + @Test + void formatMap() { + Map map1 = new HashMap() {{ + put("key1", "some message"); + }}; + String string = Messages.format("Arg1: {key1}", map1); + assertEquals("Arg1: some message", string); + + Map map2 = new HashMap() {{ + put("key1", "some message"); + }}; + string = Messages.format("Arg1: {key1}", map2); + assertEquals("Arg1: some message", string); + } + + @Test + void formatArray() { + String string = Messages.format("Arg1: {key1}", "key1", "some message"); + assertEquals("Arg1: some message", string); + } + + @Test + void getParametrizedMessageMap() { + Map map2 = new HashMap() {{ + put("key1", "some message"); + }}; + String string = Messages.get("parametrized-message", map2); + assertEquals("Arg1: some message", string); + + Map map3 = new HashMap() {{ + put("key1", "some message"); + }}; + string = Messages.get("parametrized-message", map3); + assertEquals("Arg1: some message", string); + } + + @Test + void getParametrizedMessageMapNotFound() { + Map map2 = new HashMap() {{ + put("key1", "some message"); + }}; + + String string = Messages.get("non-exists-key", map2); + assertEquals("non-exists-key", string); + } + + @Test + void getParametrizedMessageArray() { + String string = Messages.get("parametrized-message", "key1", "some message"); + assertEquals("Arg1: some message", string); + } + + @Test + void getParametrizedMessageArrayNotFound() { + String string = Messages.get("non-exists-key", "key1", "some message"); + assertEquals("non-exists-key", string); + } +} \ No newline at end of file diff --git a/src/test/java/ghast/logger/BukkitLoggerTest.java b/src/test/java/ghast/logger/BukkitLoggerTest.java new file mode 100644 index 0000000..8e599e8 --- /dev/null +++ b/src/test/java/ghast/logger/BukkitLoggerTest.java @@ -0,0 +1,54 @@ +package ghast.logger; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.logging.Level; +import java.util.logging.Logger; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +class BukkitLoggerTest { + + Logger logger; + BukkitLogger bukkitLogger; + + @BeforeEach + void before() { + logger = mock(Logger.class); + bukkitLogger = new BukkitLogger(logger); + } + + @Test + void debug() { + bukkitLogger.debug("Some String"); + verify(logger).log(Level.CONFIG, "Some String"); + + Exception exception = new Exception("oops!"); + bukkitLogger.debug("Some String", exception); + verify(logger).log(Level.CONFIG, "Some String", exception); + } + + @Test + void info() { + bukkitLogger.info("some message"); + verify(logger).log(Level.INFO, "some message"); + } + + @Test + void warn() { + bukkitLogger.warn("some message"); + verify(logger).log(Level.WARNING, "some message"); + } + + @Test + void error() { + bukkitLogger.error("some message"); + verify(logger).log(Level.SEVERE, "some message"); + + Exception exception = new Exception("oops!"); + bukkitLogger.error("Some String", exception); + verify(logger).log(Level.SEVERE, "Some String", exception); + } +} \ No newline at end of file diff --git a/src/test/java/ghast/logger/LoggerAdapterTest.java b/src/test/java/ghast/logger/LoggerAdapterTest.java new file mode 100644 index 0000000..00eccd2 --- /dev/null +++ b/src/test/java/ghast/logger/LoggerAdapterTest.java @@ -0,0 +1,50 @@ +package ghast.logger; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +class LoggerAdapterTest { + + LoggerAdapter loggerAdapter; + + @BeforeEach + void before() { + loggerAdapter = mock(LoggerAdapter.class, CALLS_REAL_METHODS); + } + + @Test + void debug() { + loggerAdapter.debug("some pattern {}", "item-1"); + verify(loggerAdapter).debug("some pattern item-1"); + + Exception exception = new Exception("oops!"); + loggerAdapter.debug("some pattern {}", "item-1", exception); + verify(loggerAdapter).debug("some pattern item-1", exception); + } + + @Test + void info() { + loggerAdapter.info("some pattern {}", "item-1"); + verify(loggerAdapter).info("some pattern item-1"); + } + + @Test + void warn() { + loggerAdapter.warn("some pattern {}", "item-1"); + verify(loggerAdapter).warn("some pattern item-1"); + } + + @Test + void error() { + loggerAdapter.error("some pattern {}", "item-1"); + verify(loggerAdapter).error("some pattern item-1"); + + Exception exception = new Exception("oops!"); + loggerAdapter.error("some pattern {}", "item-1", exception); + verify(loggerAdapter).error("some pattern item-1", exception); + } +} \ No newline at end of file diff --git a/src/test/java/ghast/logger/LoggerFormatterTest.java b/src/test/java/ghast/logger/LoggerFormatterTest.java new file mode 100644 index 0000000..60fe5c7 --- /dev/null +++ b/src/test/java/ghast/logger/LoggerFormatterTest.java @@ -0,0 +1,84 @@ +package ghast.logger; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class LoggerFormatterTest { + + @Test + void arrayFormatNullPattern() { + Exception exception = new Exception("oops!"); + FormattingPair formattingPair = LoggerFormatter.arrayFormat(null, null, exception); + + assertNotNull(formattingPair); + assertNull(formattingPair.getMessage()); + assertNotNull(formattingPair.getThrowable()); + assertEquals(exception, formattingPair.getThrowable()); + + formattingPair = LoggerFormatter.arrayFormat(null, null, null); + assertNotNull(formattingPair); + assertNull(formattingPair.getMessage()); + assertNull(formattingPair.getThrowable()); + } + + @Test + void arrayFormatNullArgs() { + FormattingPair formattingPair = LoggerFormatter.arrayFormat("some pattern", null, null); + + assertNotNull(formattingPair); + assertNotNull(formattingPair.getMessage()); + assertEquals("some pattern", formattingPair.getMessage()); + assertNull(formattingPair.getThrowable()); + + formattingPair = LoggerFormatter.arrayFormat("some pattern {}", null, null); + assertNotNull(formattingPair); + assertNotNull(formattingPair.getMessage()); + assertEquals("some pattern {}", formattingPair.getMessage()); + assertNull(formattingPair.getThrowable()); + } + + @Test + void arrayFormat() { + Exception exception = new Exception("oops!"); + FormattingPair formattingPair = LoggerFormatter.arrayFormat("some pattern {}", new Object[]{"item-1"}, exception); + + assertNotNull(formattingPair); + assertNotNull(formattingPair.getMessage()); + assertEquals("some pattern item-1", formattingPair.getMessage()); + assertNotNull(formattingPair.getThrowable()); + assertEquals(exception, formattingPair.getThrowable()); + } + + @Test + void arrayFormatWithoutThrowable() { + FormattingPair formattingPair = LoggerFormatter.arrayFormat("Arg1: {}", new Object[]{ "item-1" }); + + assertNotNull(formattingPair); + assertNotNull(formattingPair.getMessage()); + assertEquals("Arg1: item-1", formattingPair.getMessage()); + assertNull(formattingPair.getThrowable()); + } + + @Test + void arrayFormatWithoutThrowableNullArgs() { + FormattingPair formattingPair = LoggerFormatter.arrayFormat("Arg1: {}", null); + + assertNotNull(formattingPair); + assertNotNull(formattingPair.getMessage()); + assertEquals("Arg1: {}", formattingPair.getMessage()); + assertNull(formattingPair.getThrowable()); + } + + @Test + void arrayFormatThrowableInArgs() { + Exception exception = new Exception("oops!"); + FormattingPair formattingPair = LoggerFormatter.arrayFormat("Arg1: {}", new Object[]{ "item-1", exception }); + + assertNotNull(formattingPair); + assertNotNull(formattingPair.getMessage()); + assertEquals("Arg1: item-1", formattingPair.getMessage()); + assertNotNull(formattingPair.getThrowable()); + assertEquals(exception, formattingPair.getThrowable()); + } +} \ No newline at end of file diff --git a/src/test/java/ghast/logger/StringFormatterTest.java b/src/test/java/ghast/logger/StringFormatterTest.java new file mode 100644 index 0000000..33ed046 --- /dev/null +++ b/src/test/java/ghast/logger/StringFormatterTest.java @@ -0,0 +1,95 @@ +package ghast.logger; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class StringFormatterTest { + + @Test + void emptyPattern() { + String actual = StringFormatter.arrayFormat(null, null); + assertEquals("", actual); + + actual = StringFormatter.arrayFormat("", null); + assertEquals("", actual); + } + + @Test + void nullArgArray() { + String pattern = "some pattern"; + String actual = StringFormatter.arrayFormat(pattern, null); + assertEquals(pattern, actual); + + pattern = "some pattern {}"; + actual = StringFormatter.arrayFormat(pattern, null); + assertEquals(pattern, actual); + } + + @Test + void dummyPattern() { + String actual = StringFormatter.arrayFormat("dummy pattern", new Object[]{"argument"}); + assertEquals("dummy pattern", actual); + } + + @Test + void escapePattern() { + String actual = StringFormatter.arrayFormat("Arg1: \\{}", new Object[]{"item1"}); + assertEquals("Arg1: {}", actual); + + actual = StringFormatter.arrayFormat("Arg1: {}, \\{}", new Object[]{"item1"}); + assertEquals("Arg1: item1, \\{}", actual); + } + + @Test + void simpleArg() { + String actual; + + actual = StringFormatter.arrayFormat("Arg1: {}", new Object[]{"item1"}); + assertEquals("Arg1: item1", actual); + + actual = StringFormatter.arrayFormat("Arg1: {}", new Object[]{11}); + assertEquals("Arg1: 11", actual); + + actual = StringFormatter.arrayFormat("Arg1: {}", new Object[]{11.5f}); + assertEquals("Arg1: 11.5", actual); + } + + @Test + void nullArg() { + String actual = StringFormatter.arrayFormat("Arg1: {}", new Object[]{null}); + assertEquals("Arg1: null", actual); + } + + @Test + void arrayArg() { + String actual; + + actual = StringFormatter.arrayFormat("Arg1: {}", new Object[]{new boolean[]{false, true}}); + assertEquals("Arg1: [false, true]", actual); + + actual = StringFormatter.arrayFormat("Arg1: {}", new Object[]{new byte[]{0b00, 0b01}}); + assertEquals("Arg1: [0, 1]", actual); + + actual = StringFormatter.arrayFormat("Arg1: {}", new Object[]{new char[]{'c', 'h'}}); + assertEquals("Arg1: [c, h]", actual); + + actual = StringFormatter.arrayFormat("Arg1: {}", new Object[]{new short[]{11, 12}}); + assertEquals("Arg1: [11, 12]", actual); + + actual = StringFormatter.arrayFormat("Arg1: {}", new Object[]{new int[]{11, 12}}); + assertEquals("Arg1: [11, 12]", actual); + + actual = StringFormatter.arrayFormat("Arg1: {}", new Object[]{new long[]{11L, 12L}}); + assertEquals("Arg1: [11, 12]", actual); + + actual = StringFormatter.arrayFormat("Arg1: {}", new Object[]{new float[]{11.2f, 12.3f}}); + assertEquals("Arg1: [11.2, 12.3]", actual); + + actual = StringFormatter.arrayFormat("Arg1: {}", new Object[]{new double[]{11.2d, 12.3d}}); + assertEquals("Arg1: [11.2, 12.3]", actual); + + actual = StringFormatter.arrayFormat("Arg1: {}", new Object[]{new String[]{"str-arr-1", "str-arr-2"}}); + assertEquals("Arg1: [str-arr-1, str-arr-2]", actual); + } +} \ No newline at end of file