All Downloads are FREE. Search and download functionalities are using the official Maven repository.

dev.harrel.jsonschema.providers.SnakeYamlNode Maven / Gradle / Ivy

package dev.harrel.jsonschema.providers;

import dev.harrel.jsonschema.JsonNode;
import dev.harrel.jsonschema.JsonNodeFactory;
import dev.harrel.jsonschema.SimpleType;
import org.yaml.snakeyaml.LoaderOptions;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.SafeConstructor;
import org.yaml.snakeyaml.nodes.*;

import java.io.StringReader;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.*;

public final class SnakeYamlNode extends AbstractJsonNode {
    private static final SafeConstructor CONSTR = new SafeConstructor(new LoaderOptions());
    private static final SafeConstructor.ConstructYamlBool BOOLEAN_CREATOR = CONSTR.new ConstructYamlBool();
    private static final SafeConstructor.ConstructYamlInt INT_CREATOR = CONSTR.new ConstructYamlInt();
    private static final SafeConstructor.ConstructYamlFloat NUMBER_CREATOR = CONSTR.new ConstructYamlFloat();

    private BigDecimal asNumber;

    private SnakeYamlNode(Node node, String jsonPointer) {
        super(Objects.requireNonNull(node), jsonPointer);
    }

    private SnakeYamlNode(Node node) {
        this(node, "");
    }

    @Override
    public boolean asBoolean() {
        return (Boolean) BOOLEAN_CREATOR.construct(node);
    }

    @Override
    public String asString() {
        return ((ScalarNode) node).getValue();
    }

    @Override
    public BigInteger asInteger() {
        return asNumber.toBigInteger();
    }

    @Override
    public BigDecimal asNumber() {
        return asNumber;
    }

    @Override
    List createArray() {
        List arrayNode = ((SequenceNode) node).getValue();
        List elements = new ArrayList<>(arrayNode.size());
        for (int i = 0; i < arrayNode.size(); i++) {
            elements.add(new SnakeYamlNode(arrayNode.get(i), jsonPointer + "/" + i));
        }
        return elements;
    }

    @Override
    Map createObject() {
        List objectNode = ((MappingNode) node).getValue();
        Map map = MapUtil.newHashMap(objectNode.size());
        for (NodeTuple entry : objectNode) {
            String key = ((ScalarNode) entry.getKeyNode()).getValue();
            map.put(key, new SnakeYamlNode(entry.getValueNode(), jsonPointer + "/" + JsonNode.encodeJsonPointer(key)));
        }
        return map;
    }

    @Override
    SimpleType computeNodeType(Node node) {
        if (node instanceof SequenceNode) {
            return SimpleType.ARRAY;
        } else if (node instanceof MappingNode) {
            return SimpleType.OBJECT;
        }

        if (node.getTag() == Tag.NULL) {
            return SimpleType.NULL;
        } else if (node.getTag() == Tag.BOOL) {
            return SimpleType.BOOLEAN;
        } else if (node.getTag() == Tag.INT) {
            asNumber = intToBigDecimal(node);
            return SimpleType.INTEGER;
        } else if (node.getTag() == Tag.FLOAT) {
            String asString = ((ScalarNode) node).getValue().toLowerCase();
            if (asString.contains(".inf") || asString.contains(".nan")) {
                return SimpleType.STRING;
            }
            asNumber = floatToBigDecimal(node);
            if (canConvertToInteger(asNumber)) {
                return SimpleType.INTEGER;
            } else {
                return SimpleType.NUMBER;
            }
        } else {
            return SimpleType.STRING;
        }
    }

    private static BigDecimal intToBigDecimal(Node node) {
        Object intObject = INT_CREATOR.construct(node);
        if (intObject instanceof Integer || intObject instanceof Long) {
            return BigDecimal.valueOf(((Number) intObject).longValue());
        } else {
            return new BigDecimal((BigInteger) intObject);
        }
    }

    private static BigDecimal floatToBigDecimal(Node node) {
        String asString = ((ScalarNode) node).getValue();
        if (asString.contains(":")) {
            return BigDecimal.valueOf(((Double) NUMBER_CREATOR.construct(node)));
        } else {
            return new BigDecimal(asString);
        }
    }

    public static final class Factory implements JsonNodeFactory {
        private final Yaml yaml;

        public Factory() {
            this(new Yaml());
        }

        public Factory(Yaml yaml) {
            this.yaml = yaml;
        }

        @Override
        public JsonNode wrap(Object node) {
            if (node instanceof SnakeYamlNode) {
                return new SnakeYamlNode(((SnakeYamlNode) node).node);
            } else if (node instanceof Node) {
                Node providerNode = (Node) node;
                assertKeyUniqueness(providerNode);
                return new SnakeYamlNode(providerNode);
            } else {
                throw new IllegalArgumentException("Cannot wrap object which is not an instance of org.yaml.snakeyaml.nodes.Node");
            }
        }

        @Override
        public JsonNode create(String rawJson) {
            Node node = yaml.compose(new StringReader(rawJson));
            assertKeyUniqueness(node);
            return new SnakeYamlNode(node);
        }

        private static void assertKeyUniqueness(Node node) {
            assertKeyUniqueness(node, Collections.newSetFromMap(new IdentityHashMap<>()));
        }

        private static void assertKeyUniqueness(Node node, Set visited) {
            if (!(node instanceof CollectionNode) || !visited.add(node)) {
                return;
            } else if (node instanceof SequenceNode) {
                for (Node element : ((SequenceNode) node).getValue()) {
                    assertKeyUniqueness(element, visited);
                }
            } else if (node instanceof MappingNode) {
                List tuples = ((MappingNode) node).getValue();
                Set keys = new HashSet<>();
                for (NodeTuple tuple : tuples) {
                    ScalarNode keyNode = (ScalarNode) tuple.getKeyNode();
                    if (!keys.add(keyNode.getValue())) {
                        throw new IllegalArgumentException("Mapping key '" + keyNode.getValue() + "' is duplicated" + keyNode.getStartMark());
                    }
                    assertKeyUniqueness(tuple.getValueNode(), visited);
                }
            }
            visited.remove(node);
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy