Initial commit
Some checks failed
Gradle Build / build (push) Has been cancelled

This commit is contained in:
CaiXiang
2024-11-30 18:36:13 +08:00
commit aa56926258
2134 changed files with 232943 additions and 0 deletions

View File

@@ -0,0 +1,196 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.configuration.gestalt;
import static java.util.Objects.requireNonNull;
import static org.opentcs.util.Assertions.checkState;
import java.nio.file.Path;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.ServiceLoader;
import org.github.gestalt.config.Gestalt;
import org.github.gestalt.config.builder.GestaltBuilder;
import org.github.gestalt.config.decoder.ProxyDecoderMode;
import org.github.gestalt.config.entity.GestaltConfig;
import org.github.gestalt.config.exceptions.GestaltException;
import org.github.gestalt.config.reload.TimedConfigReloadStrategy;
import org.github.gestalt.config.source.ConfigSource;
import org.github.gestalt.config.source.ConfigSourcePackage;
import org.github.gestalt.config.source.FileConfigSourceBuilder;
import org.opentcs.configuration.ConfigurationBindingProvider;
import org.opentcs.configuration.ConfigurationException;
import org.opentcs.configuration.gestalt.decoders.ClassPathDecoder;
import org.opentcs.configuration.gestalt.decoders.MapLiteralDecoder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A configuration binding provider implementation using gestalt.
*/
public class GestaltConfigurationBindingProvider
implements
ConfigurationBindingProvider {
/**
* This class's logger.
*/
private static final Logger LOG
= LoggerFactory.getLogger(GestaltConfigurationBindingProvider.class);
/**
* The key of the (system) property containing the reload interval.
*/
private static final String PROPKEY_RELOAD_INTERVAL = "opentcs.configuration.reload.interval";
/**
* The default reload interval.
*/
private static final long DEFAULT_RELOAD_INTERVAL = 10000;
/**
* Default configuration file name.
*/
private final Path defaultsPath;
/**
* Supplementary configuration files.
*/
private final Path[] supplementaryPaths;
/**
* The configuration entry point.
*/
private final Gestalt gestalt;
/**
* Creates a new instance.
*
* @param defaultsPath Default configuration file name.
* @param supplementaryPaths Supplementary configuration file names.
*/
public GestaltConfigurationBindingProvider(Path defaultsPath, Path... supplementaryPaths) {
this.defaultsPath = requireNonNull(defaultsPath, "defaultsPath");
this.supplementaryPaths = requireNonNull(supplementaryPaths, "supplementaryPaths");
this.gestalt = buildGestalt();
}
@Override
public <T> T get(String prefix, Class<T> type) {
try {
return gestalt.getConfig(prefix, type);
}
catch (GestaltException e) {
throw new ConfigurationException(
String.format("Cannot get configuration value for prefix: '%s'", prefix),
e
);
}
}
private Gestalt buildGestalt() {
GestaltConfig gestaltConfig = new GestaltConfig();
gestaltConfig.setTreatMissingValuesAsErrors(true);
gestaltConfig.setProxyDecoderMode(ProxyDecoderMode.PASSTHROUGH);
try {
Gestalt provider = new GestaltBuilder()
.setGestaltConfig(gestaltConfig)
.useCacheDecorator(true)
.addDefaultDecoders()
.addDecoder(new ClassPathDecoder())
.addDecoder(new MapLiteralDecoder())
.addSources(buildSources())
.build();
provider.loadConfigs();
return provider;
}
catch (GestaltException e) {
throw new ConfigurationException(
"An error occured while creating gestalt configuration binding provider",
e
);
}
}
private List<ConfigSourcePackage> buildSources()
throws GestaltException {
Duration reloadInterval = reloadInterval();
List<ConfigSourcePackage> sources = new ArrayList<>();
// A file for baseline defaults MUST exist in the distribution.
checkState(
defaultsPath.toFile().isFile(),
"Required default configuration file {} does not exist.",
defaultsPath.toFile().getAbsolutePath()
);
LOG.info(
"Using default configuration file {}...",
defaultsPath.toFile().getAbsolutePath()
);
sources.add(
FileConfigSourceBuilder.builder()
.setPath(defaultsPath)
.addConfigReloadStrategy(new TimedConfigReloadStrategy(reloadInterval))
.build()
);
// Files with supplementary configuration MAY exist in the distribution.
for (Path supplementaryPath : supplementaryPaths) {
if (supplementaryPath.toFile().isFile()) {
LOG.info(
"Using overrides from supplementary configuration file {}...",
supplementaryPath.toFile().getAbsolutePath()
);
sources.add(
FileConfigSourceBuilder.builder()
.setPath(supplementaryPath)
.addConfigReloadStrategy(new TimedConfigReloadStrategy(reloadInterval))
.build()
);
}
else {
LOG.warn(
"Supplementary configuration file {} not found, skipped.",
supplementaryPath.toFile().getAbsolutePath()
);
}
}
for (ConfigSource source : ServiceLoader.load(SupplementaryConfigSource.class)) {
LOG.info(
"Using overrides from additional configuration source implementation {}...",
source.getClass()
);
sources.add(
new ConfigSourcePackage(
source,
List.of(new TimedConfigReloadStrategy(reloadInterval))
)
);
}
return sources;
}
private Duration reloadInterval() {
String valueString = System.getProperty(PROPKEY_RELOAD_INTERVAL);
if (valueString == null) {
LOG.info("Using default configuration reload interval ({} ms).", DEFAULT_RELOAD_INTERVAL);
return Duration.ofMillis(DEFAULT_RELOAD_INTERVAL);
}
try {
long value = Long.parseLong(valueString);
LOG.info("Using configuration reload interval of {} ms.", value);
return Duration.ofMillis(value);
}
catch (NumberFormatException exc) {
LOG.warn(
"Could not parse '{}', using default configuration reload interval ({} ms).",
valueString,
DEFAULT_RELOAD_INTERVAL,
exc
);
return Duration.ofMillis(DEFAULT_RELOAD_INTERVAL);
}
}
}

View File

@@ -0,0 +1,14 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.configuration.gestalt;
import org.github.gestalt.config.source.ConfigSource;
/**
* A supplementary source providing configuration overrides.
*/
public interface SupplementaryConfigSource
extends
ConfigSource {
}

View File

@@ -0,0 +1,129 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.configuration.gestalt.decoders;
import org.github.gestalt.config.decoder.Decoder;
import org.github.gestalt.config.decoder.DecoderContext;
import org.github.gestalt.config.decoder.Priority;
import org.github.gestalt.config.entity.ValidationError;
import org.github.gestalt.config.entity.ValidationLevel;
import org.github.gestalt.config.node.ConfigNode;
import org.github.gestalt.config.node.NodeType;
import org.github.gestalt.config.reflect.TypeCapture;
import org.github.gestalt.config.tag.Tags;
import org.github.gestalt.config.utils.ValidateOf;
/**
* A decoder to decode fully qualified class names to their representative class object.
*
* This decoder looks through the class path to find a class with the specified class name
* and returns the class object. It will fail if the specified class cannot be found or
* the specified class cannot be assigned to the type that is expected to be returned.
*/
public class ClassPathDecoder
implements
Decoder<Class<?>> {
/**
* Creates a new instance.
*/
public ClassPathDecoder() {
}
@Override
public Priority priority() {
return Priority.MEDIUM;
}
@Override
public String name() {
return ClassPathDecoder.class.getName();
}
@Override
public boolean canDecode(String string, Tags tags, ConfigNode cn, TypeCapture<?> tc) {
return Class.class.isAssignableFrom(tc.getRawType()) && tc.hasParameter();
}
@Override
public ValidateOf<Class<?>> decode(
String path,
Tags tags,
ConfigNode node,
TypeCapture<?> type,
DecoderContext context
) {
// This decoder only decodes nodes of type leaf. For other types the default decoders
// `ArrayDecoder` and `ObjectDecoder` will eventually call this decoder if necessary.
if (node.getNodeType() != NodeType.LEAF) {
return ValidateOf.inValid(
new ValidationError.DecodingExpectedLeafNodeType(path, node, this.name())
);
}
// Look for a class with the configured name. The class must be assignable to the
// class this decoder is expected to return via the type capture.
return node.getValue().map(className -> {
try {
Class<?> configuredClass = Class.forName(className);
if (type.getFirstParameterType().isAssignableFrom(configuredClass)) {
return ValidateOf.<Class<?>>valid(configuredClass);
}
else {
return ValidateOf.<Class<?>>inValid(
new CannotCast(className, type.getFirstParameterType().getName())
);
}
}
catch (ClassNotFoundException e) {
return ValidateOf.<Class<?>>inValid(new ClassNotFound(className));
}
}).orElse(
ValidateOf.<Class<?>>inValid(
new ValidationError.DecodingLeafMissingValue(path, this.name())
)
);
}
/**
* The configured class cannot be cast to the class expected by the decoder.
*/
public static class CannotCast
extends
ValidationError {
private final String from;
private final String to;
public CannotCast(String from, String to) {
super(ValidationLevel.ERROR);
this.from = from;
this.to = to;
}
@Override
public String description() {
return "The class `" + this.from + "` cannot be cast to `" + this.to + "`.";
}
}
/**
* The configured class cannot be found in the class path.
*/
public static class ClassNotFound
extends
ValidationError {
private final String className;
public ClassNotFound(String className) {
super(ValidationLevel.ERROR);
this.className = className;
}
@Override
public String description() {
return "The class `" + this.className + "` cannot be found.";
}
}
}

View File

@@ -0,0 +1,142 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.configuration.gestalt.decoders;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.github.gestalt.config.decoder.Decoder;
import org.github.gestalt.config.decoder.DecoderContext;
import org.github.gestalt.config.decoder.Priority;
import org.github.gestalt.config.entity.ValidationError;
import org.github.gestalt.config.entity.ValidationLevel;
import org.github.gestalt.config.node.ConfigNode;
import org.github.gestalt.config.node.LeafNode;
import org.github.gestalt.config.node.NodeType;
import org.github.gestalt.config.reflect.TypeCapture;
import org.github.gestalt.config.tag.Tags;
import org.github.gestalt.config.utils.ValidateOf;
/**
* A decoder to read map literals in the form
* {@code <KEY_1>=<VALUE_1>,<KEY_2>=<VALUE_2>,...,<KEY_N>=<VALUE_N>}, where the key-value pairs
* (i.e. map entries) are separated by commas as a delimiter.
*/
public class MapLiteralDecoder
implements
Decoder<Map<?, ?>> {
public MapLiteralDecoder() {
}
@Override
public Priority priority() {
return Priority.HIGH;
}
@Override
public String name() {
return MapLiteralDecoder.class.getName();
}
@Override
public boolean canDecode(String path, Tags tags, ConfigNode node, TypeCapture<?> type) {
return node.getNodeType() == NodeType.LEAF
&& Map.class.isAssignableFrom(type.getRawType())
&& type.getParameterTypes().size() == 2;
}
@Override
public ValidateOf<Map<?, ?>> decode(
String path,
Tags tags,
ConfigNode node,
TypeCapture<?> type,
DecoderContext decoderContext
) {
// This decoder only decodes nodes of type leaf. For other types the default decoders
// `ArrayDecoder` and `ObjectDecoder` will eventually call this decoder if necessary.
if (node.getNodeType() != NodeType.LEAF) {
return ValidateOf.inValid(
new ValidationError.DecodingExpectedLeafNodeType(path, node, this.name())
);
}
if (node.getValue().isEmpty()) {
return ValidateOf.inValid(
new ValidationError.LeafNodesHaveNoValues(path)
);
}
List<ValidationError> errors = new ArrayList<>();
Map<Object, Object> result = new HashMap<>();
// Split the node value on ',' to seperate it into `key=value` pairs and split those
// again into the `key` and `value`. Then decode the key and value to the required types.
for (String entry : node.getValue().get().split(",")) {
if (entry.isBlank()) {
continue;
}
String[] keyValuePair = entry.split("=");
if (keyValuePair.length != 2) {
errors.add(new MapEntryFormatInvalid(entry));
continue;
}
// Decode the key string to the required key type.
ValidateOf<?> key = decoderContext.getDecoderService()
.decodeNode(
path,
tags,
new LeafNode(keyValuePair[0].trim()),
type.getFirstParameterType(),
decoderContext
);
if (key.hasErrors()) {
errors.addAll(key.getErrors());
continue;
}
// Decode the value string to the required value type.
ValidateOf<?> value = decoderContext.getDecoderService()
.decodeNode(
path,
tags,
new LeafNode(keyValuePair[1].trim()),
type.getSecondParameterType(),
decoderContext
);
if (value.hasErrors()) {
errors.addAll(value.getErrors());
continue;
}
result.put(key.results(), value.results());
}
return ValidateOf.validateOf(result, errors);
}
/**
* A validation error for map entries not in the format {@code <KEY>=<VALUE>}.
*/
public static class MapEntryFormatInvalid
extends
ValidationError {
private final String rawEntry;
public MapEntryFormatInvalid(String rawEntry) {
super(ValidationLevel.ERROR);
this.rawEntry = rawEntry;
}
@Override
public String description() {
return "Map entry is not in the format '<KEY>=<VALUE>':" + rawEntry;
}
}
}

View File

@@ -0,0 +1,69 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.configuration.gestalt;
import java.util.Objects;
/**
* A dummy class for testing sample configuration entries.
*/
public class DummyClass {
private final String name;
private final String surname;
private final int age;
public DummyClass() {
name = "";
surname = "";
age = 0;
}
public DummyClass(String paramString) {
String[] split = paramString.split("\\|", 3);
name = split[0];
surname = split[1];
age = Integer.parseInt(split[2]);
}
public DummyClass(String name, String surname, int age) {
this.name = name;
this.surname = surname;
this.age = age;
}
public String getName() {
return name;
}
public String getSurname() {
return surname;
}
public int getAge() {
return age;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof DummyClass)) {
return false;
}
DummyClass other = (DummyClass) o;
return name.equals(other.name) && surname.equals(other.surname) && age == other.age;
}
@Override
public int hashCode() {
int hash = 7;
hash = 23 * hash + Objects.hashCode(this.name);
hash = 23 * hash + Objects.hashCode(this.surname);
hash = 23 * hash + this.age;
return hash;
}
@Override
public String toString() {
return getName() + " - " + getSurname() + ":" + getAge();
}
}

View File

@@ -0,0 +1,158 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.configuration.gestalt;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertFalse;
import java.net.URISyntaxException;
import java.nio.file.Paths;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.opentcs.configuration.ConfigurationBindingProvider;
/**
* Tests for reading configuration entries with gestalt.
*/
public class SampleConfigurationTest {
private ConfigurationBindingProvider input;
@BeforeEach
void setUp() {
input = gestaltConfigurationBindingProvider();
}
@Test
void testBoolean() {
SampleConfig config = input.get(SampleConfig.PREFIX, SampleConfig.class);
assertThat(config.simpleBoolean(), is(true));
}
@Test
void testInteger() {
SampleConfig config = input.get(SampleConfig.PREFIX, SampleConfig.class);
assertThat(config.simpleInteger(), is(600));
}
@Test
void testString() {
SampleConfig config = input.get(SampleConfig.PREFIX, SampleConfig.class);
assertThat(config.simpleString(), is("HelloWorld"));
}
@Test
void testEnum() {
SampleConfig config = input.get(SampleConfig.PREFIX, SampleConfig.class);
assertThat(config.simpleEnum(), is(SampleConfigurationTest.DummyEnum.ORDER));
}
@Test
void testStringList() {
SampleConfig config = input.get(SampleConfig.PREFIX, SampleConfig.class);
assertThat(config.stringList(), equalTo(List.of("A", "B", "C")));
}
@Test
void testStringMap() {
SampleConfig config = input.get(SampleConfig.PREFIX, SampleConfig.class);
assertThat(config.stringMap(), equalTo(Map.of("A", "1", "B", "2", "C", "3")));
}
@Test
void testEnumMap() {
SampleConfig config = input.get(SampleConfig.PREFIX, SampleConfig.class);
assertThat(config.enumMap(), equalTo(Map.of(DummyEnum.ORDER, "1", DummyEnum.POINT, "2")));
}
@Test
void testObjectList() {
SampleConfig config = input.get(SampleConfig.PREFIX, SampleConfig.class);
assertThat(
config.objectList(), equalTo(
List.of(
new DummyClass("A", "B", 1),
new DummyClass("C", "D", 2)
)
)
);
}
@Test
void testStringConstructor() {
SampleConfig config = input.get(SampleConfig.PREFIX, SampleConfig.class);
assertThat(config.stringConstructor(), equalTo(new DummyClass("A", "B", 1)));
}
@Test
void testClassPath() {
SampleConfig config = input.get(SampleConfig.PREFIX, SampleConfig.class);
assertThat(config.classPath(), is(DummyClass.class));
}
private static ConfigurationBindingProvider gestaltConfigurationBindingProvider() {
try {
return new GestaltConfigurationBindingProvider(
Paths.get(
Thread.currentThread().getContextClassLoader()
.getResource("org/opentcs/configuration/gestalt/sampleConfig.properties").toURI()
)
);
}
catch (URISyntaxException ex) {
Logger.getLogger(SampleConfigurationTest.class.getName()).log(Level.SEVERE, null, ex);
assertFalse(true);
return null;
}
}
public interface SampleConfig {
/**
* This configuration's prefix.
*/
String PREFIX = "sampleConfig";
boolean simpleBoolean();
int simpleInteger();
String simpleString();
SampleConfigurationTest.DummyEnum simpleEnum();
List<String> stringList();
Map<String, String> stringMap();
Map<DummyEnum, String> enumMap();
List<DummyClass> objectList();
DummyClass stringConstructor();
Class<DummyClass> classPath();
}
public enum DummyEnum {
/**
* Vehicle.
*/
VEHICLE,
/**
* Point.
*/
POINT,
/**
* Order.
*/
ORDER
}
}

View File

@@ -0,0 +1,167 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.configuration.gestalt.decoders;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.instanceOf;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.github.gestalt.config.Gestalt;
import org.github.gestalt.config.builder.GestaltBuilder;
import org.github.gestalt.config.decoder.DecoderContext;
import org.github.gestalt.config.decoder.DecoderRegistry;
import org.github.gestalt.config.decoder.DecoderService;
import org.github.gestalt.config.exceptions.GestaltConfigurationException;
import org.github.gestalt.config.exceptions.GestaltException;
import org.github.gestalt.config.lexer.SentenceLexer;
import org.github.gestalt.config.node.ConfigNodeService;
import org.github.gestalt.config.node.LeafNode;
import org.github.gestalt.config.path.mapper.StandardPathMapper;
import org.github.gestalt.config.reflect.TypeCapture;
import org.github.gestalt.config.source.MapConfigSourceBuilder;
import org.github.gestalt.config.tag.Tags;
import org.github.gestalt.config.utils.ValidateOf;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
/**
* Tests the map literal decoder {@link MapLiteralDecoder} for Gestalt.
*/
class MapLiteralDecoderTest {
private ConfigNodeService configNodeService;
private SentenceLexer lexer;
private DecoderService decoderService;
@BeforeEach
void setup()
throws GestaltConfigurationException {
configNodeService = mock(ConfigNodeService.class);
lexer = mock(SentenceLexer.class);
decoderService = new DecoderRegistry(
Collections.singletonList(new MapLiteralDecoder()),
configNodeService,
lexer,
List.of(new StandardPathMapper())
);
}
@Test
void shouldDecodeMapLiteral()
throws GestaltException {
Map<String, String> config = Map.of("entry_path", "AAAA=1, BBBB=2, CCCC=3");
Gestalt gestalt = buildGestaltConfig(config);
Map<String, Integer> result = gestalt.getConfig(
"entry_path",
new TypeCapture<Map<String, Integer>>() {
}
);
assertEquals(result.get("AAAA"), 1);
assertEquals(result.get("BBBB"), 2);
assertEquals(result.get("CCCC"), 3);
}
@Test
void shouldDecodeMapLiteralToEnum()
throws GestaltException {
Map<String, String> config = Map.of("entry_path", "Foo=1, Bar=2, Baz=3");
Gestalt gestalt = buildGestaltConfig(config);
Map<Things, Integer> result = gestalt.getConfig(
"entry_path",
new TypeCapture<Map<Things, Integer>>() {
}
);
assertEquals(result.get(Things.Foo), 1);
assertEquals(result.get(Things.Bar), 2);
assertEquals(result.get(Things.Baz), 3);
}
@Test
void emptyLeafShouldGiveAnEmptyMap()
throws GestaltException {
Map<String, String> config = Map.of("entry_path", "");
Gestalt gestalt = buildGestaltConfig(config);
Map<String, Integer> result = gestalt.getConfig(
"entry_path",
new TypeCapture<Map<String, Integer>>() {
}
);
assertTrue(result.isEmpty());
}
@Test
void shouldGiveErrorWhenWrongDelimiterIsUsed() {
MapLiteralDecoder decoder = new MapLiteralDecoder();
ValidateOf<Map<?, ?>> result = decoder.decode(
"entry_path",
Tags.of(),
new LeafNode("AAAA=1; BBBB=2; CCCC=3"),
new TypeCapture<Map<String, String>>() {
},
new DecoderContext(decoderService, null)
);
assertThat(result.getErrors(), hasSize(1));
assertThat(
result.getErrors().get(0),
instanceOf(MapLiteralDecoder.MapEntryFormatInvalid.class)
);
}
@Test
void shouldGiveErrorWhenWrongAsignmentIsUsed() {
MapLiteralDecoder decoder = new MapLiteralDecoder();
ValidateOf<Map<?, ?>> result = decoder.decode(
"entry_path",
Tags.of(),
new LeafNode("AAAA~1, BBBB~2, CCCC~3"),
new TypeCapture<Map<String, String>>() {
},
new DecoderContext(decoderService, null)
);
assertThat(result.getErrors(), hasSize(3));
assertThat(
result.getErrors().get(0),
instanceOf(MapLiteralDecoder.MapEntryFormatInvalid.class)
);
assertThat(
result.getErrors().get(1),
instanceOf(MapLiteralDecoder.MapEntryFormatInvalid.class)
);
assertThat(
result.getErrors().get(2),
instanceOf(MapLiteralDecoder.MapEntryFormatInvalid.class)
);
}
private enum Things {
Foo,
Bar,
Baz
}
private Gestalt buildGestaltConfig(Map<String, String> config)
throws GestaltException {
Gestalt gestalt = new GestaltBuilder()
.addDefaultDecoders()
.addDecoder(new ClassPathDecoder())
.addDecoder(new MapLiteralDecoder())
.addSource(MapConfigSourceBuilder.builder().setCustomConfig(config).build())
.build();
gestalt.loadConfigs();
return gestalt;
}
}

View File

@@ -0,0 +1,13 @@
# SPDX-FileCopyrightText: The openTCS Authors
# SPDX-License-Identifier: CC-BY-4.0
sampleConfig.simpleBoolean = true
sampleConfig.simpleInteger = 600
sampleConfig.simpleString = HelloWorld
sampleConfig.simpleEnum= ORDER
sampleConfig.stringList = A,B,C
sampleConfig.stringMap = A=1,B=2,C=3
sampleConfig.enumMap = ORDER=1,POINT=2
sampleConfig.objectList = A|B|1,C|D|2
sampleConfig.stringConstructor = A|B|1
sampleConfig.classPath = org.opentcs.configuration.gestalt.DummyClass