blob: 622f20d05be56be5872d4d423f5db48ea5ece43e [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE
* file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file
* to You under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package org.apache.tuweni.config;
import static org.apache.tuweni.toml.Toml.joinKeyPath;
import static org.apache.tuweni.toml.Toml.parseDottedKey;
import org.apache.tuweni.toml.TomlArray;
import org.apache.tuweni.toml.TomlInvalidTypeException;
import org.apache.tuweni.toml.TomlParseResult;
import org.apache.tuweni.toml.TomlPosition;
import org.apache.tuweni.toml.TomlTable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import javax.annotation.Nullable;
final class TomlBackedConfiguration implements Configuration {
private final TomlTable toml;
private final Schema schema;
private final List<ConfigurationError> errors;
TomlBackedConfiguration(TomlParseResult toml, @Nullable Schema schema) {
List<ConfigurationError> errors = new ArrayList<>();
toml.errors().forEach(
err -> errors.add(new ConfigurationError(documentPosition(err.position()), err.getMessage(), err)));
if (schema != null) {
schema.validate(new TomlBackedConfiguration(toml, null)).forEach(errors::add);
} else {
schema = Schema.EMPTY;
}
this.toml = toml;
this.schema = schema;
this.errors = errors;
}
@Override
public List<ConfigurationError> errors() {
return errors;
}
@Override
public void toToml(Appendable appendable) throws IOException {
new TomlSerializer(this, schema).writeTo(appendable);
}
@Override
public Set<String> keySet() {
Set<String> keys = new HashSet<>();
keys.addAll(toml.dottedKeySet());
keys.addAll(schema.defaultsKeySet());
return keys;
}
@Override
public boolean contains(String key) {
return toml.contains(key) || schema.hasDefault(key);
}
@Nullable
@Override
public Object get(String key) {
Object obj = toml.get(key);
if (obj != null) {
if (obj instanceof TomlArray) {
return deepToList((TomlArray) obj);
}
if (obj instanceof TomlTable) {
return deepToMap((TomlTable) obj);
}
return obj;
}
return schema.getDefault(key);
}
@Nullable
@Override
public DocumentPosition inputPositionOf(String key) {
TomlPosition position = toml.inputPositionOf(key);
if (position == null) {
return null;
}
return documentPosition(position);
}
private DocumentPosition inputPositionOf(List<String> keyPath) {
TomlPosition position = toml.inputPositionOf(keyPath);
if (position == null) {
return null;
}
return documentPosition(position);
}
@Override
public String getString(String key) {
return getValue(key, toml::getString, schema::getDefaultString);
}
@Override
public int getInteger(String key) {
return getValue(key, keyPath -> {
Long longValue = toml.getLong(keyPath);
if (longValue != null && longValue > Integer.MAX_VALUE) {
throw new InvalidConfigurationPropertyTypeException(
inputPositionOf(keyPath),
"Value of property '" + joinKeyPath(keyPath) + "' is too large for an integer");
}
return (longValue != null) ? longValue.intValue() : null;
}, schema::getDefaultInteger);
}
@Override
public long getLong(String key) {
return getValue(key, toml::getLong, schema::getDefaultLong);
}
@Override
public double getDouble(String key) {
return getValue(key, toml::getDouble, schema::getDefaultDouble);
}
@Override
public boolean getBoolean(String key) {
return getValue(key, toml::getBoolean, schema::getDefaultBoolean);
}
@Override
public Map<String, Object> getMap(String key) {
return getValue(key, keyPath -> {
TomlTable table = toml.getTable(keyPath);
if (table == null) {
return null;
}
return deepToMap(table);
}, schema::getDefaultMap);
}
@Override
public List<Object> getList(String key) {
return getValue(key, keyPath -> {
TomlArray array = toml.getArray(keyPath);
if (array == null) {
return null;
}
return deepToList(array);
}, schema::getDefaultList);
}
@Override
public List<String> getListOfString(String key) {
return getList(key, "strings", TomlArray::containsStrings, schema::getDefaultListOfString);
}
@Override
public List<Integer> getListOfInteger(String key) {
return getValue(key, keyPath -> {
TomlArray array = toml.getArray(keyPath);
if (array == null) {
return null;
}
if (!array.containsLongs()) {
throw new InvalidConfigurationPropertyTypeException(
inputPositionOf(keyPath),
"List property '" + joinKeyPath(keyPath) + "' does not contain integers");
}
@SuppressWarnings("unchecked")
List<Long> longList = (List<Long>) (List) array.toList();
return IntStream.range(0, longList.size()).mapToObj(i -> {
Long value = longList.get(i);
if (value > Integer.MAX_VALUE) {
throw new InvalidConfigurationPropertyTypeException(
inputPositionOf(keyPath),
"Value of property '" + joinKeyPath(keyPath) + "', index " + i + ", is too large for an integer");
}
return value.intValue();
}).collect(Collectors.toList());
}, schema::getDefaultListOfInteger);
}
@Override
public List<Long> getListOfLong(String key) {
return getList(key, "longs", TomlArray::containsLongs, schema::getDefaultListOfLong);
}
@Override
public List<Double> getListOfDouble(String key) {
return getList(key, "doubles", TomlArray::containsDoubles, schema::getDefaultListOfDouble);
}
@Override
public List<Boolean> getListOfBoolean(String key) {
return getList(key, "booleans", TomlArray::containsBooleans, schema::getDefaultListOfBoolean);
}
@Override
public List<Map<String, Object>> getListOfMap(String key) {
return getValue(key, keyPath -> {
TomlArray array = toml.getArray(keyPath);
if (array == null) {
return null;
}
if (!array.containsTables()) {
throw new InvalidConfigurationPropertyTypeException(
inputPositionOf(keyPath),
"List property '" + joinKeyPath(keyPath) + "' does not contain maps");
}
@SuppressWarnings("unchecked")
List<Map<String, Object>> typedList = (List<Map<String, Object>>) (List) deepToList(array);
return typedList;
}, schema::getDefaultListOfMap);
}
private DocumentPosition documentPosition(TomlPosition position) {
return DocumentPosition.positionAt(position.line(), position.column());
}
private <T> T getValue(String key, Function<List<String>, T> tomlGet, Function<String, T> defaultGet) {
List<String> keyPath = parseDottedKey(key);
T value;
try {
value = tomlGet.apply(keyPath);
} catch (TomlInvalidTypeException e) {
throw new InvalidConfigurationPropertyTypeException(inputPositionOf(keyPath), e.getMessage(), e);
}
if (value != null) {
return value;
}
String canonicalKey = joinKeyPath(keyPath);
value = defaultGet.apply(canonicalKey);
if (value != null) {
return value;
}
throw new NoConfigurationPropertyException("No value for property '" + canonicalKey + "'");
}
private <T> List<T> getList(
String key,
String typeName,
Predicate<TomlArray> tomlCheck,
Function<String, List<T>> defaultGet) {
return getValue(key, keyPath -> {
TomlArray array = toml.getArray(keyPath);
if (array == null) {
return null;
}
if (!tomlCheck.test(array)) {
throw new InvalidConfigurationPropertyTypeException(
inputPositionOf(keyPath),
"List property '" + joinKeyPath(keyPath) + "' does not contain " + typeName);
}
@SuppressWarnings("unchecked")
List<T> typedList = (List<T>) array.toList();
return typedList;
}, defaultGet);
}
private static List<Object> deepToList(TomlArray array) {
return array.toList().stream().map(o -> {
if (o instanceof TomlArray) {
return deepToList((TomlArray) o);
}
if (o instanceof TomlTable) {
return deepToMap((TomlTable) o);
}
return o;
}).collect(Collectors.toList());
}
private static Map<String, Object> deepToMap(TomlTable table) {
return table.toMap().entrySet().stream().collect(Collectors.toMap(Entry::getKey, e -> {
Object o = e.getValue();
if (o instanceof TomlArray) {
return deepToList((TomlArray) o);
}
if (o instanceof TomlTable) {
return deepToMap((TomlTable) o);
}
return o;
}));
}
}