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

org.etlunit.json.validator.JsonSchemaObjectNode Maven / Gradle / Ivy

There is a newer version: 1.6.9
Show newest version
package org.etlunit.json.validator;

import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.node.ArrayNode;
import org.codehaus.jackson.node.ObjectNode;

import java.util.*;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

public class JsonSchemaObjectNode {
	private static final Map typesByName = new HashMap();
	private final ObjectNode sourceNode;

	public static enum valid_type
	{
		t_string,
		t_number,
		t_integer,
		t_boolean,
		t_object,
		t_array,
		t_null,
		t_any;

		private valid_type()
		{
			typesByName.put(name().substring(2), this);
		}
	}

	static
	{
		// silly little Java thing to make sure this class is loaded
		valid_type.t_string.ordinal();
	}

	private final List type = new ArrayList();

	private final boolean required;
	private final boolean uniqueItems;

	private final Double maximum;
	private final Double minimum;
	private final Double exclusiveMaximum;
	private final Double exclusiveMinimum;

	private final Integer maxItems;
	private final Integer minItems;
	private final Integer minLength;
	private final Integer maxLength;

	private final String id;
	private final String _ref;
	private final String _schema;
	private final String title;
	private final String description;
	private final String format;
	private final String defaultValue;

	private final List enumValues;

	private final Pattern pattern;

	private final Double divisibleBy;

	private final Map propertySchemas = new HashMap();
	private final Map patternPropertySchemas = new HashMap();

	private boolean arrayItems = false;
	private final List items = new ArrayList();
	private final List exclusive;
	private boolean exclusiveRequired;

	private final List _extends = new ArrayList();

	private final boolean allowsAdditionalProperties;
	private final JsonSchemaObjectNode additionalProperties;

	private final boolean allowsAdditionalItems;
	private final JsonSchemaObjectNode additionalItems;

	JsonSchemaObjectNode(ObjectNode node) throws JsonSchemaValidationException
	{
		sourceNode = node;
		required = readBoolean(node, "required", false);
		readTypeNode(node);
		uniqueItems = readBoolean(node, "uniqueItems", false);
		exclusive = readExclusive(node);

		maximum = readBigDecimal(node, "maximum", null);
		minimum = readBigDecimal(node, "minimum", null);
		exclusiveMaximum = readBigDecimal(node, "exclusiveMaximum", null);
		exclusiveMinimum = readBigDecimal(node, "exclusiveMinimum", null);
		divisibleBy = readBigDecimal(node, "divisibleBy", null);

		if (divisibleBy != null && divisibleBy.doubleValue() <= 0)
		{
			throw new JsonSchemaValidationException("Invalid divisibleBy property - value must be > 0", "", node, null);
		}

		minItems = readArrayInteger(node, "minItems", new Integer(0));

		if (minItems != null && minItems.intValue() < 0)
		{
			throw new JsonSchemaValidationException("Invalid minItems property - value must be >= 0", "", node, null);
		}

		maxItems = readArrayInteger(node, "maxItems", null);
		minLength = readInteger(node, "minLength", new Integer(0));

		if (minLength != null && minLength.intValue() < 0)
		{
			throw new JsonSchemaValidationException("Invalid minLength property - value must be >= 0", "", node, null);
		}

		maxLength = readInteger(node, "maxLength", null);

		title = readString(node, "title");
		description = readString(node, "description");
		format = readString(node, "format");
		id = readString(node, "id");
		_ref = readString(node, "$ref");
		_schema = readString(node, "$schema");

		enumValues = readEnum(node);
		readExtends(node);

		pattern = readPattern(node);

		defaultValue = readDefault(node);

		readProperties(node);
		readPatternProperties(node);
		readItems(node);

		Object add = readAdditionalProperties(node, "additionalProperties");

		if (add instanceof Boolean)
		{
			allowsAdditionalProperties = ((Boolean) add).booleanValue();
			additionalProperties = null;
		}
		else
		{
			allowsAdditionalProperties = true;
			additionalProperties = (JsonSchemaObjectNode) add;
		}

		add = readAdditionalProperties(node, "additionalItems");

		if (add instanceof Boolean)
		{
			allowsAdditionalItems = ((Boolean) add).booleanValue();
			additionalItems = null;
		}
		else
		{
			allowsAdditionalItems = true;
			additionalItems = (JsonSchemaObjectNode) add;
		}
	}

	private Object readAdditionalProperties(ObjectNode node, String propertyName) throws JsonSchemaValidationException {
		JsonNode defNode = node.get(propertyName);

		if (defNode == null)
		{
			return Boolean.TRUE;
		}
		else if (defNode.isBoolean())
		{
			return new Boolean(defNode.asBoolean());
		}
		else if (defNode.isObject())
		{
			return new JsonSchemaObjectNode((ObjectNode) defNode);
		}
		else
		{
			throw new JsonSchemaValidationException("Invalid additionalProperties property - not object or boolean type", "", defNode, null);
		}
	}

	private String readDefault(ObjectNode node) {
		JsonNode defNode = node.get("default");

		if (defNode != null)
		{
			return defNode.toString();
		}

		return null;
	}

	private List readExclusive(ObjectNode node) throws JsonSchemaValidationException {
		JsonNode propsNode = node.get("exclusive");

		if (propsNode == null)
		{
			return null;
		}

		List enode = new ArrayList();

		if (!propsNode.isObject())
		{
			throw new JsonSchemaValidationException("Invalid exclusive property - not object type", "", propsNode, null);
		}
		else
		{
			Map attMap = new HashMap();
			attMap.put("items", Boolean.TRUE);
			attMap.put("required", Boolean.FALSE);

			validatePropertySet((ObjectNode) propsNode, attMap, propsNode);

			JsonNode required1 = propsNode.get("required");

			if (required1 != null)
			{
				exclusiveRequired = required1.asBoolean();
			}

			propsNode = propsNode.get("items");

			for (JsonNode inode : propsNode)
			{
				if (!inode.isObject())
				{
					throw new JsonSchemaValidationException("Invalid exclusive schema entry - not object type", "", inode, null);
				}

				enode.add(loadSchemaItem((ObjectNode) inode));
			}
		}

		return enode;
	}

	private void readItems(ObjectNode node) throws JsonSchemaValidationException {
		JsonNode propsNode = node.get("items");

		if (propsNode == null)
		{
			return;
		}

		if (propsNode.isObject())
		{
			addItem((ObjectNode) propsNode);
		}
		else if (propsNode.isArray())
		{
			arrayItems = true;

			for (JsonNode inode : propsNode)
			{
				if (!inode.isObject())
				{
					throw new JsonSchemaValidationException("Invalid items entry - not object type", "", inode, null);
				}

				addItem((ObjectNode) inode);
			}
		}
		else
		{
			throw new JsonSchemaValidationException("Invalid items property - not object or array type", "", propsNode, null);
		}
	}

	private JsonSchemaObjectNode loadSchemaItem(ObjectNode inode) throws JsonSchemaValidationException {
		return new JsonSchemaObjectNode(inode);
	}

	private void addItem(ObjectNode inode) throws JsonSchemaValidationException {
		items.add(loadSchemaItem(inode));
	}

	private void readProperties(ObjectNode node) throws JsonSchemaValidationException {
		JsonNode propsNode = node.get("properties");

		if (propsNode == null)
		{
			return;
		}

		if (!propsNode.isObject())
		{
			throw new JsonSchemaValidationException("Invalid properties property - not object type", "", propsNode, null);
		}

		Iterator> fields = propsNode.getFields();

		while (fields.hasNext())
		{
			Map.Entry field = fields.next();

			if (propertySchemas.containsKey(field.getKey()))
			{
				throw new JsonSchemaValidationException("Invalid properties property - property specified twice: " + field, "", propsNode, null);
			}

			JsonNode value = field.getValue();

			if (!value.isObject())
			{
				throw new JsonSchemaValidationException("Invalid properties property - value type not object: " + value, "", propsNode, null);
			}

			propertySchemas.put(field.getKey(), new JsonSchemaObjectNode((ObjectNode) value));
		}
	}

	private void readPatternProperties(ObjectNode node) throws JsonSchemaValidationException {
		JsonNode propsNode = node.get("patternProperties");

		if (propsNode == null)
		{
			return;
		}

		if (!propsNode.isObject())
		{
			throw new JsonSchemaValidationException("Invalid patternProperties property - not object type", "", propsNode, null);
		}

		Iterator> fields = propsNode.getFields();

		while (fields.hasNext())
		{
			Map.Entry field = fields.next();

			if (patternPropertySchemas.containsKey(field.getKey()))
			{
				throw new JsonSchemaValidationException("Invalid patternProperties property - pattern specified twice: " + field, "", propsNode, null);
			}

			JsonNode value = field.getValue();

			if (!value.isObject())
			{
				throw new JsonSchemaValidationException("Invalid patternProperties property - value type not object: " + value, "", propsNode, null);
			}

			try
			{
				patternPropertySchemas.put(Pattern.compile(field.getKey()), new JsonSchemaObjectNode((ObjectNode) value));
			}
			catch(PatternSyntaxException exc)
			{
				throw new JsonSchemaValidationException("Invalid regular expression pattern: " + field.getKey(), "", propsNode, null);
			}
		}
	}

	private Pattern readPattern(ObjectNode node) throws JsonSchemaValidationException {
		String text = readString(node, "pattern");

		if (text == null)
		{
			return null;
		}

		try
		{
			return Pattern.compile(text);
		}
		catch(PatternSyntaxException exc)
		{
			throw new JsonSchemaValidationException("Invalid regular expression pattern: " + text, "", node, null);
		}
	}

	private List readEnum(ObjectNode node) throws JsonSchemaValidationException {
		JsonNode reqNode = node.get("enum");

		if (reqNode == null)
		{
			return null;
		}

		List list = new ArrayList();

		ArrayNode anode = (ArrayNode) reqNode;

		if (anode.size() == 0)
		{
			throw new JsonSchemaValidationException("Invalid enum - at least one value must be specified: " + anode, "", anode, null);
		}

		for (JsonNode enode : anode)
		{
			if (!enode.isTextual())
			{
				throw new JsonSchemaValidationException("Invalid enum value type - must be a string: " + enode, "", enode, null);
			}

			String text = enode.asText();

			if (list.contains(text))
			{
				throw new JsonSchemaValidationException("Invalid enum - value duplicated: " + enode, "", anode, null);
			}

			list.add(text);
		}

		return list;
	}

	private void readExtends(ObjectNode node) throws JsonSchemaValidationException {
		JsonNode reqNode = node.get("extends");

		if (reqNode == null)
		{
			return;
		}

		if (reqNode.isTextual())
		{
			_extends.add(reqNode.asText());
		}
		else if (reqNode.isArray())
		{
			ArrayNode anode = (ArrayNode) reqNode;

			for (JsonNode enode : anode)
			{
				if (!enode.isTextual())
				{
					throw new JsonSchemaValidationException("Invalid extends value type - must be a string: " + enode, "", enode, null);
				}

				String text = enode.asText();

				if (_extends.contains(text))
				{
					throw new JsonSchemaValidationException("Invalid extends - value duplicated: " + enode, "", anode, null);
				}

				_extends.add(text);
			}
		}
		else
		{
			throw new JsonSchemaValidationException("Invalid extends - node wrong type", "", reqNode, null);
		}
	}

	private void validatePropertySet(ObjectNode node, Map attMap, JsonNode jnode) throws JsonSchemaValidationException
	{
		// check for extra nodes
		Iterator attrit = node.getFieldNames();

		while (attrit.hasNext())
		{
			String str = attrit.next();

			if (!attMap.containsKey(str))
			{
				throw new JsonSchemaValidationException("Invalid schema property: " + str, "", jnode, null);
			}
		}
	}

	private Integer readInteger(ObjectNode node, String property, Integer default_) throws JsonSchemaValidationException {
		JsonNode reqNode = node.get(property);

		if (reqNode == null)
		{
			return default_;
		}

		// check that this object is defining a number type
		if (type == null)
		{
			throw new JsonSchemaValidationException("Cannot specify property " + property + " on an unspecified type node: " + node, "", reqNode, null);
		}

		if (
			type.contains(valid_type.t_string)
				||
			type.contains(valid_type.t_any)
				||
			type.contains(valid_type.t_array)
				||
			type.contains(valid_type.t_boolean)
				||
			type.contains(valid_type.t_object)
				)
		{
			throw new JsonSchemaValidationException("Cannot specify property " + property + " on a non-number type", "", node, null);
		}

		if (!reqNode.isInt())
		{
			throw new JsonSchemaValidationException(property + " property must be an integer: " + node.toString(), "", reqNode, null);
		}

		return reqNode.asInt();
	}

	private Integer readArrayInteger(ObjectNode node, String property, Integer default_) throws JsonSchemaValidationException {
		JsonNode reqNode = node.get(property);

		if (reqNode == null)
		{
			return default_;
		}

		// check that this object is defining a number type
		if (type == null)
		{
			throw new JsonSchemaValidationException("Cannot specify property " + property + " on an unspecified type node", "", reqNode, null);
		}

		if (!reqNode.isInt())
		{
			throw new JsonSchemaValidationException(property + " property must be an integer: " + node.toString(), "", reqNode, null);
		}

		return reqNode.asInt();
	}

	private Double readBigDecimal(ObjectNode node, String property, Double default_) throws JsonSchemaValidationException {
		JsonNode reqNode = node.get(property);

		if (reqNode == null)
		{
			return default_;
		}

		// check that this object is defining a number type
		if (!reqNode.isNumber() && !reqNode.isIntegralNumber())
		{
			throw new JsonSchemaValidationException("Cannot specify property " + property + " on a non-number type", "", reqNode, null);
		}

		if (!reqNode.isNumber())
		{
			throw new JsonSchemaValidationException(property + " property must be an integer", "", reqNode, null);
		}

		return new Double(reqNode.asDouble());
	}

	private boolean readBoolean(ObjectNode node, String property, boolean default_) throws JsonSchemaValidationException {
		JsonNode reqNode = node.get(property);

		if (reqNode == null)
		{
			return default_;
		}

		if (!reqNode.isBoolean())
		{
			throw new JsonSchemaValidationException(property + " property must be a boolean", "", reqNode, null);
		}

		return reqNode.asBoolean();
	}

	private String readString(ObjectNode node, String property) throws JsonSchemaValidationException {
		JsonNode reqNode = node.get(property);

		if (reqNode == null)
		{
			return null;
		}

		if (!reqNode.isTextual())
		{
			throw new JsonSchemaValidationException(property + " property must be a string", "", reqNode, null);
		}

		return reqNode.asText();
	}

	private void readTypeNode(ObjectNode node) throws JsonSchemaValidationException {
		JsonNode typeNode = node.get("type");

		if (typeNode != null)
		{
			if (typeNode.isArray())
			{
				Iterator it = ((ArrayNode) typeNode).getElements();

				while (it.hasNext())
				{
					JsonNode vnode = it.next();

					if (!vnode.isTextual())
					{
						throw new JsonSchemaValidationException("Invalid type union.  Item is not textual", "", typeNode, null);
					}

					addTypeValue(vnode.asText());
				}
			}
			else if (typeNode.isTextual())
			{
				addTypeValue(typeNode.asText());
			}
			else
			{
				throw new JsonSchemaValidationException("Invalid schema - invalid object type", "", typeNode, null);
			}
		}
	}

	private void addTypeValue(String s) throws JsonSchemaValidationException {
		valid_type vvalid_type = typesByName.get(s);

		if (type.contains(vvalid_type))
		{
			throw new JsonSchemaValidationException("Type " + s + " specified twice in union", "", null, null);
		}

		if (vvalid_type != null)
		{
			type.add(vvalid_type);
		}
		else
		{
			throw new JsonSchemaValidationException("Invalid object type: " + s, "", null, null);
		}
	}

	public List getType() {
		return type;
	}

	public boolean isRequired()
	{
		return required;
	}

	public boolean isUniqueItems() {
		return uniqueItems;
	}

	public Double getMaximum() {
		return maximum;
	}

	public Double getMinimum() {
		return minimum;
	}

	public Double getExclusiveMaximum() {
		return exclusiveMaximum;
	}

	public Double getExclusiveMinimum() {
		return exclusiveMinimum;
	}

	public Integer getMaxItems() {
		return maxItems;
	}

	public Integer getMaxLength() {
		return maxLength;
	}

	public String getTitle() {
		return title;
	}

	public String getDescription() {
		return description;
	}

	public String getFormat() {
		return format;
	}

	public List getEnumValues() {
		return enumValues;
	}

	public Pattern getPattern() {
		return pattern;
	}

	public Integer getMinLength() {
		return minLength;
	}

	public Integer getMinItems() {
		return minItems;
	}

	public Double getDivisibleBy() {
		return divisibleBy;
	}

	public Map getPropertySchemas() {
		return propertySchemas;
	}

	public List getItems() {
		return items;
	}

	public String getDefaultValue() {
		return defaultValue;
	}

	public Map getPatternPropertySchemas() {
		return patternPropertySchemas;
	}

	public boolean allowsAdditionalProperties() {
		return allowsAdditionalProperties;
	}

	public boolean allowsAdditionalItems() {
		return allowsAdditionalItems;
	}

	public JsonSchemaObjectNode getAdditionalItems() {
		return additionalItems;
	}

	public JsonSchemaObjectNode getAdditionalProperties() {
		return additionalProperties;
	}

	public String getId() {
		return id;
	}

	public String getRef() {
		return _ref;
	}

	public String getSchema() {
		return _schema;
	}

	public boolean isArrayItems() {
		return arrayItems;
	}

	@Override
	public String toString() {
		return "JsonSchemaObjectNode{" +
				"sourceNode=" + sourceNode +
				'}';
	}

	public List getExtends() {
		return _extends;
	}

	public ObjectNode getSourceNode() {
		return sourceNode;
	}

	public List getExclusive()
	{
		return exclusive;
	}

	public boolean isExclusiveRequired()
	{
		return exclusiveRequired;
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy