package com.onthegomap.planetiler.custommap;
import com.google.api.expr.v1alpha1.Constant;
import com.google.api.expr.v1alpha1.Decl;
import com.google.api.expr.v1alpha1.Type;
import com.google.protobuf.NullValue;
import com.onthegomap.planetiler.config.Arguments;
import com.onthegomap.planetiler.config.PlanetilerConfig;
import com.onthegomap.planetiler.custommap.expression.ParseException;
import com.onthegomap.planetiler.custommap.expression.ScriptContext;
import com.onthegomap.planetiler.custommap.expression.ScriptEnvironment;
import com.onthegomap.planetiler.expression.DataType;
import com.onthegomap.planetiler.reader.SourceFeature;
import com.onthegomap.planetiler.reader.WithGeometryType;
import com.onthegomap.planetiler.reader.WithTags;
import com.onthegomap.planetiler.util.Try;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.projectnessie.cel.checker.Decls;
import org.projectnessie.cel.common.types.NullT;
import org.projectnessie.cel.common.types.pb.ProtoTypeRegistry;
import org.projectnessie.cel.common.types.ref.TypeAdapter;
import org.projectnessie.cel.common.types.ref.Val;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Wrapper objects that provide all available inputs to different parts of planetiler schema configs at runtime.
*
* Contexts provide inputs to java code, and also global variable definitions to CEL expressions. Contexts are nested so
* that all global variables from a parent context are also available to its child context.
*/
public class Contexts {
private static final Logger LOGGER = LoggerFactory.getLogger(Contexts.class);
private static Object wrapNullable(Object nullable) {
return nullable == null ? NullT.NullValue : nullable;
}
public static Root emptyRoot() {
return new Root(Arguments.of().silence(), Map.of());
}
/**
* Returns a {@link Root} context built from {@code schemaArgs} argument definitions and {@code origArguments}
* arguments provided from the command-line/environment.
*
* Arguments may depend on the value of other arguments so this iteratively evaluates the arguments until their values
* settle.
*
* @throws ParseException if the argument definitions are malformed, or if there's an infinite loop
*/
public static Contexts.Root buildRootContext(Arguments origArguments, Map schemaArgs) {
boolean loggingEnabled = !origArguments.silenced();
origArguments.silence();
Map argDescriptions = new LinkedHashMap<>();
Map unparsedSchemaArgs = new HashMap<>(schemaArgs);
Map parsedSchemaArgs = new HashMap<>(origArguments.toMap());
Contexts.Root result = new Root(origArguments, parsedSchemaArgs);
Arguments arguments = origArguments;
int iters = 0;
// arguments may reference the value of other arguments, so continue parsing until they all succeed...
while (!unparsedSchemaArgs.isEmpty()) {
final var root = result;
final var args = arguments;
Map failures = new HashMap<>();
Map.copyOf(unparsedSchemaArgs).forEach((key, value) -> {
boolean builtin = root.builtInArgs.contains(key);
String description;
Object defaultValueObject;
DataType type = null;
if (value instanceof Map, ?> map) {
if (builtin) {
throw new ParseException("Cannot override built-in argument: " + key);
}
var typeObject = map.get("type");
if (typeObject != null) {
type = DataType.from(Objects.toString(typeObject));
}
var descriptionObject = map.get("description");
description = descriptionObject == null ? "no description provided" : descriptionObject.toString();
defaultValueObject = map.get("default");
if (type != null) {
var fromArgs = args.getString(key, description, null);
if (fromArgs != null) {
parsedSchemaArgs.put(key, type.convertFrom(fromArgs));
}
}
} else {
defaultValueObject = value;
description = "no description provided";
}
argDescriptions.put(key, description);
Try