001/*
002 * SPDX-FileCopyrightText: none
003 * SPDX-License-Identifier: CC0-1.0
004 */
005
006package gov.nist.secauto.metaschema.databind.io.yaml;
007
008import gov.nist.secauto.metaschema.core.util.ObjectUtils;
009
010import org.json.JSONException;
011import org.json.JSONObject;
012import org.yaml.snakeyaml.DumperOptions;
013import org.yaml.snakeyaml.LoaderOptions;
014import org.yaml.snakeyaml.Yaml;
015import org.yaml.snakeyaml.constructor.Constructor;
016import org.yaml.snakeyaml.nodes.Tag;
017import org.yaml.snakeyaml.representer.Representer;
018import org.yaml.snakeyaml.resolver.Resolver;
019
020import java.io.BufferedInputStream;
021import java.io.IOException;
022import java.net.URI;
023import java.util.Map;
024
025import edu.umd.cs.findbugs.annotations.NonNull;
026
027public final class YamlOperations {
028  /**
029   * Thread-local Yaml parser to ensure thread safety. SnakeYAML's Yaml class is
030   * not thread-safe, so each thread needs its own instance.
031   */
032  private static final ThreadLocal<Yaml> YAML_PARSER = ThreadLocal.withInitial(() -> {
033    LoaderOptions loaderOptions = new LoaderOptions();
034    loaderOptions.setCodePointLimit(Integer.MAX_VALUE - 1); // 2GB
035    Constructor constructor = new Constructor(loaderOptions);
036    DumperOptions dumperOptions = new DumperOptions();
037    Representer representer = new Representer(dumperOptions);
038    return new Yaml(constructor, representer, dumperOptions, loaderOptions, new Resolver() {
039      @Override
040      protected void addImplicitResolvers() {
041        addImplicitResolver(Tag.BOOL, BOOL, "yYnNtTfFoO");
042        addImplicitResolver(Tag.INT, INT, "-+0123456789");
043        addImplicitResolver(Tag.FLOAT, FLOAT, "-+0123456789.");
044        addImplicitResolver(Tag.MERGE, MERGE, "<");
045        addImplicitResolver(Tag.NULL, NULL, "~nN\0");
046        addImplicitResolver(Tag.NULL, EMPTY, null);
047        // addImplicitResolver(Tag.TIMESTAMP, TIMESTAMP, "0123456789");
048      }
049    });
050  });
051
052  private YamlOperations() {
053    // disable construction
054  }
055
056  /**
057   * Parse the data represented in YAML in the provided {@code target}, producing
058   * an mapping of field names to Java object values.
059   *
060   * @param target
061   *          the YAML file to parse
062   * @return the mapping of field names to Java object values
063   * @throws IOException
064   *           if an error occurred while parsing the YAML content
065   */
066  @SuppressWarnings({ "unchecked", "null" })
067  @NonNull
068  public static Map<String, Object> parseYaml(URI target) throws IOException {
069    try (BufferedInputStream is = new BufferedInputStream(ObjectUtils.notNull(target.toURL().openStream()))) {
070      return (Map<String, Object>) YAML_PARSER.get().load(is);
071    }
072  }
073
074  /**
075   * Converts the provided YAML {@code map} into JSON.
076   *
077   * @param map
078   *          the YAML map
079   * @return the JSON object
080   * @throws JSONException
081   *           if an error occurred while building the JSON tree
082   */
083  public static JSONObject yamlToJson(@NonNull Map<String, Object> map) {
084    return new JSONObject(map);
085  }
086}