blob: 19eebdb0643861d6918d71ef2b211f8aaeba6cc3 [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.toml;
import static org.apache.tuweni.toml.Parser.parseDottedKey;
import static org.apache.tuweni.toml.TomlPosition.positionAt;
import static org.apache.tuweni.toml.TomlType.typeFor;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.annotation.Nullable;
final class MutableTomlTable implements TomlTable {
private static class Element {
final Object value;
final TomlPosition position;
private Element(Object value, TomlPosition position) {
this.value = value;
this.position = position;
}
}
static final TomlTable EMPTY = new MutableTomlTable(true);
private Map<String, Element> properties = new HashMap<>();
private boolean implicitlyDefined;
MutableTomlTable() {
this(false);
}
private MutableTomlTable(boolean implicitlyDefined) {
this.implicitlyDefined = implicitlyDefined;
}
@Override
public int size() {
return properties.size();
}
@Override
public boolean isEmpty() {
return properties.isEmpty();
}
@Override
public Set<String> keySet() {
return properties.keySet();
}
@Override
public Set<List<String>> keyPathSet(boolean includeTables) {
return properties.entrySet().stream().flatMap(entry -> {
String key = entry.getKey();
List<String> basePath = Collections.singletonList(key);
Element element = entry.getValue();
if (!(element.value instanceof TomlTable)) {
return Stream.of(basePath);
}
Stream<List<String>> subKeys = ((TomlTable) element.value).keyPathSet(includeTables).stream().map(subPath -> {
List<String> path = new ArrayList<>(subPath.size() + 1);
path.add(key);
path.addAll(subPath);
return path;
});
if (includeTables) {
return Stream.concat(Stream.of(basePath), subKeys);
} else {
return subKeys;
}
}).collect(Collectors.toSet());
}
@Override
@Nullable
@SuppressWarnings("unchecked")
public Object get(List<String> path) {
if (path.isEmpty()) {
return this;
}
Element element = getElement(path);
return (element != null) ? element.value : null;
}
@Override
@Nullable
public TomlPosition inputPositionOf(List<String> path) {
if (path.isEmpty()) {
return positionAt(1, 1);
}
Element element = getElement(path);
return (element != null) ? element.position : null;
}
private Element getElement(List<String> path) {
MutableTomlTable table = this;
int depth = path.size();
assert depth > 0;
for (int i = 0; i < (depth - 1); ++i) {
Element element = table.properties.get(path.get(i));
if (element == null) {
return null;
}
if (element.value instanceof MutableTomlTable) {
table = (MutableTomlTable) element.value;
continue;
}
return null;
}
return table.properties.get(path.get(depth - 1));
}
@Override
public Map<String, Object> toMap() {
return properties.entrySet().stream().collect(Collectors.toMap(Entry::getKey, e -> e.getValue().value));
}
MutableTomlTable createTable(List<String> path, TomlPosition position) {
if (path.isEmpty()) {
return this;
}
int depth = path.size();
MutableTomlTable table = ensureTable(path.subList(0, depth - 1), position, true);
String key = path.get(depth - 1);
Element element = table.properties.get(key);
if (element == null) {
MutableTomlTable newTable = new MutableTomlTable();
table.properties.put(key, new Element(newTable, position));
return newTable;
}
if (element.value instanceof MutableTomlTable) {
table = (MutableTomlTable) element.value;
if (table.implicitlyDefined) {
table.implicitlyDefined = false;
table.properties.put(key, new Element(table, position));
return table;
}
}
String message = Toml.joinKeyPath(path) + " previously defined at " + element.position;
throw new TomlParseError(message, position);
}
MutableTomlTable createArrayTable(List<String> path, TomlPosition position) {
if (path.isEmpty()) {
throw new IllegalArgumentException("empty path");
}
int depth = path.size();
MutableTomlTable table = ensureTable(path.subList(0, depth - 1), position, true);
String key = path.get(depth - 1);
Element element = table.properties.computeIfAbsent(key, k -> new Element(new MutableTomlArray(), position));
if (!(element.value instanceof MutableTomlArray)) {
String message = Toml.joinKeyPath(path) + " is not an array (previously defined at " + element.position + ")";
throw new TomlParseError(message, position);
}
MutableTomlArray array = (MutableTomlArray) element.value;
if (array.wasDefinedAsLiteral()) {
String message = Toml.joinKeyPath(path) + " previously defined as a literal array at " + element.position;
throw new TomlParseError(message, position);
}
MutableTomlTable newTable = new MutableTomlTable();
array.append(newTable, position);
return newTable;
}
MutableTomlTable set(String keyPath, Object value, TomlPosition position) {
return set(parseDottedKey(keyPath), value, position);
}
MutableTomlTable set(List<String> path, Object value, TomlPosition position) {
int depth = path.size();
assert (depth > 0);
assert (value != null);
if (value instanceof Integer) {
value = ((Integer) value).longValue();
}
assert (typeFor(value).isPresent()) : "Unexpected value of type " + value.getClass();
MutableTomlTable table = ensureTable(path.subList(0, depth - 1), position, false);
Element prevElem = table.properties.putIfAbsent(path.get(depth - 1), new Element(value, position));
if (prevElem != null) {
String pathString = Toml.joinKeyPath(path);
String message = pathString + " previously defined at " + prevElem.position;
throw new TomlParseError(message, position);
}
return this;
}
private MutableTomlTable ensureTable(List<String> path, TomlPosition position, boolean followArrayTables) {
MutableTomlTable table = this;
int depth = path.size();
for (int i = 0; i < depth; ++i) {
Element element =
table.properties.computeIfAbsent(path.get(i), k -> new Element(new MutableTomlTable(true), position));
if (element.value instanceof MutableTomlTable) {
table = (MutableTomlTable) element.value;
continue;
}
if (followArrayTables && element.value instanceof MutableTomlArray) {
MutableTomlArray array = (MutableTomlArray) element.value;
if (!array.wasDefinedAsLiteral() && !array.isEmpty() && array.containsTables()) {
table = (MutableTomlTable) array.get(array.size() - 1);
continue;
}
}
String message =
Toml.joinKeyPath(path.subList(0, i + 1)) + " is not a table (previously defined at " + element.position + ")";
throw new TomlParseError(message, position);
}
return table;
}
}