#1874 Create hierarchical values property and migrate log level setting to it

This commit is contained in:
ljacqu 2019-12-08 19:37:45 +01:00
parent e1106a7eb0
commit dc770687db
14 changed files with 350 additions and 44 deletions

View File

@ -83,7 +83,7 @@ public final class ConsoleLogger {
* @param settings the settings to read from
*/
public void initializeSettings(Settings settings) {
this.logLevel = settings.getProperty(PluginSettings.LOG_LEVEL);
this.logLevel = settings.getProperty(PluginSettings.LOG_LEVEL).getValue(name);
}
public LogLevel getLogLevel() {

View File

@ -5,7 +5,6 @@ import fr.xephi.authme.output.ConsoleLoggerFactory;
import fr.xephi.authme.output.LogLevel;
import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.settings.properties.EmailSettings;
import fr.xephi.authme.settings.properties.PluginSettings;
import fr.xephi.authme.util.StringUtils;
import org.apache.commons.mail.EmailConstants;
import org.apache.commons.mail.EmailException;
@ -69,7 +68,7 @@ public class SendMailSsl {
email.setFrom(senderMail, senderName);
email.setSubject(settings.getProperty(EmailSettings.RECOVERY_MAIL_SUBJECT));
email.setAuthentication(settings.getProperty(EmailSettings.MAIL_ACCOUNT), mailPassword);
if (settings.getProperty(PluginSettings.LOG_LEVEL).includes(LogLevel.DEBUG)) {
if (logger.getLogLevel().includes(LogLevel.DEBUG)) {
email.setDebug(true);
}

View File

@ -24,7 +24,8 @@ public final class ConsoleLoggerFactory {
* @return logger for the given class
*/
public static ConsoleLogger get(Class<?> owningClass) {
String name = owningClass.getCanonicalName();
// Remove technical package start, so log levels are configured just with "authme", e.g. as "authme.events.Foo"
String name = owningClass.getCanonicalName().replace("fr.xephi.", "");
return consoleLoggers.computeIfAbsent(name, ConsoleLoggerFactory::createLogger);
}

View File

@ -4,6 +4,7 @@ import ch.jalu.configme.configurationdata.ConfigurationData;
import ch.jalu.configme.migration.PlainMigrationService;
import ch.jalu.configme.properties.Property;
import ch.jalu.configme.resource.PropertyReader;
import com.google.common.collect.ImmutableMap;
import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.initialization.DataFolder;
import fr.xephi.authme.output.ConsoleLoggerFactory;
@ -11,6 +12,7 @@ import fr.xephi.authme.output.LogLevel;
import fr.xephi.authme.process.register.RegisterSecondaryArgument;
import fr.xephi.authme.process.register.RegistrationType;
import fr.xephi.authme.security.HashAlgorithm;
import fr.xephi.authme.settings.hierarchicalvalues.HierarchicalValues;
import fr.xephi.authme.settings.properties.DatabaseSettings;
import fr.xephi.authme.settings.properties.PluginSettings;
import fr.xephi.authme.settings.properties.RegistrationSettings;
@ -22,7 +24,6 @@ import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import static ch.jalu.configme.properties.PropertyInitializer.newListProperty;
@ -73,13 +74,13 @@ public class SettingsMigrationService extends PlainMigrationService {
| migrateJoinLeaveMessages(reader, configurationData)
| migrateForceSpawnSettings(reader, configurationData)
| migratePoolSizeSetting(reader, configurationData)
| changeBooleanSettingToLogLevelProperty(reader, configurationData)
| hasOldHelpHeaderProperty(reader)
| hasSupportOldPasswordProperty(reader)
| convertToRegistrationType(reader, configurationData)
| mergeAndMovePermissionGroupSettings(reader, configurationData)
| moveDeprecatedHashAlgorithmIntoLegacySection(reader, configurationData)
| moveSaltColumnConfigWithOtherColumnConfigs(reader, configurationData)
| migrateLogLevelProperty(reader, configurationData)
|| hasDeprecatedProperties(reader);
}
@ -202,28 +203,6 @@ public class SettingsMigrationService extends PlainMigrationService {
return true;
}
/**
* Changes the old boolean property "hide spam from console" to the new property specifying
* the log level.
*
* @param reader The property reader
* @param configData Configuration data
* @return True if the configuration has changed, false otherwise
*/
private static boolean changeBooleanSettingToLogLevelProperty(PropertyReader reader,
ConfigurationData configData) {
final String oldPath = "Security.console.noConsoleSpam";
final Property<LogLevel> newProperty = PluginSettings.LOG_LEVEL;
if (!newProperty.isPresent(reader) && reader.contains(oldPath)) {
logger.info("Moving '" + oldPath + "' to '" + newProperty.getPath() + "'");
boolean oldValue = Optional.ofNullable(reader.getBoolean(oldPath)).orElse(false);
LogLevel level = oldValue ? LogLevel.INFO : LogLevel.FINE;
configData.setValue(newProperty, level);
return true;
}
return false;
}
private static boolean hasOldHelpHeaderProperty(PropertyReader reader) {
if (reader.contains("settings.helpHeader")) {
logger.warning("Help header setting is now in messages/help_xx.yml, "
@ -342,6 +321,33 @@ public class SettingsMigrationService extends PlainMigrationService {
return moveProperty(oldProperty, DatabaseSettings.MYSQL_COL_SALT, reader, configData);
}
/**
* Migrates the old single-value log level property to the hierarchical values structure, setting the old value
* for the root, "authme". Also ensures that an entry is always present for the "authme" key.
*
* @param reader the reader
* @param configData the configuration data
* @return true if the configuration has changed, false otherwise
*/
private static boolean migrateLogLevelProperty(PropertyReader reader, ConfigurationData configData) {
LogLevel defaultRootValue = PluginSettings.LOG_LEVEL.getRootValueFromDefault();
if (!reader.contains(PluginSettings.LOG_LEVEL.getPath())) {
Property<LogLevel> oldProperty = newProperty(LogLevel.class, "settings.logLevel", defaultRootValue);
LogLevel oldLogLevel = oldProperty.determineValue(reader);
configData.setValue(PluginSettings.LOG_LEVEL,
HierarchicalValues.createContainer(defaultRootValue, ImmutableMap.of("authme", oldLogLevel)));
return true;
} else {
// Ensure there is an entry for "authme"
HierarchicalValues<LogLevel> logLevelProperty = configData.getValue(PluginSettings.LOG_LEVEL);
if (!logLevelProperty.hasSpecificValueForKey("authme")) {
logLevelProperty.addValue("authme", defaultRootValue);
return true;
}
}
return false;
}
/**
* Retrieves the old config to run a command when alt accounts are detected and sets them to this instance
* for further processing.

View File

@ -0,0 +1,103 @@
package fr.xephi.authme.settings.hierarchicalvalues;
import fr.xephi.authme.util.StringUtils;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;
/**
* Container for values which are inherited from parents but can be overridden at arbitrary levels.
* Never returns null for any {@link #getValue value lookup} if the fallback provided on initialization
* is not null.
*
* @param <T> the value type
*/
public class HierarchicalValues<T> {
private final Map<String, T> values;
private boolean isRootAbsent;
private HierarchicalValues(Map<String, T> values, boolean isRootAbsent) {
this.values = values;
this.isRootAbsent = isRootAbsent;
}
public static <T> HierarchicalValues<T> createContainerWithRoot(T value) {
Map<String, T> values = new HashMap<>();
values.put("", value);
return new HierarchicalValues<>(values, false);
}
public static <T> HierarchicalValues<T> createContainer(T rootValueFallback, Map<String, T> values) {
Map<String, T> valuesInternal = new HashMap<>(values);
boolean isRootAbsent = false;
if (valuesInternal.get("") == null) {
valuesInternal.put("", rootValueFallback);
isRootAbsent = true;
}
return new HierarchicalValues<>(valuesInternal, isRootAbsent);
}
/**
* Returns the most specific value for the given key.
*
* @param key the key whose value should be fetched
* @return value applicable to the key
*/
public T getValue(String key) {
if (StringUtils.isEmpty(key)) {
return values.get("");
}
T value = values.get(key);
if (value == null) {
return getValue(createParentKey(key));
} else {
return value;
}
}
/**
* Adds the given value for the provided key.
*
* @param key the key to add (or replace) the value for
* @param value the value to set
*/
public void addValue(String key, T value) {
values.put(key, value);
if (isRootAbsent && "".equals(key)) {
isRootAbsent = false;
}
}
public boolean hasSpecificValueForKey(String key) {
return values.containsKey(key);
}
/**
* Returns a stream of all the values for which a value was specified.
*
* @return stream of the specific entries
*/
public Stream<Map.Entry<String, T>> createValuesStream() {
if (isRootAbsent) {
return values.entrySet().stream()
.filter(entry -> !entry.getKey().equals(""));
}
return values.entrySet().stream();
}
/**
* Returns the direct parent of the given key, e.g. "authme.settings" for "authme.settings.properties".
*
* @param key the key to create the parent of
* @return the parent key
*/
private static String createParentKey(String key) {
int lastDotIndex = Math.max(key.lastIndexOf('.'), 0);
return key.substring(0, lastDotIndex);
}
}

View File

@ -0,0 +1,60 @@
package fr.xephi.authme.settings.hierarchicalvalues;
import ch.jalu.configme.properties.BaseProperty;
import ch.jalu.configme.properties.types.PropertyType;
import ch.jalu.configme.resource.PropertyReader;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
/**
* Property implementation containing a hierarchical list of values.
*
* @param <T> the value type
*/
public class HierarchicalValuesProperty<T> extends BaseProperty<HierarchicalValues<T>> {
private final PropertyType<T> propertyType;
/**
* Constructor.
*
* @param propertyType the property type to convert the map values with
* @param path the path of the property
* @param defaultValue the root value to use in the default value
*/
public HierarchicalValuesProperty(PropertyType<T> propertyType, String path, T defaultValue) {
super(path, HierarchicalValues.createContainerWithRoot(defaultValue));
this.propertyType = propertyType;
}
@Override
protected HierarchicalValues<T> getFromReader(PropertyReader reader) {
Object obj = reader.getObject(getPath());
if (!(obj instanceof Map<?, ?>)) {
return null;
}
Map<String, T> values = new HashMap<>();
for (Map.Entry<?, ?> entry : ((Map<?, ?>) obj).entrySet()) {
if (entry.getKey() instanceof String) {
T value = propertyType.convert(entry.getValue());
if (value != null) {
values.put((String) entry.getKey(), value);
}
}
}
return HierarchicalValues.createContainer(getRootValueFromDefault(), values);
}
public T getRootValueFromDefault() {
return getDefaultValue().getValue("");
}
@Override
public Object toExportValue(HierarchicalValues<T> rulesContainer) {
return rulesContainer.createValuesStream()
.collect(Collectors.toMap(Map.Entry::getKey, e -> propertyType.toExportValue(e.getValue())));
}
}

View File

@ -4,8 +4,10 @@ import ch.jalu.configme.Comment;
import ch.jalu.configme.SettingsHolder;
import ch.jalu.configme.properties.Property;
import fr.xephi.authme.output.LogLevel;
import fr.xephi.authme.settings.hierarchicalvalues.HierarchicalValuesProperty;
import static ch.jalu.configme.properties.PropertyInitializer.newProperty;
import static ch.jalu.configme.properties.types.EnumPropertyType.of;
public final class PluginSettings implements SettingsHolder {
@ -72,8 +74,8 @@ public final class PluginSettings implements SettingsHolder {
"FINE for some additional detailed ones (like password failed),",
"and DEBUG for debugging"
})
public static final Property<LogLevel> LOG_LEVEL =
newProperty(LogLevel.class, "settings.logLevel", LogLevel.FINE);
public static final HierarchicalValuesProperty<LogLevel> LOG_LEVEL =
new HierarchicalValuesProperty<>(of(LogLevel.class), "settings.logging", LogLevel.FINE);
@Comment({
"By default we schedule async tasks when talking to the database. If you want",

View File

@ -2,6 +2,7 @@ package fr.xephi.authme;
import fr.xephi.authme.output.LogLevel;
import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.settings.hierarchicalvalues.HierarchicalValues;
import fr.xephi.authme.settings.properties.PluginSettings;
import fr.xephi.authme.settings.properties.SecuritySettings;
import org.junit.After;
@ -208,7 +209,7 @@ public class ConsoleLoggerTest {
private static Settings newSettings(boolean logToFile, LogLevel logLevel) {
Settings settings = mock(Settings.class);
given(settings.getProperty(SecuritySettings.USE_LOGGING)).willReturn(logToFile);
given(settings.getProperty(PluginSettings.LOG_LEVEL)).willReturn(logLevel);
given(settings.getProperty(PluginSettings.LOG_LEVEL)).willReturn(HierarchicalValues.createContainerWithRoot(logLevel));
return settings;
}

View File

@ -28,6 +28,7 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import static fr.xephi.authme.settings.hierarchicalvalues.HierarchicalValues.createContainerWithRoot;
import static org.hamcrest.Matchers.containsString;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.doThrow;
@ -73,7 +74,7 @@ public class ReloadCommandTest {
@Before
public void setDefaultSettings() {
// Mock properties retrieved by ConsoleLogger
given(settings.getProperty(PluginSettings.LOG_LEVEL)).willReturn(LogLevel.INFO);
given(settings.getProperty(PluginSettings.LOG_LEVEL)).willReturn(createContainerWithRoot(LogLevel.INFO));
given(settings.getProperty(SecuritySettings.USE_LOGGING)).willReturn(false);
}

View File

@ -4,10 +4,8 @@ import ch.jalu.injector.testing.BeforeInjecting;
import ch.jalu.injector.testing.DelayedInjectionRunner;
import ch.jalu.injector.testing.InjectDelayed;
import fr.xephi.authme.TestHelper;
import fr.xephi.authme.output.LogLevel;
import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.settings.properties.EmailSettings;
import fr.xephi.authme.settings.properties.PluginSettings;
import org.apache.commons.mail.EmailException;
import org.apache.commons.mail.HtmlEmail;
import org.junit.BeforeClass;
@ -17,7 +15,6 @@ import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import java.io.IOException;
import java.util.Properties;
import static org.hamcrest.Matchers.equalTo;
@ -48,10 +45,9 @@ public class SendMailSslTest {
}
@BeforeInjecting
public void initFields() throws IOException {
public void initFields() {
given(settings.getProperty(EmailSettings.MAIL_ACCOUNT)).willReturn("mail@example.org");
given(settings.getProperty(EmailSettings.MAIL_PASSWORD)).willReturn("pass1234");
given(settings.getProperty(PluginSettings.LOG_LEVEL)).willReturn(LogLevel.INFO);
}
@Test
@ -70,7 +66,6 @@ public class SendMailSslTest {
given(settings.getProperty(EmailSettings.MAIL_ACCOUNT)).willReturn(senderAccount);
String senderName = "Server administration";
given(settings.getProperty(EmailSettings.MAIL_SENDER_NAME)).willReturn(senderName);
given(settings.getProperty(PluginSettings.LOG_LEVEL)).willReturn(LogLevel.DEBUG);
// when
HtmlEmail email = sendMailSsl.initializeMail("recipient@example.com");

View File

@ -5,6 +5,7 @@ import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.ReflectionTestUtils;
import fr.xephi.authme.TestHelper;
import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.settings.hierarchicalvalues.HierarchicalValues;
import fr.xephi.authme.settings.properties.PluginSettings;
import fr.xephi.authme.settings.properties.SecuritySettings;
import org.junit.After;
@ -48,9 +49,9 @@ public class ConsoleLoggerFactoryTest {
ConsoleLogger logger = ConsoleLoggerFactory.get(AuthMe.class);
// then
assertThat(logger.getName(), equalTo("fr.xephi.authme.AuthMe"));
assertThat(logger.getName(), equalTo("authme.AuthMe"));
assertThat(logger.getLogLevel(), equalTo(LogLevel.INFO));
assertThat(getConsoleLoggerMap().keySet(), contains("fr.xephi.authme.AuthMe"));
assertThat(getConsoleLoggerMap().keySet(), contains("authme.AuthMe"));
}
@Test
@ -70,7 +71,8 @@ public class ConsoleLoggerFactoryTest {
public void shouldInitializeAccordingToSettings() {
// given
Settings settings = mock(Settings.class);
given(settings.getProperty(PluginSettings.LOG_LEVEL)).willReturn(LogLevel.FINE);
given(settings.getProperty(PluginSettings.LOG_LEVEL))
.willReturn(HierarchicalValues.createContainerWithRoot(LogLevel.FINE));
given(settings.getProperty(SecuritySettings.USE_LOGGING)).willReturn(false);
ConsoleLogger existingLogger = ConsoleLoggerFactory.get(String.class);

View File

@ -4,6 +4,7 @@ import ch.jalu.configme.configurationdata.ConfigurationData;
import ch.jalu.configme.resource.PropertyReader;
import ch.jalu.configme.resource.PropertyResource;
import ch.jalu.configme.resource.YamlFileResource;
import com.google.common.collect.ImmutableMap;
import com.google.common.io.Files;
import fr.xephi.authme.TestHelper;
import fr.xephi.authme.initialization.DataFolder;
@ -11,6 +12,7 @@ import fr.xephi.authme.output.LogLevel;
import fr.xephi.authme.process.register.RegisterSecondaryArgument;
import fr.xephi.authme.process.register.RegistrationType;
import fr.xephi.authme.security.HashAlgorithm;
import fr.xephi.authme.settings.hierarchicalvalues.HierarchicalValues;
import fr.xephi.authme.settings.properties.AuthMeSettingsRetriever;
import org.junit.BeforeClass;
import org.junit.Rule;
@ -22,6 +24,8 @@ import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import static fr.xephi.authme.TestHelper.getJarFile;
import static fr.xephi.authme.settings.properties.DatabaseSettings.MYSQL_COL_SALT;
@ -121,7 +125,6 @@ public class SettingsMigrationServiceTest {
assertThat(settings.getProperty(DELAY_JOIN_MESSAGE), equalTo(true));
assertThat(settings.getProperty(FORCE_SPAWN_LOCATION_AFTER_LOGIN), equalTo(true));
assertThat(settings.getProperty(FORCE_SPAWN_ON_WORLDS), contains("survival", "survival_nether", "creative"));
assertThat(settings.getProperty(LOG_LEVEL), equalTo(LogLevel.INFO));
assertThat(settings.getProperty(REGISTRATION_TYPE), equalTo(RegistrationType.EMAIL));
assertThat(settings.getProperty(REGISTER_SECOND_ARGUMENT), equalTo(RegisterSecondaryArgument.CONFIRMATION));
assertThat(settings.getProperty(ENABLE_PERMISSION_CHECK), equalTo(true));
@ -130,6 +133,7 @@ public class SettingsMigrationServiceTest {
assertThat(settings.getProperty(PASSWORD_HASH), equalTo(HashAlgorithm.SHA256));
assertThat(settings.getProperty(LEGACY_HASHES), contains(HashAlgorithm.PBKDF2, HashAlgorithm.WORDPRESS, HashAlgorithm.SHA512));
assertThat(settings.getProperty(MYSQL_COL_SALT), equalTo("salt_col_name"));
verifyLoggingProperty(settings);
// Check migration of old setting to email.html
assertThat(Files.readLines(new File(dataFolder, "email.html"), StandardCharsets.UTF_8),
@ -138,6 +142,16 @@ public class SettingsMigrationServiceTest {
+ "change password after login! <br /> /changepassword <generatedpass /> newPassword"));
}
private void verifyLoggingProperty(Settings settings) {
HierarchicalValues<LogLevel> logLevelProperty = settings.getProperty(LOG_LEVEL);
assertThat(logLevelProperty.getValue(""), equalTo(LogLevel.FINE));
assertThat(logLevelProperty.getValue("authme"), equalTo(LogLevel.DEBUG));
Map<String, LogLevel> allValues = logLevelProperty.createValuesStream()
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
assertThat(allValues, equalTo(ImmutableMap.of("authme", LogLevel.DEBUG)));
}
private static class TestMigrationServiceExtension extends SettingsMigrationService {
private List<Boolean> returnedValues = new ArrayList<>();

View File

@ -0,0 +1,123 @@
package fr.xephi.authme.settings.hierarchicalvalues;
import com.google.common.collect.ImmutableMap;
import org.junit.Test;
import java.util.Map;
import java.util.stream.Collectors;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat;
/**
* Test for {@link HierarchicalValues}.
*/
public class HierarchicalValuesTest {
@Test
public void shouldCreateContainerWithSingleRootValue() {
// given
int value = 1024;
HierarchicalValues<Integer> container = HierarchicalValues.createContainerWithRoot(value);
// when / then
assertThat(container.getValue(""), equalTo(value));
assertThat(container.getValue(null), equalTo(value));
assertThat(container.getValue("foo"), equalTo(value));
assertThat(container.getValue("foo.bar"), equalTo(value));
assertThat(container.getValue("foo.bar.Baz"), equalTo(value));
}
@Test
public void shouldCreateContainerWithGivenValues() {
// given
Map<String, Double> values = ImmutableMap.of(
"country", 3.0,
"country.europe", 4.2,
"country.europe.switzerland", 4.4,
"country.asia.china", 2.8,
"country.africa", 2.5);
HierarchicalValues<Double> container = HierarchicalValues.createContainer(2.9, values);
// when / then
assertThat(container.getValue(""), equalTo(2.9));
assertThat(container.getValue("country"), equalTo(3.0));
assertThat(container.getValue("country.europe.italy"), equalTo(4.2));
assertThat(container.getValue("country.europe.switzerland"), equalTo(4.4));
assertThat(container.getValue("country.europe.switzerland.zug"), equalTo(4.4));
assertThat(container.getValue("country.asia.japan"), equalTo(3.0));
assertThat(container.getValue("country.africa"), equalTo(2.5));
assertThat(container.getValue("country.africa.rwanda"), equalTo(2.5));
assertThat(container.getValue("other"), equalTo(2.9));
}
@Test
public void shouldCreateContainerWithGivenValuesIncludingRoot() {
// given
Map<String, Integer> values = ImmutableMap.of(
"", 9,
"foo.bar", 12);
HierarchicalValues<Integer> container = HierarchicalValues.createContainer(-1337, values);
// when / then
assertThat(container.getValue(""), equalTo(9));
assertThat(container.getValue(null), equalTo(9));
assertThat(container.getValue("bogus"), equalTo(9));
assertThat(container.getValue("foo.bar"), equalTo(12));
assertThat(container.getValue("..."), equalTo(9));
assertThat(container.getValue(".values.test."), equalTo(9));
assertThat(container.getValue("foo.bar.child."), equalTo(12));
}
@Test
public void shouldAddAndReplaceValues() {
// given
Map<String, Character> values = ImmutableMap.of(
"1", 'A',
"2", 'B');
HierarchicalValues<Character> container = HierarchicalValues.createContainer('0', values);
// when
container.addValue("3", 'C');
container.addValue("1", 'Z');
// then
assertThat(container.getValue("1"), equalTo('Z'));
assertThat(container.getValue("2"), equalTo('B'));
assertThat(container.getValue("3"), equalTo('C'));
assertThat(container.getValue("4"), equalTo('0')); // fallback to default
}
@Test
public void shouldReturnAllValuesIncludingRootIfWasSpecificallyAdded() {
// given
Map<String, Character> givenValues = ImmutableMap.of(
"1", 'A',
"2", 'B');
HierarchicalValues<Character> container = HierarchicalValues.createContainer('0', givenValues);
// when / then
Map<String, Character> values1 = container.createValuesStream().collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
assertThat(values1, equalTo(givenValues));
container.addValue("", 'q');
Map<String, Character> values2 = container.createValuesStream().collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
assertThat(values2, equalTo(ImmutableMap.of("1", 'A', "2", 'B', "", 'q')));
}
@Test
public void shouldReturnAllValuesIncludingInitiallySpecifiedRoot() {
// given
Map<String, Character> givenValues = ImmutableMap.of(
"1", 'A',
"", '^');
HierarchicalValues<Character> container = HierarchicalValues.createContainer('0', givenValues);
// when
Map<String, Character> values = container.createValuesStream().collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
// then
assertThat(values, equalTo(givenValues));
}
}

View File

@ -167,6 +167,7 @@ settings:
ResetInventoryIfCreative: false
# Do we need to force the survival mode ONLY after /login process ?
ForceOnlyAfterLogin: false
logLevel: 'DEBUG'
security:
# minimum Length of password
minPasswordLength: 5
@ -318,8 +319,6 @@ Security:
# /reload support
useReloadCommandSupport: true
console:
# Remove spam console
noConsoleSpam: true
# Replace passwords in the console when player type a command like /login
removePassword: true
captcha: