implement content-settings and content-config on JSON/YAML tiny datastructures

This commit is contained in:
Jörg Prante 2021-10-12 11:48:29 +02:00
parent 86c28934a5
commit 823b15d3a9
57 changed files with 2164 additions and 221 deletions

View file

@ -1,4 +1,5 @@
dependencies {
api project(':content-json')
api project(':content-yaml')
api project(':content-settings-datastructures')
testImplementation project(':content-settings-datastructures-json')
testImplementation project(':content-settings-datastructures-yaml')
}

View file

@ -1,5 +1,10 @@
import org.xbib.content.config.ConfigLogger;
import org.xbib.content.settings.datastructures.SettingsLoader;
module org.xbib.content.config {
exports org.xbib.content.config;
requires org.xbib.content.json;
requires org.xbib.content.yaml;
uses ConfigLogger;
uses SettingsLoader;
provides ConfigLogger with org.xbib.content.config.SystemConfigLogger;
requires transitive org.xbib.content.settings.datastructures;
}

View file

@ -0,0 +1,17 @@
package org.xbib.content.config;
@SuppressWarnings("serial")
public class ConfigException extends RuntimeException {
public ConfigException(Exception e) {
super(e);
}
public ConfigException(String message) {
super(message);
}
public ConfigException(String message, Throwable cause) {
super(message, cause);
}
}

View file

@ -0,0 +1,167 @@
package org.xbib.content.config;
import java.io.IOException;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.FileVisitOption;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.PathMatcher;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.EnumSet;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class ConfigFinder {
private final FileSystem fileSystem;
private final EnumSet<FileVisitOption> opts;
private final List<Path> result;
private Comparator<Path> comparator;
public ConfigFinder() {
this(FileSystems.getDefault(), EnumSet.of(FileVisitOption.FOLLOW_LINKS));
}
public ConfigFinder(FileSystem fileSystem, EnumSet<FileVisitOption> opts) {
this.fileSystem = fileSystem;
this.opts = opts;
this.result = new ArrayList<>();
}
public ConfigFinder find(String path, String pathPattern) throws IOException {
return find(null, null, fileSystem.getPath(path), pathPattern);
}
public ConfigFinder find(String base, String basePattern, String path, String pathPattern) throws IOException {
return find(base == null || base.isEmpty() ? null : fileSystem.getPath(base), basePattern,
path == null || path.isEmpty() ? null : fileSystem.getPath(path), pathPattern);
}
public ConfigFinder find(Path base, String basePattern, Path path, String pathPattern) throws IOException {
return find(base, basePattern, path, pathPattern, null);
}
/**
* Find the most recent version of a file.
*
* @param base the path of the base directory
* @param basePattern a pattern to match directory entries in the base directory or null to match '*'
* @param path the path of the file if no recent path can be found in the base directory or null
* @param pathPattern the file name pattern to match
* @param modifiedSince time stamp for file or null
* @return this Finder
* @throws IOException if find fails
*/
public ConfigFinder find(Path base,
String basePattern,
Path path,
String pathPattern,
FileTime modifiedSince) throws IOException {
if (base != null && path == null) {
// find input in base
final PathMatcher baseMatcher = base.getFileSystem()
.getPathMatcher("glob:" + (basePattern != null ? basePattern : "*"));
final PathMatcher pathMatcher = base.getFileSystem()
.getPathMatcher("glob:" + (pathPattern != null ? pathPattern : "*"));
List<Path> directories = new ArrayList<>();
List<Path> list = Files.find(base, 1,
(p, a) -> {
if (Files.isDirectory(p) && baseMatcher.matches(p.getFileName())) {
directories.add(p);
return false;
}
return Files.isRegularFile(p) && pathMatcher.matches(p.getFileName());
}, FileVisitOption.FOLLOW_LINKS)
.collect(Collectors.toList());
if (directories.isEmpty()) {
return this;
}
list.sort(LAST_MODIFIED_COMPARATOR.reversed());
result.addAll(list);
path = list.iterator().next();
}
if (path == null) {
return this;
}
final PathMatcher pathMatcher = path.getFileSystem()
.getPathMatcher("glob:" + (pathPattern != null ? pathPattern : "*"));
Files.walkFileTree(path, opts, Integer.MAX_VALUE, new SimpleFileVisitor<>() {
@Override
public FileVisitResult visitFile(Path p, BasicFileAttributes a) {
if ((Files.isRegularFile(p) && pathMatcher.matches(p.getFileName())) &&
(modifiedSince == null || a.lastModifiedTime().toMillis() > modifiedSince.toMillis())) {
result.add(p);
}
return FileVisitResult.CONTINUE;
}
});
return this;
}
public ConfigFinder sortBy(String mode) {
if ("lastmodified".equals(mode)) {
this.comparator = LAST_MODIFIED_COMPARATOR;
} else if ("name".equals(mode)) {
this.comparator = PATH_NAME_COMPARATOR;
}
return this;
}
public ConfigFinder order(String mode) {
if ("desc".equals(mode)) {
this.comparator = Collections.reverseOrder(comparator);
}
return this;
}
public Stream<Path> getPathFiles() {
return getPathFiles(-1);
}
public Stream<Path> getPathFiles(long max) {
if (comparator != null) {
result.sort(comparator);
}
return result.stream().limit(max < 0 ? result.size() : max);
}
public Stream<Path> skipPathFiles(long skip) {
if (comparator != null) {
result.sort(comparator);
}
return result.stream().skip(skip < 0 ? 0 : skip);
}
public Stream<String> getPaths() {
return getPaths(-1);
}
public Stream<String> getPaths(long max) {
if (comparator != null) {
result.sort(comparator);
}
return result.stream()
.map(p -> p.toAbsolutePath().toString())
.limit(max < 0 ? result.size() : max);
}
private static final Comparator<Path> LAST_MODIFIED_COMPARATOR = Comparator.comparing(p -> {
try {
return Files.getLastModifiedTime(p);
} catch (IOException e) {
return null;
}});
private static final Comparator<Path> PATH_NAME_COMPARATOR = Comparator.comparing(Path::toString);
}

View file

@ -1,9 +1,9 @@
package org.xbib.content.config;
import org.xbib.content.json.JsonSettingsLoader;
import org.xbib.content.settings.Settings;
import org.xbib.content.SettingsLoader;
import org.xbib.content.yaml.YamlSettingsLoader;
import org.xbib.content.settings.datastructures.SettingsLoader;
import org.xbib.content.settings.datastructures.Settings;
import org.xbib.content.settings.datastructures.SettingsLoaderService;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
@ -14,74 +14,145 @@ import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.ServiceLoader;
import java.util.stream.Collectors;
/**
* A configuration file loader for JSON/YAML configuration files.
* A configuration loader for configuration files.
*/
public class ConfigLoader {
private static final String JSON = ".json";
private final Map<ConfigParams, Settings> map;
private static final String YML = ".yml";
private ConfigLogger logger;
private static final String YAML = ".yaml";
private ConfigLoader() {
this.map = new HashMap<>();
}
private final ConfigLogger logger;
private static class Holder {
private static ConfigLogger createConfigLogger() {
ServiceLoader<ConfigLogger> serviceLoader = ServiceLoader.load(ConfigLogger.class);
Optional<ConfigLogger> optionalConfigLogger = serviceLoader.findFirst();
return optionalConfigLogger.orElse(new SystemConfigLogger());
}
static ConfigLoader LOADER = new ConfigLoader().withLogger(createConfigLogger());
}
public ConfigLoader(ConfigLogger logger) {
public static ConfigLoader getInstance() {
return Holder.LOADER;
}
public ConfigLoader withLogger(ConfigLogger logger) {
this.logger = logger;
return this;
}
public Settings.Builder loadSettings(String[] args,
ClassLoader classLoader,
String applicationName,
String... fileNamesWithoutSuffix) throws IOException {
Settings.Builder settings = createSettingsFromArgs(args, applicationName, fileNamesWithoutSuffix);
return settings != null ? settings : loadSettings(classLoader, applicationName, fileNamesWithoutSuffix);
public synchronized Settings load(ConfigParams configParams) throws ConfigException {
map.computeIfAbsent(configParams, p -> internalLoad(p).build());
return map.get(configParams);
}
public Settings.Builder loadSettings(ClassLoader classLoader,
String applicationName,
String... fileNamesWithoutSuffix) throws IOException {
Settings.Builder settings = createSettingsFromStdin();
if (settings != null) {
return overrideFromProperties(applicationName, settings);
private Settings.Builder internalLoad(ConfigParams params) throws ConfigException {
Settings.Builder settings = Settings.settingsBuilder();
if (params.withSystemEnvironment) {
settings.loadFromSystemEnvironment();
}
for (String fileNameWithoutSuffix : fileNamesWithoutSuffix) {
settings = createSettingsFromFile(createListOfLocations(applicationName, fileNameWithoutSuffix));
if (settings != null) {
return overrideFromProperties(applicationName, settings);
if (params.withSystemProperties) {
settings.loadFromSystemProperties();
}
for (ClassLoader cl : List.of(classLoader,
Thread.currentThread().getContextClassLoader(),
ConfigLoader.class.getClassLoader(),
ClassLoader.getSystemClassLoader())) {
if (!params.settings.isEmpty()) {
for (Settings s : params.settings) {
settings.put(s);
}
}
if (!params.reader.isEmpty()) {
for (ConfigParams.SuffixedReader reader : params.reader) {
Settings.Builder readerSettings = createSettingsFromReader(reader.reader, reader.suffix);
if (readerSettings != null) {
settings.put(readerSettings.build());
if (!params.includeAll) {
return settings;
}
}
}
}
if (params.args != null) {
Settings.Builder argsSettings = createSettingsFromArgs(params);
if (argsSettings != null) {
settings.put(argsSettings.build());
if (!params.includeAll) {
return settings;
}
}
}
if (params.withStdin) {
Settings.Builder stdinSettings = createSettingsFromStdin();
if (stdinSettings != null) {
settings.put(stdinSettings.build());
if (!params.includeAll) {
return overrideFromProperties(params, settings);
}
}
}
if (!params.fileLocations.isEmpty()) {
Settings.Builder fileSettings = createSettingsFromFile(params.fileLocations);
if (fileSettings != null) {
settings.put(fileSettings.build());
if (!params.includeAll) {
return overrideFromProperties(params, settings);
}
}
}
if (!params.fileNamesWithoutSuffix.isEmpty()) {
for (String fileNameWithoutSuffix : params.fileNamesWithoutSuffix) {
Settings.Builder fileSettings = createSettingsFromFile(createListOfLocations(params, fileNameWithoutSuffix));
if (fileSettings != null) {
settings.put(fileSettings.build());
if (!params.includeAll) {
return overrideFromProperties(params, settings);
}
}
}
for (String fileNameWithoutSuffix : params.fileNamesWithoutSuffix) {
if (params.classLoaders != null) {
for (ClassLoader cl : params.classLoaders) {
if (cl != null) {
settings = createClasspathSettings(cl, applicationName, fileNameWithoutSuffix);
if (settings != null) {
return overrideFromProperties(applicationName, settings);
Settings.Builder classpathSettings = createClasspathSettings(params, cl, fileNameWithoutSuffix);
if (classpathSettings != null) {
settings.put(classpathSettings.build());
if (!params.includeAll) {
return overrideFromProperties(params, settings);
}
}
}
}
throw new IllegalArgumentException("no config found for " + applicationName + " " +
Arrays.asList(fileNamesWithoutSuffix));
}
}
}
if (params.includeAll) {
return overrideFromProperties(params, settings);
}
throw new ConfigException("no config found");
}
private Settings.Builder createSettingsFromArgs(String[] args,
String applicationName,
String... fileNamesWithoutSuffix) throws IOException {
for (String fileNameWithoutSuffix : fileNamesWithoutSuffix) {
for (String suffix : List.of(YML, YAML, JSON)) {
for (int i = 0; i < args.length - 1; i++) {
String arg = args[i];
if (arg.equals("--" + applicationName + "-" + fileNameWithoutSuffix + suffix)) {
return createSettingsFromReader(new StringReader(args[i + 1]), suffix);
private Settings.Builder createSettingsFromArgs(ConfigParams params) throws ConfigException {
if (!params.fileNamesWithoutSuffix.isEmpty() && params.args != null) {
for (String fileNameWithoutSuffix : params.fileNamesWithoutSuffix) {
for (String suffix : SettingsLoaderService.getInstance().getSuffixes()) {
for (int i = 0; i < params.args.size() - 1; i++) {
String arg = params.args.get(i);
String s = params.directoryName != null ?
"--" + params.directoryName + "-" + fileNameWithoutSuffix + suffix : "--" + fileNameWithoutSuffix + suffix;
if (arg.equals(s)) {
return createSettingsFromReader(new StringReader(params.args.get(i + 1)), suffix);
}
}
}
}
@ -89,82 +160,111 @@ public class ConfigLoader {
return null;
}
private Settings.Builder createSettingsFromStdin() throws IOException {
private Settings.Builder createSettingsFromStdin() throws ConfigException {
if (System.in != null) {
try {
int numBytesWaiting = System.in.available();
if (numBytesWaiting > 0) {
String suffix = System.getProperty("config.format", "yaml");
return createSettingsFromStream(System.in, "." + suffix);
}
} catch (IOException e) {
throw new ConfigException(e);
}
}
return null;
}
private Settings.Builder createSettingsFromFile(List<String> settingsFileNames) throws IOException {
private Settings.Builder createSettingsFromFile(List<String> settingsFileNames) throws ConfigException {
Settings.Builder settings = Settings.settingsBuilder();
for (String settingsFileName: settingsFileNames) {
int pos = settingsFileName.lastIndexOf('.');
String suffix = (pos > 0 ? settingsFileName.substring(pos) : "").toLowerCase(Locale.ROOT);
String suffix = (pos > 0 ? settingsFileName.substring(pos + 1) : "").toLowerCase(Locale.ROOT);
Path path = Paths.get(settingsFileName);
logger.info("trying " + path.toString());
if (logger != null) {
logger.info("trying " + path);
}
if (Files.exists(path)) {
if (logger != null) {
logger.info("found path: " + path);
}
System.setProperty("config.path", path.getParent().toString());
return createSettingsFromStream(Files.newInputStream(path), suffix);
try {
InputStream inputStream = Files.newInputStream(path);
Settings.Builder fileSettings = createSettingsFromStream(inputStream, suffix);
if (fileSettings != null) {
settings.put(fileSettings.build());
}
} catch (Exception e) {
throw new ConfigException(e);
}
}
return null;
}
return settings.isEmpty() ? null : settings;
}
private Settings.Builder createClasspathSettings(ClassLoader classLoader,
String applicationName,
String fileNameWithoutSuffix)
throws IOException {
for (String suffix : List.of(YML, YAML, JSON)) {
InputStream inputStream = classLoader.getResourceAsStream(applicationName + '-' +
fileNameWithoutSuffix + suffix);
private Settings.Builder createClasspathSettings(ConfigParams params,
ClassLoader classLoader,
String fileNameWithoutSuffix) throws ConfigException {
Settings.Builder settings = Settings.settingsBuilder();
for (String suffix : SettingsLoaderService.getInstance().getSuffixes()) {
String path = params.directoryName != null ?
params.directoryName + '-' + fileNameWithoutSuffix + suffix : fileNameWithoutSuffix + suffix;
InputStream inputStream = classLoader.getResourceAsStream(path);
if (inputStream != null) {
logger.info("found resource: " + applicationName + '-' + fileNameWithoutSuffix + suffix);
Settings.Builder settings = createSettingsFromStream(inputStream, suffix);
if (settings != null) {
return settings;
if (logger != null) {
logger.info("found resource: " + path);
}
Settings.Builder streamSettings = createSettingsFromStream(inputStream, suffix);
if (streamSettings != null) {
settings.put(streamSettings.build());
}
}
}
return null;
return settings.isEmpty() ? null : settings;
}
private Settings.Builder createSettingsFromStream(InputStream inputStream, String suffix) throws IOException {
private Settings.Builder createSettingsFromStream(InputStream inputStream,
String suffix) throws ConfigException {
if (inputStream == null) {
if (logger != null) {
logger.error("unable to open input stream");
}
return null;
}
return createSettingsFromReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8), suffix);
}
private Settings.Builder createSettingsFromReader(Reader reader, String suffix) throws IOException {
private Settings.Builder createSettingsFromReader(Reader reader,
String suffix) throws ConfigException {
if (reader == null) {
if (logger != null) {
logger.error("unable to open reader");
}
return null;
}
SettingsLoader settingsLoader = isYaml(suffix) ? new YamlSettingsLoader() :
isJson(suffix) ? new JsonSettingsLoader() : null;
SettingsLoader settingsLoader = SettingsLoaderService.getInstance().loaderFromResource(suffix);
if (settingsLoader != null) {
Settings.Builder settings;
try (BufferedReader bufferedReader = new BufferedReader(reader)) {
String content = bufferedReader.lines().collect(Collectors.joining("\n"));
settings = Settings.settingsBuilder().put(settingsLoader.load(content));
} catch (IOException e) {
throw new ConfigException(e);
}
return settings;
} else {
if (logger != null) {
logger.error("suffix is invalid: " + suffix);
}
}
return null;
}
private static Settings.Builder overrideFromProperties(String applicationName, Settings.Builder settings) {
for (Map.Entry<String, String> entry : settings.map().entrySet()) {
String key = entry.getKey();
String value = System.getProperty(applicationName + '.' + key);
private Settings.Builder overrideFromProperties(ConfigParams params,
Settings.Builder settings) {
for (String key : settings.map().keySet()) {
String value = System.getProperty(params.directoryName != null ? params.directoryName + '.' + key : key);
if (value != null) {
settings.put(key, value);
}
@ -172,28 +272,24 @@ public class ConfigLoader {
return settings;
}
private static List<String> createListOfLocations(String applicationName, String fileNameWithoutSuffix) {
private List<String> createListOfLocations(ConfigParams params,
String fileNameWithoutSuffix) {
List<String> list = new ArrayList<>();
for (String suffix : SettingsLoaderService.getInstance().getSuffixes()) {
String xdgConfigHome = System.getenv("XDG_CONFIG_HOME");
if (xdgConfigHome == null) {
xdgConfigHome = System.getProperty("user.home") + "/.config";
}
return List.of(
applicationName + '-' + fileNameWithoutSuffix + YML,
applicationName + '-' + fileNameWithoutSuffix + YAML,
applicationName + '-' + fileNameWithoutSuffix + JSON,
xdgConfigHome + '/' + applicationName + '/' + fileNameWithoutSuffix + YML,
xdgConfigHome + '/' + applicationName + '/' + fileNameWithoutSuffix + YAML,
xdgConfigHome + '/' + applicationName + '/' + fileNameWithoutSuffix + JSON,
"/etc/" + applicationName + '/' + fileNameWithoutSuffix + YML,
"/etc/" + applicationName + '/' + fileNameWithoutSuffix + YAML,
"/etc/" + applicationName + '/' + fileNameWithoutSuffix + JSON);
}
private static boolean isYaml(String suffix) {
return YAML.equals(suffix) || YML.equals(suffix);
}
private static boolean isJson(String suffix) {
return JSON.equals(suffix);
if (params.directoryName != null) {
list.add(params.directoryName + '-' + fileNameWithoutSuffix + "." + suffix);
list.add(xdgConfigHome + '/' + params.directoryName + '/' + fileNameWithoutSuffix + "." +suffix);
list.add("/etc/" + params.directoryName + '/' + fileNameWithoutSuffix + "." + suffix);
} else {
list.add(fileNameWithoutSuffix + "." + suffix);
list.add(xdgConfigHome + '/' + fileNameWithoutSuffix + "." + suffix);
list.add("/etc/" + fileNameWithoutSuffix + "." + suffix);
}
}
return list;
}
}

View file

@ -0,0 +1,129 @@
package org.xbib.content.config;
import org.xbib.content.settings.datastructures.Settings;
import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
public class ConfigParams implements Comparable<ConfigParams> {
private static final Comparator<ConfigParams> COMPARATOR =
Comparator.comparing(ConfigParams::toString);
boolean withSystemEnvironment = false;
boolean withSystemProperties = false;
boolean includeAll = false;
boolean withStdin = false;
List<ClassLoader> classLoaders = null;
final List<SuffixedReader> reader = new ArrayList<>();
final List<Settings> settings = new ArrayList<>();
List<String> args = null;
String directoryName = null;
final List<String> fileNamesWithoutSuffix = new ArrayList<>();
final List<String> fileLocations = new ArrayList<>();
public ConfigParams() {
}
public ConfigParams withSystemEnvironment() {
this.withSystemEnvironment = true;
return this;
}
public ConfigParams withSystemProperties() {
this.withSystemProperties = true;
return this;
}
public ConfigParams includeAll() {
this.includeAll = true;
return this;
}
public ConfigParams withStdin(boolean withStdin) {
this.withStdin = withStdin;
return this;
}
public ConfigParams withArgs(String[] args) {
this.args = Arrays.asList(args);
return this;
}
public ConfigParams withClassLoaders(ClassLoader... classLoaders) {
this.classLoaders = Arrays.asList(classLoaders);
return this;
}
public ConfigParams withReader(Reader reader, String suffix) {
SuffixedReader suffixedReader = new SuffixedReader();
suffixedReader.reader = reader;
suffixedReader.suffix = suffix;
this.reader.add(suffixedReader);
return this;
}
public ConfigParams withSettings(Settings settings) {
this.settings.add(settings);
return this;
}
public ConfigParams withDirectoryName(String directoryName) {
this.directoryName = directoryName;
return this;
}
public ConfigParams withFileNamesWithoutSuffix(String... fileNamesWithoutSuffix) {
this.fileNamesWithoutSuffix.addAll(Arrays.asList(fileNamesWithoutSuffix));
return this;
}
public ConfigParams withLocation(String location) {
this.fileLocations.add(location);
return this;
}
public ConfigParams withPath(String basePath, String basePattern, String path, String pathPattern) throws IOException {
ConfigFinder configFinder = new ConfigFinder();
configFinder.find(basePath, basePattern, path, pathPattern).getPaths().forEach(this::withLocation);
return this;
}
@Override
public int compareTo(ConfigParams o) {
return COMPARATOR.compare(this, o);
}
@Override
public String toString() {
return "" +
withSystemEnvironment +
withSystemProperties +
withStdin +
classLoaders +
reader +
args +
directoryName +
fileNamesWithoutSuffix +
fileLocations;
}
public static class SuffixedReader {
Reader reader;
String suffix;
}
}

View file

@ -0,0 +1 @@
org.xbib.content.config.SystemConfigLogger

View file

@ -0,0 +1,45 @@
package org.xbib.content.config.test;
import org.junit.jupiter.api.Test;
import org.xbib.content.config.ConfigLoader;
import org.xbib.content.config.ConfigParams;
import org.xbib.content.settings.datastructures.Settings;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.util.logging.Level;
import java.util.logging.Logger;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class ConfigLoaderTest {
@Test
public void configTest() {
Reader reader = new StringReader("a=b");
Settings settings = ConfigLoader.getInstance()
.load(new ConfigParams()
.withReader(reader, "properties"));
assertEquals("b", settings.get("a"));
}
@Test
public void configFileTest() throws IOException {
Settings settings = ConfigLoader.getInstance()
.load(new ConfigParams()
.withPath(null, null, "src/test/resources", "config.*"));
assertEquals("world", settings.get("hello"));
assertEquals("world2", settings.get("hello2"));
}
@Test
public void configInterlibraryTest() {
Settings settings = ConfigLoader.getInstance()
.load(new ConfigParams()
.withDirectoryName("interlibrary")
.withFileNamesWithoutSuffix("test"));
Logger.getAnonymousLogger().log(Level.INFO, settings.getAsMap().toString());
}
}

View file

@ -0,0 +1,3 @@
org.xbib.content.settings.datastructures.PropertiesSettingsLoader
org.xbib.content.settings.datastructures.json.JsonSettingsLoader
org.xbib.content.settings.datastructures.yaml.YamlSettingsLoader

View file

@ -0,0 +1,3 @@
{
"hello2": "world2"
}

View file

@ -0,0 +1 @@
hello: world

View file

@ -1,5 +1,4 @@
dependencies {
api project(':content-api')
api "org.xbib:datastructures-tiny:${project.property('xbib-datastructures-tiny.version')}"
api "com.fasterxml.jackson.core:jackson-core:${project.property('jackson.version')}"
}

View file

@ -11,7 +11,6 @@ module org.xbib.content.core {
exports org.xbib.content.util.unit;
exports org.xbib.content.core;
requires transitive org.xbib.content;
requires transitive org.xbib.datastructures.tiny;
requires com.fasterxml.jackson.core;
provides SettingsLoader with PropertiesSettingsLoader;
}

View file

@ -1,10 +1,8 @@
package org.xbib.content.core;
import org.xbib.content.XContentParser;
import org.xbib.datastructures.tiny.TinyMap;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -13,83 +11,17 @@ import java.util.Map;
*/
public abstract class AbstractXContentParser implements XContentParser {
private static final MapFactory SIMPLE_MAP_FACTORY = HashMap::new;
//private static final MapFactory SIMPLE_MAP_FACTORY = HashMap::new;
private static final MapFactory TINY_MAP_FACTORY = TinyMap::builder;
//private static final MapFactory TINY_MAP_FACTORY = TinyMap::builder;
private boolean losslessDecimals;
private boolean base16Checks;
private static Map<String, Object> readMap(XContentParser parser) throws IOException {
return readMap(parser, SIMPLE_MAP_FACTORY);
}
protected abstract MapFactory getMapFactory();
private static Map<String, Object> readOrderedMap(XContentParser parser) throws IOException {
return readMap(parser, TINY_MAP_FACTORY);
}
private static Map<String, Object> readMap(XContentParser parser, MapFactory mapFactory) throws IOException {
Map<String, Object> map = mapFactory.newMap();
XContentParser.Token t = parser.currentToken();
if (t == null) {
t = parser.nextToken();
}
if (t == XContentParser.Token.START_OBJECT) {
t = parser.nextToken();
}
for (; t == XContentParser.Token.FIELD_NAME; t = parser.nextToken()) {
String fieldName = parser.currentName();
t = parser.nextToken();
Object value = readValue(parser, mapFactory, t);
map.put(fieldName, value);
}
return map;
}
private static List<Object> readList(XContentParser parser, MapFactory mapFactory) throws IOException {
ArrayList<Object> list = new ArrayList<>();
Token t;
while ((t = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
list.add(readValue(parser, mapFactory, t));
}
return list;
}
private static Object readValue(XContentParser parser, MapFactory mapFactory, XContentParser.Token t) throws IOException {
if (t == XContentParser.Token.VALUE_NULL) {
return null;
} else if (t == XContentParser.Token.VALUE_STRING) {
if (parser.isBase16Checks()) {
return XContentHelper.parseBase16(parser.text());
}
return parser.text();
} else if (t == XContentParser.Token.VALUE_NUMBER) {
XContentParser.NumberType numberType = parser.numberType();
if (numberType == XContentParser.NumberType.INT) {
return parser.isLosslessDecimals() ? parser.bigIntegerValue() : parser.intValue();
} else if (numberType == XContentParser.NumberType.LONG) {
return parser.isLosslessDecimals() ? parser.bigIntegerValue() : parser.longValue();
} else if (numberType == XContentParser.NumberType.FLOAT) {
return parser.isLosslessDecimals() ? parser.bigDecimalValue() : parser.floatValue();
} else if (numberType == XContentParser.NumberType.DOUBLE) {
return parser.isLosslessDecimals() ? parser.bigDecimalValue() : parser.doubleValue();
} else if (numberType == NumberType.BIG_INTEGER) {
return parser.bigIntegerValue();
} else if (numberType == NumberType.BIG_DECIMAL) {
return parser.bigDecimalValue();
}
} else if (t == XContentParser.Token.VALUE_BOOLEAN) {
return parser.booleanValue();
} else if (t == XContentParser.Token.START_OBJECT) {
return readMap(parser, mapFactory);
} else if (t == XContentParser.Token.START_ARRAY) {
return readList(parser, mapFactory);
} else if (t == XContentParser.Token.VALUE_EMBEDDED_OBJECT) {
return parser.binaryValue();
}
return null;
}
protected abstract MapFactory getOrderedMapFactory();
@Override
public boolean isBooleanValue() throws IOException {
@ -233,11 +165,6 @@ public abstract class AbstractXContentParser implements XContentParser {
}
}
@FunctionalInterface
interface MapFactory {
Map<String, Object> newMap();
}
/**
* Returns true if the a sequence of chars is one of "true","false","on","off","yes","no","0","1".
*
@ -266,4 +193,74 @@ public abstract class AbstractXContentParser implements XContentParser {
return length == 5 && (text[offset] == 'f' && text[offset + 1] == 'a' && text[offset + 2] == 'l'
&& text[offset + 3] == 's' && text[offset + 4] == 'e');
}
private Map<String, Object> readMap(XContentParser parser) throws IOException {
return readMap(parser, getMapFactory());
}
private Map<String, Object> readOrderedMap(XContentParser parser) throws IOException {
return readMap(parser, getOrderedMapFactory());
}
private static Map<String, Object> readMap(XContentParser parser, MapFactory mapFactory) throws IOException {
Map<String, Object> map = mapFactory.newMap();
XContentParser.Token t = parser.currentToken();
if (t == null) {
t = parser.nextToken();
}
if (t == XContentParser.Token.START_OBJECT) {
t = parser.nextToken();
}
for (; t == XContentParser.Token.FIELD_NAME; t = parser.nextToken()) {
String fieldName = parser.currentName();
t = parser.nextToken();
Object value = readValue(parser, mapFactory, t);
map.put(fieldName, value);
}
return map;
}
private static List<Object> readList(XContentParser parser, MapFactory mapFactory) throws IOException {
ArrayList<Object> list = new ArrayList<>();
Token t;
while ((t = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
list.add(readValue(parser, mapFactory, t));
}
return list;
}
private static Object readValue(XContentParser parser, MapFactory mapFactory, XContentParser.Token t) throws IOException {
if (t == XContentParser.Token.VALUE_NULL) {
return null;
} else if (t == XContentParser.Token.VALUE_STRING) {
if (parser.isBase16Checks()) {
return XContentHelper.parseBase16(parser.text());
}
return parser.text();
} else if (t == XContentParser.Token.VALUE_NUMBER) {
XContentParser.NumberType numberType = parser.numberType();
if (numberType == XContentParser.NumberType.INT) {
return parser.isLosslessDecimals() ? parser.bigIntegerValue() : parser.intValue();
} else if (numberType == XContentParser.NumberType.LONG) {
return parser.isLosslessDecimals() ? parser.bigIntegerValue() : parser.longValue();
} else if (numberType == XContentParser.NumberType.FLOAT) {
return parser.isLosslessDecimals() ? parser.bigDecimalValue() : parser.floatValue();
} else if (numberType == XContentParser.NumberType.DOUBLE) {
return parser.isLosslessDecimals() ? parser.bigDecimalValue() : parser.doubleValue();
} else if (numberType == NumberType.BIG_INTEGER) {
return parser.bigIntegerValue();
} else if (numberType == NumberType.BIG_DECIMAL) {
return parser.bigDecimalValue();
}
} else if (t == XContentParser.Token.VALUE_BOOLEAN) {
return parser.booleanValue();
} else if (t == XContentParser.Token.START_OBJECT) {
return readMap(parser, mapFactory);
} else if (t == XContentParser.Token.START_ARRAY) {
return readList(parser, mapFactory);
} else if (t == XContentParser.Token.VALUE_EMBEDDED_OBJECT) {
return parser.binaryValue();
}
return null;
}
}

View file

@ -0,0 +1,8 @@
package org.xbib.content.core;
import java.util.Map;
@FunctionalInterface
public interface MapFactory {
Map<String, Object> newMap();
}

View file

@ -10,8 +10,6 @@ import java.util.Set;
*/
public class JsonSettingsLoader extends AbstractSettingsLoader {
private static final Set<String> JSON_SUFFIXES = Set.of("json");
@Override
public XContent content() {
return JsonXContent.jsonContent();
@ -19,7 +17,7 @@ public class JsonSettingsLoader extends AbstractSettingsLoader {
@Override
public Set<String> suffixes() {
return JSON_SUFFIXES;
return Set.of("json");
}
@Override

View file

@ -5,10 +5,13 @@ import com.fasterxml.jackson.core.JsonToken;
import org.xbib.content.core.AbstractXContentParser;
import org.xbib.content.XContent;
import org.xbib.content.XContentParser;
import org.xbib.content.core.MapFactory;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.HashMap;
import java.util.LinkedHashMap;
public class JsonXContentParser extends AbstractXContentParser {
@ -53,6 +56,16 @@ public class JsonXContentParser extends AbstractXContentParser {
return parser.getCurrentName();
}
@Override
protected MapFactory getMapFactory() {
return HashMap::new;
}
@Override
protected MapFactory getOrderedMapFactory() {
return LinkedHashMap::new;
}
@Override
protected boolean doBooleanValue() throws IOException {
return parser.getBooleanValue();

View file

@ -0,0 +1,4 @@
dependencies {
api project(':content-settings-datastructures')
api "org.xbib:datastructures-json-tiny:${project.property('xbib-datastructures.version')}"
}

View file

@ -0,0 +1,9 @@
import org.xbib.content.settings.datastructures.SettingsLoader;
import org.xbib.content.settings.datastructures.json.JsonSettingsLoader;
module org.xbib.content.settings.datastructures.json {
exports org.xbib.content.settings.datastructures.json;
requires transitive org.xbib.content.settings.datastructures;
requires org.xbib.datastructures.json.tiny;
provides SettingsLoader with JsonSettingsLoader;
}

View file

@ -0,0 +1,24 @@
package org.xbib.content.settings.datastructures.json;
import org.xbib.content.settings.datastructures.AbstractSettingsLoader;
import org.xbib.datastructures.api.DataStructure;
import org.xbib.datastructures.json.tiny.Json;
import java.util.Set;
public class JsonSettingsLoader extends AbstractSettingsLoader {
@Override
public DataStructure dataStructure() {
return new Json();
}
@Override
public Set<String> suffixes() {
return Set.of("json");
}
@Override
public boolean canLoad(String source) {
return source.indexOf('{') != -1 && source.indexOf('}') != -1;
}
}

View file

@ -0,0 +1,4 @@
/**
* JSON settings with the datastructures package.
*/
package org.xbib.content.settings.datastructures.json;

View file

@ -0,0 +1 @@
org.xbib.content.settings.datastructures.json.JsonSettingsLoader

View file

@ -0,0 +1,67 @@
package org.xbib.content.settings.datastructures.json.test;
import org.junit.jupiter.api.Test;
import org.xbib.content.settings.datastructures.Settings;
import org.xbib.content.settings.datastructures.SettingsLoader;
import org.xbib.content.settings.datastructures.json.JsonSettingsLoader;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class JsonSettingsTest {
@Test
public void testMapForSettings() throws IOException {
Map<String, String> map = new HashMap<>();
map.put("hello", "world");
Map<String, Object> settingsMap = new HashMap<>();
settingsMap.put("map", map);
SettingsLoader settingsLoader = new JsonSettingsLoader();
Settings settings = Settings.settingsBuilder()
.put(settingsLoader.load(settingsMap))
.build();
assertEquals("{map.hello=world}", settings.getAsMap().toString());
}
@Test
public void testMapSettingsFromReader() throws IOException {
Map<String, Object> map = Map.of("map", Map.of("hello", "world"));
SettingsLoader settingsLoader = new JsonSettingsLoader();
Settings settings = Settings.settingsBuilder()
.put(settingsLoader.load(map))
.build();
assertEquals("{map.hello=world}", settings.getAsMap().toString());
}
@Test
public void testFlatLoader() throws IOException {
String s = "{\"a\":{\"b\":\"c\"}}";
SettingsLoader loader = new JsonSettingsLoader();
Map<String, String> flatMap = loader.load(s);
assertEquals("{a.b=c}", flatMap.toString());
}
@Test
public void testLoadFromMap() throws IOException {
Map<String, Object> map = new LinkedHashMap<>();
Map<String, Object> code = new LinkedHashMap<>();
code.put("a", "b");
code.put("b", "c");
Map<String, Object> name = new LinkedHashMap<>();
name.put("a", "b");
name.put("b", "c");
List<String> list = Arrays.asList("a", "b");
map.put("code", code);
map.put("name", name);
map.put("list", list);
map.put("null", null);
SettingsLoader loader = new JsonSettingsLoader();
Map<String, String> result = loader.load(map);
assertEquals("{code.a=b, code.b=c, name.a=b, name.b=c, list.0=a, list.1=b, null=null}", result.toString());
}
}

View file

@ -0,0 +1,4 @@
/**
* Testing JSON settings with the datastructures package.
*/
package org.xbib.content.settings.datastructures.json.test;

View file

@ -0,0 +1,4 @@
dependencies {
api project(':content-settings-datastructures')
api "org.xbib:datastructures-yaml-tiny:${project.property('xbib-datastructures.version')}"
}

View file

@ -0,0 +1,9 @@
import org.xbib.content.settings.datastructures.SettingsLoader;
import org.xbib.content.settings.datastructures.yaml.YamlSettingsLoader;
module org.xbib.content.settings.datastructures.yaml {
exports org.xbib.content.settings.datastructures.yaml;
requires transitive org.xbib.content.settings.datastructures;
requires org.xbib.datastructures.yaml.tiny;
provides SettingsLoader with YamlSettingsLoader;
}

View file

@ -0,0 +1,33 @@
package org.xbib.content.settings.datastructures.yaml;
import org.xbib.content.settings.datastructures.AbstractSettingsLoader;
import org.xbib.datastructures.api.DataStructure;
import org.xbib.datastructures.yaml.tiny.Yaml;
import java.io.IOException;
import java.util.Map;
import java.util.Set;
public class YamlSettingsLoader extends AbstractSettingsLoader {
@Override
public DataStructure dataStructure() {
return new Yaml();
}
@Override
public Set<String> suffixes() {
return Set.of("yml", "yaml");
}
@Override
public boolean canLoad(String source) {
return source.indexOf(':') != -1;
}
@Override
public Map<String, String> load(String source) throws IOException {
// replace tabs with whitespace (yaml does not accept tabs, but many users might use it still...)
return super.load(source.replace("\t", " "));
}
}

View file

@ -0,0 +1,4 @@
/**
* YAML settings with the datastructures package.
*/
package org.xbib.content.settings.datastructures.yaml;

View file

@ -0,0 +1 @@
org.xbib.content.settings.datastructures.yaml.YamlSettingsLoader

View file

@ -0,0 +1,67 @@
package org.xbib.content.settings.datastructures.yaml.test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
import org.xbib.content.settings.datastructures.Settings;
import org.xbib.content.settings.datastructures.SettingsLoader;
import org.xbib.content.settings.datastructures.yaml.YamlSettingsLoader;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
public class YamlSettingsTest {
@Test
public void testMapForSettings() throws IOException {
Map<String, String> map = new HashMap<>();
map.put("hello", "world");
Map<String, Object> settingsMap = new HashMap<>();
settingsMap.put("map", map);
SettingsLoader settingsLoader = new YamlSettingsLoader();
Settings settings = Settings.settingsBuilder()
.put(settingsLoader.load(settingsMap))
.build();
assertEquals("{map.hello=world}", settings.getAsMap().toString());
}
@Test
public void testMapSettingsFromReader() throws IOException {
Map<String, Object> map = Map.of("map", Map.of("hello", "world"));
SettingsLoader settingsLoader = new YamlSettingsLoader();
Settings settings = Settings.settingsBuilder()
.put(settingsLoader.load(map))
.build();
assertEquals("{map.hello=world}", settings.getAsMap().toString());
}
@Test
public void testFlatLoader() throws IOException {
String s = "a:\n b: c\n";
SettingsLoader loader = new YamlSettingsLoader();
Map<String, String> flatMap = loader.load(s);
assertEquals("{a.b=c}", flatMap.toString());
}
@Test
public void testLoadFromMap() throws IOException {
Map<String, Object> map = new LinkedHashMap<>();
Map<String, Object> code = new LinkedHashMap<>();
code.put("a", "b");
code.put("b", "c");
Map<String, Object> name = new LinkedHashMap<>();
name.put("a", "b");
name.put("b", "c");
List<String> list = Arrays.asList("a", "b");
map.put("code", code);
map.put("name", name);
map.put("list", list);
map.put("null", null);
SettingsLoader loader = new YamlSettingsLoader();
Map<String, String> result = loader.load(map);
assertEquals("{code.a=b, code.b=c, name.a=b, name.b=c, list.0=a, list.1=b, null=null}", result.toString());
}
}

View file

@ -0,0 +1,4 @@
/**
* Testing YAML settings with the datastructures package.
*/
package org.xbib.content.settings.datastructures.yaml.test;

View file

@ -0,0 +1,4 @@
dependencies {
api "org.xbib:datastructures-api:${project.property('xbib-datastructures.version')}"
api "org.xbib:datastructures-tiny:${project.property('xbib-datastructures.version')}"
}

View file

@ -0,0 +1,11 @@
import org.xbib.content.settings.datastructures.PropertiesSettingsLoader;
import org.xbib.content.settings.datastructures.SettingsLoader;
module org.xbib.content.settings.datastructures {
uses SettingsLoader;
provides SettingsLoader with PropertiesSettingsLoader;
exports org.xbib.content.settings.datastructures;
requires org.xbib.datastructures.tiny;
requires transitive org.xbib.datastructures.api;
requires transitive java.sql;
}

View file

@ -0,0 +1,76 @@
package org.xbib.content.settings.datastructures;
import org.xbib.datastructures.api.Builder;
import org.xbib.datastructures.api.DataStructure;
import org.xbib.datastructures.api.ListNode;
import org.xbib.datastructures.api.MapNode;
import org.xbib.datastructures.api.Node;
import org.xbib.datastructures.api.Parser;
import org.xbib.datastructures.api.ValueNode;
import org.xbib.datastructures.tiny.TinyMap;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public abstract class AbstractSettingsLoader implements SettingsLoader {
public abstract DataStructure dataStructure();
@Override
public Map<String, String> load(Map<String, Object> map) throws IOException {
Builder builder = dataStructure().createBuilder();
builder.buildMap(map);
return load(builder.build());
}
@Override
public Map<String, String> load(String source) throws IOException {
Parser parser = dataStructure().createParser();
return load(parser, new StringReader(source));
}
private Map<String, String> load(Parser parser, Reader reader) throws IOException {
List<CharSequence> path = new ArrayList<>();
TinyMap.Builder<String, String> map = TinyMap.builder();
Node<?> node = parser.parse(reader);
parseObject(node, map, path, null);
return map.build();
}
private void parseObject(Node<?> node,
TinyMap.Builder<String, String> map,
List<CharSequence> path,
CharSequence name) {
if (node instanceof ValueNode) {
ValueNode valueNode = (ValueNode) node;
StringBuilder sb = new StringBuilder();
for (CharSequence s : path) {
sb.append(s).append('.');
}
sb.append(name);
Object object = valueNode.get();
map.put(sb.toString(), object != null ? object.toString() : null);
} else if (node instanceof ListNode) {
ListNode listNode = (ListNode) node;
int counter = 0;
for (Node<?> nn : listNode.get()) {
parseObject(nn, map, path, name + "." + (counter++));
}
} else if (node instanceof MapNode) {
if (name != null) {
path.add(name);
}
MapNode mapNode = (MapNode) node;
for (Map.Entry<CharSequence, Node<?>> me : mapNode.get().entrySet()) {
parseObject(me.getValue(), map, path, me.getKey());
}
if (name != null) {
path.remove(path.size() - 1);
}
}
}
}

View file

@ -1,4 +1,4 @@
package org.xbib.content.config;
package org.xbib.content.settings.datastructures;
import java.io.PrintWriter;
import java.io.StringWriter;

View file

@ -0,0 +1,16 @@
package org.xbib.content.settings.datastructures;
/**
* Strategy interface used to resolve replacement values for placeholders contained in Strings.
*/
@FunctionalInterface
public interface PlaceholderResolver {
/**
* Resolves the supplied placeholder name into the replacement value.
*
* @param placeholderName the name of the placeholder to resolve.
* @return the replacement value or <code>null</code> if no replacement is to be made.
*/
String resolvePlaceholder(String placeholderName);
}

View file

@ -0,0 +1,49 @@
package org.xbib.content.settings.datastructures;
import org.xbib.datastructures.tiny.TinyMap;
import java.io.IOException;
import java.io.StringReader;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
/**
* Settings loader that loads (parses) the settings in a properties format.
*/
public class PropertiesSettingsLoader implements SettingsLoader {
@Override
public Set<String> suffixes() {
return Set.of("properties");
}
@Override
public Map<String, String> load(String source) throws IOException {
Properties props = new Properties();
try (StringReader reader = new StringReader(source)) {
props.load(reader);
TinyMap.Builder<String, String> result = TinyMap.builder();
for (Map.Entry<Object, Object> entry : props.entrySet()) {
result.put((String) entry.getKey(), (String) entry.getValue());
}
return result.build();
}
}
@Override
public Map<String, String> load(Map<String, Object> source) {
Properties props = new Properties();
props.putAll(source);
TinyMap.Builder<String, String> result = TinyMap.builder();
for (Map.Entry<Object, Object> entry : props.entrySet()) {
result.put((String) entry.getKey(), (String) entry.getValue());
}
return result.build();
}
@Override
public boolean canLoad(String source) {
return true;
}
}

View file

@ -0,0 +1,118 @@
package org.xbib.content.settings.datastructures;
import java.util.HashSet;
import java.util.Set;
/**
*
*/
public class PropertyPlaceholder {
private final String placeholderPrefix;
private final String placeholderSuffix;
private final boolean ignoreUnresolvablePlaceholders;
/**
* Creates a new <code>PropertyPlaceholderHelper</code> that uses the supplied prefix and suffix.
*
* @param placeholderPrefix the prefix that denotes the start of a placeholder.
* @param placeholderSuffix the suffix that denotes the end of a placeholder.
* @param ignoreUnresolvablePlaceholders indicates whether unresolvable placeholders should be ignored
* (<code>true</code>) or cause an exception (<code>false</code>).
*/
public PropertyPlaceholder(String placeholderPrefix,
String placeholderSuffix,
boolean ignoreUnresolvablePlaceholders) {
this.placeholderPrefix = placeholderPrefix;
this.placeholderSuffix = placeholderSuffix;
this.ignoreUnresolvablePlaceholders = ignoreUnresolvablePlaceholders;
}
/**
* Replaces all placeholders of format <code>${name}</code> with the value returned from the supplied {@link
* PlaceholderResolver}.
*
* @param value the value containing the placeholders to be replaced.
* @param placeholderResolver the <code>PlaceholderResolver</code> to use for replacement.
* @return the supplied value with placeholders replaced inline.
*/
public String replacePlaceholders(String value,
PlaceholderResolver placeholderResolver) {
return parseStringValue(value, placeholderResolver, new HashSet<>());
}
protected String parseStringValue(String value,
PlaceholderResolver placeholderResolver,
Set<String> visitedPlaceholders) {
StringBuilder sb = new StringBuilder(value);
int startIndex = value.indexOf(this.placeholderPrefix);
while (startIndex != -1) {
int endIndex = findPlaceholderEndIndex(sb, startIndex);
if (endIndex != -1) {
String placeholder = sb.substring(startIndex + this.placeholderPrefix.length(), endIndex);
if (!visitedPlaceholders.add(placeholder)) {
throw new IllegalArgumentException(
"Circular placeholder reference '" + placeholder + "' in property definitions");
}
placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);
int defaultValueIdx = placeholder.indexOf(':');
String defaultValue = null;
if (defaultValueIdx != -1) {
defaultValue = placeholder.substring(defaultValueIdx + 1);
placeholder = placeholder.substring(0, defaultValueIdx);
}
String propVal = placeholderResolver.resolvePlaceholder(placeholder);
if (propVal == null) {
propVal = defaultValue;
}
if (propVal != null) {
propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders);
sb.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal);
startIndex = sb.indexOf(this.placeholderPrefix, startIndex + propVal.length());
} else if (this.ignoreUnresolvablePlaceholders) {
// Proceed with unprocessed value.
startIndex = sb.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length());
} else {
throw new IllegalArgumentException("could not resolve placeholder '" + placeholder + "'");
}
visitedPlaceholders.remove(placeholder);
} else {
startIndex = -1;
}
}
return sb.toString();
}
private int findPlaceholderEndIndex(CharSequence charSequence, int startIndex) {
int index = startIndex + this.placeholderPrefix.length();
int withinNestedPlaceholder = 0;
while (index < charSequence.length()) {
if (substringMatch(charSequence, index, this.placeholderSuffix)) {
if (withinNestedPlaceholder > 0) {
withinNestedPlaceholder--;
index = index + this.placeholderPrefix.length() - 1;
} else {
return index;
}
} else if (substringMatch(charSequence, index, this.placeholderPrefix)) {
withinNestedPlaceholder++;
index = index + this.placeholderPrefix.length();
} else {
index++;
}
}
return -1;
}
private boolean substringMatch(CharSequence charSequence, int index, CharSequence substring) {
for (int j = 0; j < substring.length(); j++) {
int i = index + j;
if (i >= charSequence.length() || charSequence.charAt(i) != substring.charAt(j)) {
return false;
}
}
return true;
}
}

View file

@ -0,0 +1,656 @@
package org.xbib.content.settings.datastructures;
import org.xbib.datastructures.api.ByteSizeValue;
import org.xbib.datastructures.api.TimeValue;
import org.xbib.datastructures.tiny.TinyMap;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.time.DateTimeException;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class Settings implements AutoCloseable {
public static final Settings EMPTY_SETTINGS = new Builder().build();
public static final String[] EMPTY_ARRAY = new String[0];
private final TinyMap<String, String> map;
private Settings(TinyMap<String, String> map) {
this.map = map;
}
public static Settings fromMap(Map<String, Object> map) {
Builder builder = new Builder();
for (Map.Entry<String, Object> entry : map.entrySet()) {
builder.put(entry.getKey(), entry.getValue() != null ? entry.getValue().toString() : null);
}
return builder.build();
}
public static void toMap(Settings settings, Map<String, Object> map) {
for (String key : settings.getAsMap().keySet()) {
map.put(key, settings.get(key));
}
}
/**
* Returns a builder to be used in order to build settings.
* @return a builder
*/
public static Builder settingsBuilder() {
return new Builder();
}
public static String[] splitStringByCommaToArray(final String s) {
return splitStringToArray(s, ',');
}
public static String[] splitStringToArray(final String s, final char c) {
if (s.length() == 0) {
return EMPTY_ARRAY;
}
final char[] chars = s.toCharArray();
int count = 1;
for (final char x : chars) {
if (x == c) {
count++;
}
}
final String[] result = new String[count];
final int len = chars.length;
int start = 0;
int pos = 0;
int i = 0;
for (; pos < len; pos++) {
if (chars[pos] == c) {
int size = pos - start;
if (size > 0) {
result[i++] = new String(chars, start, size);
}
start = pos + 1;
}
}
int size = pos - start;
if (size > 0) {
result[i++] = new String(chars, start, size);
}
if (i != count) {
String[] result1 = new String[i];
System.arraycopy(result, 0, result1, 0, i);
return result1;
}
return result;
}
public Map<String, String> getAsMap() {
return this.map;
}
public Map<String, Object> getAsStructuredMap() {
TinyMap.Builder<String, Object> stringObjectMap = TinyMap.builder();
for (String key : map.keySet()) {
String value = map.get(key);
processSetting(stringObjectMap, "", key, value);
}
for (String key : stringObjectMap.keySet()) {
Object object = stringObjectMap.get(key);
if (object instanceof Map) {
@SuppressWarnings("unchecked")
Map<String, Object> valMap = (Map<String, Object>) object;
stringObjectMap.put(key, convertMapsToArrays(valMap));
}
}
return stringObjectMap.build();
}
public Settings getByPrefix(String prefix) {
Builder builder = new Builder();
for (String key : map.keySet()) {
String value = map.get(key);
if (key.startsWith(prefix)) {
if (key.length() < prefix.length()) {
continue;
}
builder.put(key.substring(prefix.length()), value);
}
}
return builder.build();
}
public Settings getAsSettings(String setting) {
return getByPrefix(setting + ".");
}
public boolean containsSetting(String setting) {
if (map.containsKey(setting)) {
return true;
}
for (String key : map.keySet()) {
if (key.startsWith(setting)) {
return true;
}
}
return false;
}
public String get(String setting) {
return map.get(setting);
}
public String get(String setting, String defaultValue) {
String s = map.get(setting);
return s == null ? defaultValue : s;
}
public Float getAsFloat(String setting, Float defaultValue) {
String s = get(setting);
try {
return s == null ? defaultValue : Float.parseFloat(s);
} catch (NumberFormatException e) {
throw new SettingsException("Failed to parse float setting [" + setting + "] with value [" + s + "]", e);
}
}
public Double getAsDouble(String setting, Double defaultValue) {
String s = get(setting);
try {
return s == null ? defaultValue : Double.parseDouble(s);
} catch (NumberFormatException e) {
throw new SettingsException("Failed to parse double setting [" + setting + "] with value [" + s + "]", e);
}
}
public Integer getAsInt(String setting, Integer defaultValue) {
String s = get(setting);
try {
return s == null ? defaultValue : Integer.parseInt(s);
} catch (NumberFormatException e) {
throw new SettingsException("Failed to parse int setting [" + setting + "] with value [" + s + "]", e);
}
}
public Long getAsLong(String setting, Long defaultValue) {
String s = get(setting);
try {
return s == null ? defaultValue : Long.parseLong(s);
} catch (NumberFormatException e) {
throw new SettingsException("Failed to parse long setting [" + setting + "] with value [" + s + "]", e);
}
}
public Boolean getAsBoolean(String setting, Boolean defaultValue) {
String value = get(setting);
if (value == null) {
return defaultValue;
}
return !("false".equals(value) || "0".equals(value) || "off".equals(value) || "no".equals(value));
}
public TimeValue getAsTime(String setting, TimeValue defaultValue) {
return TimeValue.parseTimeValue(get(setting), defaultValue);
}
public ByteSizeValue getAsBytesSize(String setting, ByteSizeValue defaultValue) {
return ByteSizeValue.parseBytesSizeValue(get(setting), defaultValue);
}
public String[] getAsArray(String settingPrefix) {
return getAsArray(settingPrefix, EMPTY_ARRAY);
}
public String[] getAsArray(String settingPrefix, String[] defaultArray) {
List<String> result = new ArrayList<>();
if (get(settingPrefix) != null) {
String[] strings = splitStringByCommaToArray(get(settingPrefix));
if (strings.length > 0) {
for (String string : strings) {
result.add(string.trim());
}
}
}
int counter = 0;
while (true) {
String value = get(settingPrefix + '.' + (counter++));
if (value == null) {
break;
}
result.add(value.trim());
}
if (result.isEmpty()) {
return defaultArray;
}
return result.toArray(new String[0]);
}
public Map<String, Settings> getGroups(String prefix) {
String settingPrefix = prefix;
if (settingPrefix.charAt(settingPrefix.length() - 1) != '.') {
settingPrefix = settingPrefix + ".";
}
// we don't really care that it might happen twice
TinyMap.Builder<String, TinyMap.Builder<String, String>> hashMap = TinyMap.builder();
for (String o : this.map.keySet()) {
if (o.startsWith(settingPrefix)) {
String nameValue = o.substring(settingPrefix.length());
int dotIndex = nameValue.indexOf('.');
if (dotIndex == -1) {
throw new SettingsException("failed to get setting group for ["
+ settingPrefix
+ "] setting prefix and setting [" + o + "] because of a missing '.'");
}
String name = nameValue.substring(0, dotIndex);
String value = nameValue.substring(dotIndex + 1);
Map<String, String> groupSettings = hashMap.computeIfAbsent(name, k -> TinyMap.builder());
groupSettings.put(value, get(o));
}
}
TinyMap.Builder<String, Settings> retVal = TinyMap.builder();
for (String key : hashMap.keySet()) {
TinyMap.Builder<String, String> value = hashMap.get(key);
retVal.put(key, new Settings(value.build()));
}
return retVal.build();
}
@Override
public boolean equals(Object o) {
return this == o || !(o == null || getClass() != o.getClass()) && map.equals(((Settings) o).map);
}
@Override
public int hashCode() {
return map.hashCode();
}
private void processSetting(Map<String, Object> map, String prefix, String setting, String value) {
int prefixLength = setting.indexOf('.');
if (prefixLength == -1) {
@SuppressWarnings("unchecked")
Map<String, Object> innerMap = (Map<String, Object>) map.get(prefix + setting);
if (innerMap != null) {
for (String k : innerMap.keySet()) {
Object v = innerMap.get(k);
map.put(prefix + setting + "." + k, v);
}
}
map.put(prefix + setting, value);
} else {
String key = setting.substring(0, prefixLength);
String rest = setting.substring(prefixLength + 1);
Object existingValue = map.get(prefix + key);
if (existingValue == null) {
Map<String, Object> newMap = TinyMap.builder();
processSetting(newMap, "", rest, value);
map.put(key, newMap);
} else {
if (existingValue instanceof Map) {
@SuppressWarnings("unchecked")
Map<String, Object> innerMap = (Map<String, Object>) existingValue;
processSetting(innerMap, "", rest, value);
map.put(key, innerMap);
} else {
processSetting(map, prefix + key + ".", rest, value);
}
}
}
}
private Object convertMapsToArrays(Map<String, Object> map) {
if (map.isEmpty()) {
return map;
}
boolean isArray = true;
int maxIndex = -1;
for (String key : map.keySet()) {
Object value = map.get(key);
if (isArray) {
try {
int index = Integer.parseInt(key);
if (index >= 0) {
maxIndex = Math.max(maxIndex, index);
} else {
isArray = false;
}
} catch (NumberFormatException ex) {
isArray = false;
}
}
if (value instanceof Map) {
@SuppressWarnings("unchecked")
Map<String, Object> valMap = (Map<String, Object>) value;
map.put(key, convertMapsToArrays(valMap));
}
}
if (isArray && (maxIndex + 1) == map.size()) {
ArrayList<Object> newValue = new ArrayList<>(maxIndex + 1);
for (int i = 0; i <= maxIndex; i++) {
Object obj = map.get(Integer.toString(i));
if (obj == null) {
return map;
}
newValue.add(obj);
}
return newValue;
}
return map;
}
public boolean isEmpty() {
return map.isEmpty();
}
@Override
public void close() {
}
/**
*
*/
public static class Builder {
private final SettingsLoaderService settingsLoaderService = SettingsLoaderService.getInstance();
private final TinyMap.Builder<String, String> map;
private Builder() {
map = TinyMap.builder();
}
public Map<String, String> map() {
return map;
}
public String remove(String key) {
return map.remove(key);
}
public String get(String key) {
return map.get(key);
}
public boolean isEmpty() {
return map.isEmpty();
}
/**
* Sets a setting with the provided setting key and value.
*
* @param key The setting key
* @param value The setting value
* @return The builder
*/
public Builder put(String key, String value) {
map.put(key, value);
return this;
}
/**
* Sets a setting with the provided setting key and class as value.
*
* @param key The setting key
* @param clazz The setting class value
* @return The builder
*/
public Builder put(String key, Class<?> clazz) {
map.put(key, clazz.getName());
return this;
}
/**
* Sets the setting with the provided setting key and the boolean value.
*
* @param setting The setting key
* @param value The boolean value
* @return The builder
*/
public Builder put(String setting, boolean value) {
put(setting, String.valueOf(value));
return this;
}
/**
* Sets the setting with the provided setting key and the int value.
*
* @param setting The setting key
* @param value The int value
* @return The builder
*/
public Builder put(String setting, int value) {
put(setting, String.valueOf(value));
return this;
}
/**
* Sets the setting with the provided setting key and the long value.
*
* @param setting The setting key
* @param value The long value
* @return The builder
*/
public Builder put(String setting, long value) {
put(setting, String.valueOf(value));
return this;
}
/**
* Sets the setting with the provided setting key and the float value.
*
* @param setting The setting key
* @param value The float value
* @return The builder
*/
public Builder put(String setting, float value) {
put(setting, String.valueOf(value));
return this;
}
/**
* Sets the setting with the provided setting key and the double value.
*
* @param setting The setting key
* @param value The double value
* @return The builder
*/
public Builder put(String setting, double value) {
put(setting, String.valueOf(value));
return this;
}
/**
* Sets the setting with the provided setting key and an array of values.
*
* @param setting The setting key
* @param values The values
* @return The builder
*/
public Builder putArray(String setting, String... values) {
remove(setting);
int counter = 0;
while (true) {
String value = map.remove(setting + '.' + (counter++));
if (value == null) {
break;
}
}
for (int i = 0; i < values.length; i++) {
put(setting + '.' + i, values[i]);
}
return this;
}
/**
* Sets the setting with the provided setting key and an array of values.
*
* @param setting The setting key
* @param values The values
* @return The builder
*/
public Builder putArray(String setting, List<String> values) {
remove(setting);
int counter = 0;
while (true) {
String value = map.remove(setting + '.' + (counter++));
if (value == null) {
break;
}
}
for (int i = 0; i < values.size(); i++) {
put(setting + '.' + i, values.get(i));
}
return this;
}
/**
* Sets the setting group.
* @param settingPrefix setting prefix
* @param groupName group name
* @param settings settings
* @param values values
* @return a builder
* @throws SettingsException if setting fails
*/
public Builder put(String settingPrefix, String groupName, String[] settings, String[] values)
throws SettingsException {
if (settings.length != values.length) {
throw new SettingsException("the settings length must match the value length");
}
for (int i = 0; i < settings.length; i++) {
if (values[i] == null) {
continue;
}
put(settingPrefix + "" + groupName + "." + settings[i], values[i]);
}
return this;
}
/**
* Sets all the provided settings.
* @param settings settings
* @return builder
*/
public Builder put(Settings settings) {
map.putAll(settings.getAsMap());
return this;
}
/**
* Sets all the provided settings.
*
* @param settings settings
* @return a builder
*/
public Builder put(Map<String, String> settings) {
map.putAll(settings);
return this;
}
/**
* Loads settings from the actual string content that represents them using the
* {@link SettingsLoaderService#loaderFromString(String)}.
*
* @param source source
* @return builder
*/
public Builder loadFromString(String source) {
SettingsLoader settingsLoader = settingsLoaderService.loaderFromString(source);
try {
Map<String, String> loadedSettings = settingsLoader.load(source);
put(loadedSettings);
} catch (Exception e) {
throw new SettingsException("failed to load settings from [" + source + "]", e);
}
return this;
}
/**
* Loads settings from a resource.
* @param resourceName resource name
* @param inputStream input stream
* @return builder
*/
public Builder loadFromResource(String resourceName, InputStream inputStream) throws SettingsException {
SettingsLoader settingsLoader = settingsLoaderService.loaderFromResource(resourceName);
try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
Map<String, String> loadedSettings = settingsLoader.load(bufferedReader.lines().collect(Collectors.joining()));
put(loadedSettings);
} catch (Exception e) {
throw new SettingsException("failed to load settings from [" + resourceName + "]", e);
}
return this;
}
/**
* Load system properties to this settings.
* @return builder
*/
public Builder loadFromSystemProperties() {
for (Map.Entry<Object, Object> entry : System.getProperties().entrySet()) {
put((String) entry.getKey(), (String) entry.getValue());
}
return this;
}
/**
* Load system environment to this settings.
* @return builder
*/
public Builder loadFromSystemEnvironment() {
for (Map.Entry<String, String> entry : System.getenv().entrySet()) {
put(entry.getKey(), entry.getValue());
}
return this;
}
public Builder replacePropertyPlaceholders(PropertyPlaceholder propertyPlaceholder,
PlaceholderResolver placeholderResolver) {
map.replaceAll((k, v) -> propertyPlaceholder.replacePlaceholders(v, placeholderResolver));
return this;
}
/**
* Runs across all the settings set on this builder and replaces {@code ${...}} elements in the
* each setting value according to the following logic:
*
* First, tries to resolve it against a System property ({@link System#getProperty(String)}), next,
* tries and resolve it against an environment variable ({@link System#getenv(String)}), next,
* tries and resolve it against a date pattern to resolve the current date,
* and last, tries and replace it with another setting already set on this builder.
* @return builder
*/
public Builder replacePropertyPlaceholders() {
return replacePropertyPlaceholders(new PropertyPlaceholder("${", "}", false),
placeholderName -> {
// system property
String value = System.getProperty(placeholderName);
if (value != null) {
return value;
}
// environment
value = System.getenv(placeholderName);
if (value != null) {
return value;
}
// current date
try {
return DateTimeFormatter.ofPattern(placeholderName).format(LocalDate.now());
} catch (IllegalArgumentException | DateTimeException e) {
return map.get(placeholderName);
}
}
);
}
public Settings build() {
return new Settings(map.build());
}
}
}

View file

@ -0,0 +1,16 @@
package org.xbib.content.settings.datastructures;
/**
* A generic failure to handle settings.
*/
@SuppressWarnings("serial")
public class SettingsException extends RuntimeException {
public SettingsException(String message) {
super(message);
}
public SettingsException(String message, Throwable cause) {
super(message, cause);
}
}

View file

@ -0,0 +1,30 @@
package org.xbib.content.settings.datastructures;
import java.io.IOException;
import java.util.Map;
import java.util.Set;
/**
* Provides the ability to load settings from
* the actual source content that represents them.
*/
public interface SettingsLoader {
/**
* Suffices for file names to load from.
* @return a set of suffices
*/
Set<String> suffixes();
/**
* Loads the settings from a source string.
* @param source the source
* @return a Map
* @throws IOException if load fails
*/
Map<String, String> load(String source) throws IOException;
Map<String, String> load(Map<String, Object> source) throws IOException;
boolean canLoad(String source);
}

View file

@ -0,0 +1,85 @@
package org.xbib.content.settings.datastructures;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.Set;
/**
* A settings loader service for loading {@link SettingsLoader} implementations.
*/
public final class SettingsLoaderService {
private static final SettingsLoaderService INSTANCE = new SettingsLoaderService();
private final Map<Set<String>, SettingsLoader> settingsLoaderMap;
private SettingsLoaderService() {
this.settingsLoaderMap = new HashMap<>();
ServiceLoader<SettingsLoader> serviceLoader = ServiceLoader.load(SettingsLoader.class);
for (SettingsLoader settingsLoader : serviceLoader) {
settingsLoaderMap.put(settingsLoader.suffixes(), settingsLoader);
}
}
public static SettingsLoaderService getInstance() {
return INSTANCE;
}
/**
* Returns a {@link SettingsLoader} based on the resource name.
* @param resourceName the resource
* @return the settings loader
*/
public SettingsLoader loaderFromResource(String resourceName) {
for (Map.Entry<Set<String>, SettingsLoader> entry : settingsLoaderMap.entrySet()) {
Set<String> suffixes = entry.getKey();
for (String suffix : suffixes) {
if (resourceName.endsWith(suffix)) {
return entry.getValue();
}
}
}
throw new IllegalArgumentException("no settings loader for " + resourceName + " in " + settingsLoaderMap.keySet());
}
/**
* Returns a {@link SettingsLoader} based on the actual source.
* @param source the source
* @return the settings loader
*/
public SettingsLoader loaderFromString(String source) {
for (SettingsLoader loader : settingsLoaderMap.values()) {
if (loader.canLoad(source)) {
return loader;
}
}
throw new IllegalArgumentException("no settings loader");
}
public Set<String> getSuffixes() {
Set<String> suffixes = new HashSet<>();
for (Set<String> set : settingsLoaderMap.keySet()) {
suffixes.addAll(set);
}
return suffixes;
}
public static Settings fromJdbcConfTable(Connection connection, String id, String type) throws SQLException {
Settings.Builder settingsBuilder = Settings.settingsBuilder();
try (PreparedStatement statement = connection.prepareStatement("select key, value from conf where id = ? and type = ?",
new String[]{id, type}); ResultSet resultSet = statement.executeQuery()) {
while (resultSet.next()) {
String key = resultSet.getString("key");
String value = resultSet.getString("value");
settingsBuilder.put(key, value);
}
}
return settingsBuilder.build();
}
}

View file

@ -0,0 +1,4 @@
/**
* Classes for settings using the datastructures API.
*/
package org.xbib.content.settings.datastructures;

View file

@ -0,0 +1 @@
org.xbib.content.settings.datastructures.PropertiesSettingsLoader

View file

@ -0,0 +1,115 @@
package org.xbib.content.settings.datastructures.test;
import org.junit.jupiter.api.Test;
import org.xbib.content.settings.datastructures.Settings;
import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class SettingsTest {
@Test
public void testEmpty() {
Settings settings = Settings.EMPTY_SETTINGS;
assertTrue(settings.getAsMap().isEmpty());
}
@Test
public void testSimpleSettings() {
Settings settings = Settings.settingsBuilder()
.put("a", "b")
.build();
assertEquals("{a=b}", settings.getAsMap().toString());
assertEquals("{a=b}", settings.getAsStructuredMap().toString());
}
@Test
public void testArray() {
Settings settings = Settings.settingsBuilder()
.putArray("input", Arrays.asList("a", "b", "c")).build();
assertEquals("a", settings.getAsArray("input")[0]);
assertEquals("b", settings.getAsArray("input")[1]);
assertEquals("c", settings.getAsArray("input")[2]);
}
@SuppressWarnings("unchecked")
@Test
public void testArrayOfMaps() {
Settings settings = Settings.settingsBuilder()
.put("location.0.code", "Code 0")
.put("location.0.name", "Name 0")
.put("location.1.code", "Code 1")
.put("location.1.name", "Name 1")
.build();
// turn map with index keys 0,1,... into a list of maps
Map<String, Object> map = settings.getAsSettings("location").getAsStructuredMap();
List<Map<String, Object>> list = new ArrayList<>();
for (Map.Entry<String, Object> entry : map.entrySet()) {
list.add((Map<String, Object>) entry.getValue());
}
assertEquals("[{code=Code 0, name=Name 0}, {code=Code 1, name=Name 1}]", list.toString());
}
@Test
public void testGroups() {
Settings settings = Settings.settingsBuilder()
.put("prefix.group1.k1", "v1")
.put("prefix.group1.k2", "v2")
.put("prefix.group1.k3", "v3")
.put("prefix.group2.k1", "v1")
.put("prefix.group2.k2", "v2")
.put("prefix.group2.k3", "v3")
.build();
Map<String, Settings> groups = settings.getGroups("prefix");
assertEquals("[group1, group2]", groups.keySet().toString());
assertTrue(groups.get("group1").getAsMap().containsKey("k1"));
assertTrue(groups.get("group1").getAsMap().containsKey("k2"));
assertTrue(groups.get("group1").getAsMap().containsKey("k3"));
assertTrue(groups.get("group2").getAsMap().containsKey("k1"));
assertTrue(groups.get("group2").getAsMap().containsKey("k2"));
assertTrue(groups.get("group2").getAsMap().containsKey("k3"));
}
@Test
public void testCurrentYearInSettings() {
Settings settings = Settings.settingsBuilder()
.put("date", "${yyyy}")
.replacePropertyPlaceholders()
.build();
assertTrue(Integer.parseInt(settings.get("date")) > 2000);
}
@Test
public void testSystemEnvironment() {
Settings settings = Settings.settingsBuilder()
.loadFromSystemEnvironment()
.build();
assertFalse(settings.getAsMap().isEmpty());
}
@Test
public void testSystemProperties() {
Settings settings = Settings.settingsBuilder()
.loadFromSystemProperties()
.build();
assertFalse(settings.getAsMap().isEmpty());
}
@Test
public void testPropertiesLoader() {
Settings settings = Settings.settingsBuilder()
.loadFromResource(".properties", new ByteArrayInputStream("a.b=c".getBytes(StandardCharsets.UTF_8)))
.build();
assertEquals("{a.b=c}", settings.getAsMap().toString());
}
}

View file

@ -1,4 +1,5 @@
dependencies {
api project(':content-core')
api "org.xbib:datastructures-tiny:${project.property('xbib-datastructures.version')}"
testImplementation project(":content-json")
}

View file

@ -1,4 +1,6 @@
module org.xbib.content.settings {
uses org.xbib.content.SettingsLoader;
exports org.xbib.content.settings;
requires org.xbib.content.core;
requires transitive org.xbib.datastructures.tiny;
}

View file

@ -79,7 +79,6 @@ public abstract class AbstractSettingsLoader implements SettingsLoader {
if (objFieldName != null) {
path.add(objFieldName);
}
String currentFieldName = null;
XContentParser.Token token;
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {

View file

@ -6,9 +6,13 @@ import com.fasterxml.jackson.dataformat.smile.SmileParser;
import org.xbib.content.XContent;
import org.xbib.content.XContentParser;
import org.xbib.content.core.AbstractXContentParser;
import org.xbib.content.core.MapFactory;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.HashMap;
import java.util.LinkedHashMap;
public class SmileXContentParser extends AbstractXContentParser {
@ -53,6 +57,16 @@ public class SmileXContentParser extends AbstractXContentParser {
return parser.getCurrentName();
}
@Override
protected MapFactory getMapFactory() {
return HashMap::new;
}
@Override
protected MapFactory getOrderedMapFactory() {
return LinkedHashMap::new;
}
@Override
protected boolean doBooleanValue() throws IOException {
return parser.getBooleanValue();

View file

@ -5,10 +5,13 @@ import com.fasterxml.jackson.core.JsonToken;
import org.xbib.content.core.AbstractXContentParser;
import org.xbib.content.XContent;
import org.xbib.content.XContentParser;
import org.xbib.content.core.MapFactory;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.HashMap;
import java.util.LinkedHashMap;
/**
*
@ -56,6 +59,16 @@ public class XmlXContentParser extends AbstractXContentParser {
return parser.getCurrentName();
}
@Override
protected MapFactory getMapFactory() {
return HashMap::new;
}
@Override
protected MapFactory getOrderedMapFactory() {
return LinkedHashMap::new;
}
@Override
protected boolean doBooleanValue() throws IOException {
return parser.getBooleanValue();

View file

@ -53,32 +53,27 @@ public class YamlXContent implements XContent {
return new YamlXContentGenerator(yamlFactory.createGenerator(os, JsonEncoding.UTF8));
}
@Override
public XContentGenerator createGenerator(Writer writer) throws IOException {
return new YamlXContentGenerator(yamlFactory.createGenerator(writer));
}
@Override
public XContentParser createParser(String content) throws IOException {
return new YamlXContentParser(yamlFactory
.createParser(content.getBytes(StandardCharsets.UTF_8)));
}
@Override
public XContentParser createParser(InputStream is) throws IOException {
return new YamlXContentParser(yamlFactory.createParser(is));
}
@Override
public XContentParser createParser(byte[] data) throws IOException {
return new YamlXContentParser(yamlFactory.createParser(data));
}
@Override
public XContentParser createParser(byte[] data, int offset, int length) throws IOException {
return new YamlXContentParser(yamlFactory.createParser(data, offset, length));

View file

@ -6,9 +6,13 @@ import com.fasterxml.jackson.dataformat.yaml.YAMLParser;
import org.xbib.content.XContent;
import org.xbib.content.XContentParser;
import org.xbib.content.core.AbstractXContentParser;
import org.xbib.content.core.MapFactory;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.HashMap;
import java.util.LinkedHashMap;
public class YamlXContentParser extends AbstractXContentParser {
@ -54,6 +58,16 @@ public class YamlXContentParser extends AbstractXContentParser {
return parser.getCurrentName();
}
@Override
protected MapFactory getMapFactory() {
return HashMap::new;
}
@Override
protected MapFactory getOrderedMapFactory() {
return LinkedHashMap::new;
}
@Override
protected boolean doBooleanValue() throws IOException {
return parser.getBooleanValue();

View file

@ -1,10 +1,10 @@
group = org.xbib
name = content
version = 3.0.0
version = 4.0.0
gradle.wrapper.version = 6.6.1
xbib.net.version = 2.1.1
xbib-datastructures-tiny.version = 0.1.0
xbib-datastructures.version = 1.0.0
jackson.version = 2.12.3
woodstox.version = 6.2.6
snakeyaml.version = 1.28

View file

@ -28,6 +28,7 @@ task sourcesJar(type: Jar, dependsOn: classes) {
task javadocJar(type: Jar, dependsOn: javadoc) {
classifier 'javadoc'
from javadoc.destinationDir
}
artifacts {

View file

@ -1,5 +1,5 @@
def junitVersion = project.hasProperty('junit.version')?project.property('junit.version'):'5.6.2'
def junitVersion = project.hasProperty('junit.version')?project.property('junit.version'):'5.7.2'
def hamcrestVersion = project.hasProperty('hamcrest.version')?project.property('hamcrest.version'):'2.2'
dependencies {

View file

@ -10,3 +10,6 @@ include 'content-settings'
include 'content-smile'
include 'content-xml'
include 'content-yaml'
include 'content-settings-datastructures'
include 'content-settings-datastructures-json'
include 'content-settings-datastructures-yaml'