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}