org.wrml.runtime.format.application.schema.json.JsonSchema.java Source code

Java tutorial

Introduction

Here is the source code for org.wrml.runtime.format.application.schema.json.JsonSchema.java

Source

/**
 * WRML - Web Resource Modeling Language
 *  __     __   ______   __    __   __
 * /\ \  _ \ \ /\  == \ /\ "-./  \ /\ \
 * \ \ \/ ".\ \\ \  __< \ \ \-./\ \\ \ \____
 *  \ \__/".~\_\\ \_\ \_\\ \_\ \ \_\\ \_____\
 *   \/_/   \/_/ \/_/ /_/ \/_/  \/_/ \/_____/
 *
 * http://www.wrml.org
 *
 * Copyright (C) 2011 - 2013 Mark Masse <mark@wrml.org> (OSS project WRML.org)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.wrml.runtime.format.application.schema.json;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.TextNode;
import org.apache.commons.lang3.StringUtils;
import org.wrml.runtime.format.application.schema.json.JsonSchema.Definitions.JsonType;
import org.wrml.runtime.format.application.schema.json.JsonSchema.Definitions.PropertyType;
import org.wrml.runtime.syntax.SyntaxLoader;
import org.wrml.util.UniqueName;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;

/**
 * http://tools.ietf.org/html/draft-zyp-json-schema-03
 */
public class JsonSchema {

    private final JsonSchemaLoader _JsonSchemaLoader;

    private final ObjectNode _RootNode;

    private final URI _SchemaUri;

    private final ConcurrentMap<String, Property> _Properties;

    private final CopyOnWriteArrayList<Link> _Links;

    private final CopyOnWriteArraySet<URI> _ExtendsSet;

    private final CopyOnWriteArrayList<Property> _Required;

    private final Map<String, List<Property>> _Dependencies;

    JsonSchema(final JsonSchemaLoader loader, final ObjectNode rootNode) {

        _JsonSchemaLoader = loader;
        _RootNode = rootNode;

        final SyntaxLoader syntaxLoader = _JsonSchemaLoader.getContext().getSyntaxLoader();
        _SchemaUri = Definitions.PropertyType.$Schema.getValue(_RootNode, syntaxLoader);

        _ExtendsSet = new CopyOnWriteArraySet<>();

        _Properties = new ConcurrentHashMap<>();
        _Dependencies = new ConcurrentHashMap<>();
        _Required = new CopyOnWriteArrayList<>();
        _Links = new CopyOnWriteArrayList<>();

        // TODO not pass the args...
        parseExtensions(rootNode, syntaxLoader);
        parseProperties();
        parseLinks();
        parseRequireds();
        parseDependencies();
    }

    private void parseExtensions(final ObjectNode rootNode, final SyntaxLoader syntaxLoader) {
        // v3 uses the extends keyword
        if (rootNode.has(PropertyType.Extends.getName())) {
            final JsonNode extendsJsonNode = rootNode.get(PropertyType.Extends.getName());

            if (extendsJsonNode instanceof ArrayNode) {
                final ArrayNode extendsArrayNode = (ArrayNode) extendsJsonNode;
                final Iterator<JsonNode> elements = extendsArrayNode.elements();
                while (elements.hasNext()) {
                    final JsonNode baseSchemaUriNode = elements.next();
                    final String baseSchemaUriString = baseSchemaUriNode.asText();
                    if (baseSchemaUriString != null && !baseSchemaUriString.isEmpty()) {
                        final URI baseSchemaUri = syntaxLoader.parseSyntacticText(baseSchemaUriString, URI.class);
                        if (baseSchemaUri != null) {
                            _ExtendsSet.add(baseSchemaUri);
                        }
                    }
                }
            } else if (extendsJsonNode instanceof TextNode) {
                final String baseSchemaUriString = extendsJsonNode.asText();
                if (baseSchemaUriString != null && !baseSchemaUriString.isEmpty()) {
                    final URI baseSchemaUri = syntaxLoader.parseSyntacticText(baseSchemaUriString, URI.class);
                    if (baseSchemaUri != null) {
                        _ExtendsSet.add(baseSchemaUri);
                    }
                }

            }
        }

        // v4 uses the allOf keyword
        if (rootNode.has(PropertyType.AllOf.getName())) {
            final JsonNode allOfJsonNode = rootNode.get(PropertyType.AllOf.getName());

            // This element type MUST be an array
            if (allOfJsonNode instanceof ArrayNode) {
                final ArrayNode allOfArrayNode = (ArrayNode) allOfJsonNode;
                final Iterator<JsonNode> elements = allOfArrayNode.elements();
                while (elements.hasNext()) {
                    final JsonNode schemaNode = elements.next();
                    final JsonNode baseSchemaUriNode = schemaNode.get(PropertyType.$Ref.getName());
                    if (baseSchemaUriNode != null) {
                        final URI baseSchemaUri = syntaxLoader.parseSyntacticText(baseSchemaUriNode.asText(),
                                URI.class);
                        if (baseSchemaUri != null) {
                            _ExtendsSet.add(baseSchemaUri);
                        }
                    }
                }
            }
        }
    }

    private void parseProperties() {

        final ObjectNode propertiesNode = (ObjectNode) Definitions.PropertyType.Properties.getValueNode(_RootNode);

        if (propertiesNode != null) {

            final Iterator<String> propertyNames = propertiesNode.fieldNames();
            while (propertyNames.hasNext()) {
                final String name = propertyNames.next();

                final ObjectNode propertyNode = (ObjectNode) propertiesNode.get(name);

                Property property;
                try {
                    property = new Property(this, name, propertyNode);
                } catch (final IOException e) {
                    continue;
                }

                // Add to the required set if noted
                Object value = property.getValue(PropertyType.Required);
                if (value != null && (Boolean) value) {
                    _Required.add(property);
                }

                _Properties.put(name, property);

            }
        }
    }

    private void parseLinks() {

        final ArrayNode linksNode = Definitions.PropertyType.Links.getValueNode(_RootNode);

        if (linksNode != null) {

            for (final JsonNode linkNode : linksNode) {
                final Link link = new Link(this, (ObjectNode) linkNode);
                _Links.add(link);
            }

        }
    }

    private void parseRequireds() {

        final JsonNode requiredPreNode = Definitions.PropertyType.Required.getValueNode(_RootNode);

        if (requiredPreNode != null && requiredPreNode instanceof ArrayNode) {
            final ArrayNode requiredNode = (ArrayNode) requiredPreNode;
            for (final JsonNode requiredSubNode : requiredNode) {
                final String propertyName = requiredSubNode.asText();
                final Property property = _Properties.get(propertyName);
                _Required.add(property);
                if (property != null) {
                    ObjectNode node = property.getPropertyNode();
                    node.put(PropertyType.Required.getName(), true);
                }
            }
        }
    }

    private void parseDependencies() {

        final ObjectNode dependenciesNode = Definitions.PropertyType.Dependencies.getValueNode(_RootNode);

        if (dependenciesNode != null) {
            Iterator<Entry<String, JsonNode>> iter2 = dependenciesNode.fields();

            while (iter2.hasNext()) {
                Entry<String, JsonNode> current = iter2.next();
                String dependent = current.getKey();
                JsonNode dependencies = current.getValue();

                List<Property> propertyDependencies = new ArrayList<Property>();

                if (dependencies instanceof ArrayNode) {
                    ArrayNode dependenciesArray = (ArrayNode) dependencies;
                    Iterator<JsonNode> iter3 = dependenciesArray.iterator();

                    while (iter3.hasNext()) {
                        JsonNode dependency = iter3.next();
                        String depText = dependency.asText();
                        Property prop = _Properties.get(depText);
                        if (prop != null) {
                            propertyDependencies.add(prop);
                        }
                    }
                }

                // Only add if our list has values that map.
                if (!propertyDependencies.isEmpty()) {
                    _Dependencies.put(dependent, propertyDependencies);
                }
            }
        }
    }

    public static UniqueName createJsonSchemaUniqueName(final URI schemaUri) {

        String uniqueNameString = schemaUri.getPath();
        uniqueNameString = StringUtils.stripStart(uniqueNameString, "/");
        uniqueNameString = StringUtils.stripEnd(uniqueNameString, "#");
        if (uniqueNameString.endsWith(".json")) {
            uniqueNameString = uniqueNameString.substring(0, uniqueNameString.length() - ".json".length());
        }

        if (!uniqueNameString.contains("/")) {
            uniqueNameString = "schemas/" + uniqueNameString;
        }

        final UniqueName literalUniqueName = new UniqueName(uniqueNameString);
        return literalUniqueName;
    }

    public String getDescription() {

        final SyntaxLoader syntaxLoader = _JsonSchemaLoader.getContext().getSyntaxLoader();
        return Definitions.PropertyType.Description.getValue(getRootNode(), syntaxLoader);
    }

    public Set<URI> getExtendedSchemaUris() {

        return _ExtendsSet;
    }

    public URI getId() {

        final SyntaxLoader syntaxLoader = _JsonSchemaLoader.getContext().getSyntaxLoader();
        return Definitions.PropertyType.Id.getValue(_RootNode, syntaxLoader);
    }

    public List<Link> getLinks() {

        return _Links;
    }

    public List<Property> getRequired() {

        return _Required;
    }

    public Map<String, List<Property>> getDependencies() {

        return _Dependencies;
    }

    public JsonSchemaLoader getLoader() {

        return _JsonSchemaLoader;
    }

    public ConcurrentMap<String, Property> getProperties() {

        return _Properties;
    }

    public ObjectNode getRootNode() {

        return _RootNode;
    }

    public URI getSchemaUri() {

        return _SchemaUri;
    }

    public JsonNode getTypeNode() {

        return Definitions.PropertyType.Type.getValueNode(getRootNode());
    }

    @Override
    public String toString() {

        return String.valueOf(getRootNode());
    }

    /*
     * date-time This SHOULD be a date in ISO 8601 format of YYYY-MM-
     * DDThh:mm:ssZ in UTC time. This is the recommended form of date/
     * timestamp.
     *
     * date This SHOULD be a date in the format of YYYY-MM-DD. It is
     * recommended that you use the "date-time" format instead of "date"
     * unless you need to transfer only the date part.
     *
     * time This SHOULD be a time in the format of hh:mm:ss. It is
     * recommended that you use the "date-time" format instead of "time"
     * unless you need to transfer only the time part.
     *
     * utc-millisec This SHOULD be the difference, measured in
     * milliseconds, between the specified time and midnight, 00:00 of
     * January 1, 1970 UTC. The value SHOULD be a number (integer or
     * float).
     *
     * regex A regular expression, following the regular expression
     * specification from ECMA 262/Perl 5.
     *
     * color This is a CSS color (like "#FF0000" or "red"), based on CSS
     * 2.1 [W3C.CR-CSS21-20070719].
     *
     * style This is a CSS style definition (like "color: red; background-
     * color:#FFF"), based on CSS 2.1 [W3C.CR-CSS21-20070719].
     *
     * phone This SHOULD be a phone number (format MAY follow E.123).
     *
     * uri This value SHOULD be a URI..
     *
     * email This SHOULD be an email address.
     *
     * ip-address This SHOULD be an ip version 4 address.
     *
     * ipv6 This SHOULD be an ip version 6 address.
     *
     * host-name This SHOULD be a host-name.
     */
    public enum JsonStringFormat {
        DateTime("date-time", Date.class), Date("date", Date.class), Time("time", Date.class), Uri("uri",
                URI.class);

        private final static Map<String, JsonStringFormat> KEYWORD_MAP = new HashMap<>();

        private final static Map<Class<?>, JsonStringFormat> JAVA_TYPE_MAP = new HashMap<>();

        static {
            for (final JsonStringFormat jsonStringFormat : JsonStringFormat.values()) {
                KEYWORD_MAP.put(jsonStringFormat.getKeyword(), jsonStringFormat);
                JAVA_TYPE_MAP.put(jsonStringFormat.getJavaType(), jsonStringFormat);
            }
        }

        private final String _Keyword;

        private final Class<?> _JavaType;

        JsonStringFormat(final String keyword, final Class<?> javaType) {

            _Keyword = keyword;
            _JavaType = javaType;
        }

        public static JsonStringFormat forJavaType(final Class<?> javaType) {

            if (!JAVA_TYPE_MAP.containsKey(javaType)) {
                return null;
            }
            return JAVA_TYPE_MAP.get(javaType);
        }

        public static JsonStringFormat forKeyword(final String keyword) {

            if (!KEYWORD_MAP.containsKey(keyword)) {
                return null;
            }
            return KEYWORD_MAP.get(keyword);
        }

        public Class<?> getJavaType() {

            return _JavaType;
        }

        public String getKeyword() {

            return _Keyword;
        }

    }

    public static interface Definitions {

        /**
         * http://tools.ietf.org/html/draft-zyp-json-schema-03#section-5.1
         */
        public static enum JsonType {

            /**
             * Value MAY be of any type including null.
             */
            Any("any"),

            /**
             * Value MUST be an array.
             */
            Array("array"),

            /**
             * Value MUST be a boolean.
             */
            Boolean("boolean"),

            /**
             * Value MUST be an integer, no floating point numbers are allowed. This is a subset of the number type.
             */
            Integer("integer"),

            /**
             * Value MUST be null. Note this is mainly for purpose of being able use union types to define
             * nullability. If this type is not included in a union, null values are not allowed (the primitives
             * listed above do not allow nulls on their own).
             */
            Null("null"),

            /**
             * Value MUST be a number, floating point numbers are allowed.
             */
            Number("number"),

            /**
             * Value MUST be an object.
             */
            Object("object"),

            /**
             * Value MUST be a string.
             */
            String("string");

            private final static Map<String, JsonType> KEYWORD_MAP = new HashMap<>();

            static {
                for (final JsonType jsonType : JsonType.values()) {
                    KEYWORD_MAP.put(jsonType.getKeyword(), jsonType);
                }
            }

            private final String _Keyword;

            JsonType(final String keyword) {

                _Keyword = keyword;
            }

            public static JsonType forKeyword(final String keyword) {

                if (!KEYWORD_MAP.containsKey(keyword)) {
                    return null;
                }
                return KEYWORD_MAP.get(keyword);
            }

            public String getKeyword() {

                return _Keyword;
            }

        }

        /**
         * http://tools.ietf.org/html/draft-zyp-json-schema-03
         */
        public static enum PropertyType {

            AdditionalItems("additionalItems", JsonType.Any), AdditionalProperties("additionalProperties",
                    JsonType.Any), AllOf("allOf", JsonType.Object), AnyOf("anyOf", JsonType.Object), Default(
                            "default",
                            JsonType.Any), Definitions("definitions", JsonType.Object), Dependencies("dependencies",
                                    JsonType.Object), Description("description", JsonType.String), Disallow(
                                            "disallow", JsonType.Any), DivisibleBy("divisibleBy",
                                                    JsonType.Number), Enctype("enctype", JsonType.String), Enum(
                                                            "enum",
                                                            JsonType.Array), ExclusiveMaximum("exclusiveMaximum",
                                                                    JsonType.Boolean), ExclusiveMinimum(
                                                                            "exclusiveMinimum",
                                                                            JsonType.Boolean), Extends("extends",
                                                                                    JsonType.Any), Format("format",
                                                                                            JsonType.String), Href(
                                                                                                    "href",
                                                                                                    URI.class), Id(
                                                                                                            "id",
                                                                                                            URI.class), Items(
                                                                                                                    "items",
                                                                                                                    JsonType.Any), Links(
                                                                                                                            "links",
                                                                                                                            JsonType.Array), Maximum(
                                                                                                                                    "maximum",
                                                                                                                                    JsonType.Number), MaxItems(
                                                                                                                                            "maxItems",
                                                                                                                                            JsonType.Integer), MaxLength(
                                                                                                                                                    "maxLength",
                                                                                                                                                    JsonType.Integer), MaxProperties(
                                                                                                                                                            "maxProperties",
                                                                                                                                                            JsonType.Integer), Method(
                                                                                                                                                                    "method",
                                                                                                                                                                    JsonType.String), Minimum(
                                                                                                                                                                            "minimum",
                                                                                                                                                                            JsonType.Number), MinItems(
                                                                                                                                                                                    "minItems",
                                                                                                                                                                                    JsonType.Integer), MinLength(
                                                                                                                                                                                            "minLength",
                                                                                                                                                                                            JsonType.Integer), MinProperties(
                                                                                                                                                                                                    "minProperties",
                                                                                                                                                                                                    JsonType.Integer), MultipleOf(
                                                                                                                                                                                                            "multipleOf",
                                                                                                                                                                                                            JsonType.Number), Pattern(
                                                                                                                                                                                                                    "pattern",
                                                                                                                                                                                                                    JsonType.String), PatternProperties(
                                                                                                                                                                                                                            "patternProperties",
                                                                                                                                                                                                                            JsonType.Object), Properties(
                                                                                                                                                                                                                                    "properties",
                                                                                                                                                                                                                                    JsonType.Object), $Ref(
                                                                                                                                                                                                                                            "$ref",
                                                                                                                                                                                                                                            URI.class), Rel(
                                                                                                                                                                                                                                                    "rel",
                                                                                                                                                                                                                                                    JsonType.String),
            // TODO This field is now deprecated in the properties
            Required("required", JsonType.Boolean), $Schema("$schema", URI.class), Schema("schema",
                    JsonType.Object), TargetSchema("targetSchema", JsonType.Object), Title("title",
                            JsonType.String), Type("type",
                                    JsonType.Any), UniqueItems("uniqueItems", JsonType.Boolean);

            private final static Map<String, PropertyType> NAME_MAP = new HashMap<>();

            static {
                for (final PropertyType propertyType : PropertyType.values()) {
                    final String name = propertyType.getName();
                    if (NAME_MAP.containsKey(name)) {
                        throw new RuntimeException("Duplicate mappings detected.");
                    }

                    NAME_MAP.put(name, propertyType);
                }
            }

            private final String _Name;

            private final JsonType _JsonType;

            private final Object _DefaultValue;

            private final Class<?> _Format;

            private PropertyType(final String textPropertyName, final Class<?> format) {

                this(textPropertyName, JsonType.String, format, null);
            }

            private PropertyType(final String propertyName, final JsonType jsonType) {

                this(propertyName, jsonType, null, null);
            }

            private PropertyType(final String propertyName, final JsonType jsonType, final Class<?> format,
                    final Object defaultValue) {

                _Name = propertyName;
                _JsonType = jsonType;
                _Format = format;
                _DefaultValue = defaultValue;
            }

            private PropertyType(final String propertyName, final JsonType jsonType, final Object defaultValue) {

                this(propertyName, jsonType, null, defaultValue);
            }

            public static PropertyType forName(final String name) {

                if (!NAME_MAP.containsKey(name)) {
                    return null;
                }
                return NAME_MAP.get(name);
            }

            public Object getDefaultValue() {

                return _DefaultValue;
            }

            public Class<?> getFormat() {

                return _Format;
            }

            public JsonType getJsonType() {

                return _JsonType;
            }

            public String getName() {

                return _Name;
            }

            @SuppressWarnings("unchecked")
            public <T> T getValue(final JsonNode jsonNode, final SyntaxLoader syntaxLoader) {

                final JsonNode valueNode = getValueNode(jsonNode);
                if (valueNode == null) {
                    return null;
                }

                final T value;

                switch (_JsonType) {

                case Any: {

                    value = (T) valueNode.asText();
                    break;
                }
                case Array: {
                    value = null;
                    break;
                }
                case Boolean: {

                    if (valueNode.isBoolean()) {
                        value = (T) ((valueNode.asBoolean()) ? Boolean.TRUE : Boolean.FALSE);
                    } else {
                        value = null;
                    }

                    break;
                }
                case Integer: {

                    if (valueNode.isIntegralNumber()) {
                        value = (T) new Integer(valueNode.asInt());
                    } else {
                        value = null;
                    }

                    break;
                }
                case Null: {
                    value = null;
                    break;
                }
                case Number: {
                    if (valueNode.isFloatingPointNumber()) {
                        value = (T) new Double(valueNode.asDouble());
                    } else if (valueNode.isIntegralNumber()) {
                        value = (T) new Integer(valueNode.asInt());
                    } else {
                        value = null;
                    }

                    break;
                }
                case Object: {
                    value = (T) valueNode;
                    break;
                }
                case String: {

                    if (valueNode.isTextual()) {
                        final String textValue = valueNode.textValue();

                        if (_Format != null && textValue != null && !(textValue.trim()).isEmpty()) {
                            value = syntaxLoader.parseSyntacticText(textValue, _Format);
                        } else {
                            value = (T) textValue;
                        }
                    } else {
                        value = null;
                    }

                    break;
                }
                default: {
                    value = null;
                    break;
                }
                }

                return value;
            }

            @SuppressWarnings("unchecked")
            public <T extends JsonNode> T getValueNode(final JsonNode jsonNode) {

                return (T) jsonNode.get(_Name);
            }

            public void setValue(final ObjectNode jsonNode, final Object value, final SyntaxLoader syntaxLoader) {

                if (value == null) {
                    return;
                }

                final String name = getName();
                switch (_JsonType) {
                case Any: {
                    if (value instanceof String) {
                        jsonNode.put(name, (String) value);
                    } else if (value instanceof Integer) {
                        jsonNode.put(name, (Integer) value);
                    } else if (value instanceof Long) {
                        jsonNode.put(name, (Long) value);
                    } else if (value instanceof Double) {
                        jsonNode.put(name, (Double) value);
                    } else if (value instanceof Boolean) {
                        jsonNode.put(name, (Boolean) value);
                    } else {
                        final String formattedValue = syntaxLoader.formatSyntaxValue(value);
                        if (formattedValue != null) {
                            jsonNode.put(name, formattedValue);
                        }
                    }
                    break;
                }
                case Array: {
                    break;
                }
                case Boolean: {
                    jsonNode.put(name, (Boolean) value);
                    break;
                }
                case Integer: {
                    jsonNode.put(name, (Integer) value);
                    break;
                }
                case Null: {
                    break;
                }
                case Number: {
                    if (value instanceof Integer) {
                        jsonNode.put(name, (Integer) value);
                    } else if (value instanceof Long) {
                        jsonNode.put(name, (Long) value);
                    } else if (value instanceof Double) {
                        jsonNode.put(name, (Double) value);
                    }

                    break;
                }
                case Object: {
                    jsonNode.putObject(name);
                    break;

                }
                case String: {
                    jsonNode.put(name, syntaxLoader.formatSyntaxValue(value));
                    break;
                }
                default: {
                    break;
                }

                }

            }
        }

    }

    public static final class Link {

        private final JsonSchema _JsonSchema;

        private final ObjectNode _LinkNode;

        private final String _Rel;

        private final URI _RelId;

        private final URI _SchemaId;

        private final URI _TargetSchemaId;

        public Link(final JsonSchema jsonSchema, final ObjectNode linkNode) {

            if (jsonSchema == null || linkNode == null) {
                throw new NullPointerException();
            }
            _JsonSchema = jsonSchema;
            _LinkNode = linkNode;

            final SyntaxLoader syntaxLoader = getJsonSchema().getLoader().getContext().getSyntaxLoader();
            _Rel = PropertyType.Rel.getValue(getLinkNode(), syntaxLoader);

            URI linkRelationUri = null;
            try {
                linkRelationUri = new URI(_Rel);
            } catch (final URISyntaxException e) {

            }

            _RelId = linkRelationUri;

            // isolate()
            {
                final ObjectNode targetSchemaNode = PropertyType.TargetSchema.getValueNode(getLinkNode());
                if (targetSchemaNode == null) {
                    _TargetSchemaId = null;
                } else {
                    _TargetSchemaId = PropertyType.$Ref.getValue(targetSchemaNode, syntaxLoader);
                }
            }

            // isolate()
            {

                final ObjectNode schemaNode = PropertyType.Schema.getValueNode(getLinkNode());
                if (schemaNode == null) {
                    _SchemaId = null;
                } else {
                    _SchemaId = PropertyType.$Ref.getValue(schemaNode, syntaxLoader);
                }
            }

        }

        public JsonSchema getJsonSchema() {

            return _JsonSchema;
        }

        public ObjectNode getLinkNode() {

            return _LinkNode;
        }

        public String getRel() {

            return _Rel;
        }

        public URI getRelId() {

            return _RelId;
        }

        public URI getSchemaId() {

            return _SchemaId;
        }

        public URI getTargetSchemaId() {

            return _TargetSchemaId;
        }

    }

    public static final class Property {

        private final JsonSchema _JsonSchema;

        private final String _Name;

        private final ObjectNode _PropertyNode;

        private final JsonType _JsonType;

        public Property(final JsonSchema jsonSchema, final String name, final ObjectNode propertyNode)
                throws IOException {

            if (jsonSchema == null || name == null || propertyNode == null) {
                throw new NullPointerException();
            }

            _JsonSchema = jsonSchema;
            _Name = name;

            final boolean isReference = isReference(propertyNode);
            if (isReference) {
                final JsonSchema referencedJsonSchema = resolveReference(propertyNode);
                _PropertyNode = referencedJsonSchema.getRootNode();
            } else {
                _PropertyNode = propertyNode;
            }

            final JsonNode typeNode = PropertyType.Type.getValueNode(_PropertyNode);

            if (typeNode != null) {
                if (typeNode.isTextual()) {
                    final String typeName = typeNode.textValue();
                    _JsonType = Definitions.JsonType.forKeyword(typeName);
                } else if (typeNode instanceof ArrayNode) {
                    _JsonType = JsonType.String;
                } else {
                    _JsonType = null;
                }
            } else {
                _JsonType = null;
            }

        }

        public JsonSchema getJsonSchema() {

            return _JsonSchema;
        }

        public JsonType getJsonType() {

            return _JsonType;
        }

        public String getName() {

            return _Name;
        }

        public ObjectNode getPropertyNode() {

            return _PropertyNode;
        }

        void setRequired(final Boolean required) {

        }

        public PropertyType getPropertyType() {

            return PropertyType.forName(getName());
        }

        public <T> T getValue(final PropertyType propertyType) {

            final SyntaxLoader syntaxLoader = getJsonSchema().getLoader().getContext().getSyntaxLoader();
            return propertyType.getValue(getPropertyNode(), syntaxLoader);
        }

        public JsonNode getValueNode(final PropertyType propertyType) {

            return propertyType.getValueNode(getPropertyNode());
        }

        private boolean isReference(final JsonNode node) {

            final JsonNode refNode = PropertyType.$Ref.getValueNode(node);
            if (refNode == null) {
                return false;
            }
            final JsonSchemaLoader jsonSchemaLoader = getJsonSchema().getLoader();
            final SyntaxLoader syntaxLoader = jsonSchemaLoader.getContext().getSyntaxLoader();
            final URI refUri = PropertyType.$Ref.getValue(node, syntaxLoader);
            return (refUri != null);

        }

        private JsonSchema resolveReference(final JsonNode node) throws IOException {

            final JsonNode refNode = PropertyType.$Ref.getValueNode(node);
            if (refNode == null) {
                return null;
            }

            final JsonSchema jsonSchema = getJsonSchema();
            final JsonSchemaLoader jsonSchemaLoader = jsonSchema.getLoader();
            final SyntaxLoader syntaxLoader = jsonSchemaLoader.getContext().getSyntaxLoader();
            final URI refUri = PropertyType.$Ref.getValue(node, syntaxLoader);
            if (refUri == null) {
                return null;
            }

            final URI resolvedUri;
            if (refUri.isAbsolute()) {
                resolvedUri = refUri;
            } else {
                resolvedUri = jsonSchema.getId().resolve(refUri);
            }

            final JsonSchema referencedSchema = jsonSchemaLoader.load(resolvedUri);

            return referencedSchema;
        }

    }

}