Bug 494111 - Validate JSON file with JSON Schema Change-Id: I51491c77a6f270c17f6df3b84f47e5ccb7ea13bf Signed-off-by: Snjezana Peco <snjezana.peco@redhat.com> Signed-off-by: Victor Rubezhny <vrubezhny@redhat.com>
diff --git a/bundles/org.eclipse.json/src/org/eclipse/json/impl/schema/JSONSchemaDocument.java b/bundles/org.eclipse.json/src/org/eclipse/json/impl/schema/JSONSchemaDocument.java index 7294799..effb191 100644 --- a/bundles/org.eclipse.json/src/org/eclipse/json/impl/schema/JSONSchemaDocument.java +++ b/bundles/org.eclipse.json/src/org/eclipse/json/impl/schema/JSONSchemaDocument.java
@@ -13,10 +13,6 @@ import java.io.IOException; import java.io.Reader; import java.util.Collection; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; import org.eclipse.json.IValidationReporter; import org.eclipse.json.jsonpath.IJSONPath; @@ -24,77 +20,13 @@ import org.eclipse.json.provisonnal.com.eclipsesource.json.JsonValue; import org.eclipse.json.schema.IJSONSchemaDocument; import org.eclipse.json.schema.IJSONSchemaProperty; -import org.eclipse.json.schema.JSONSchemaType; @SuppressWarnings("serial") public class JSONSchemaDocument extends JSONSchemaNode implements IJSONSchemaDocument { - private static final String DEFINITIONS = "#/definitions/"; //$NON-NLS-1$ - private final Map<String, IJSONSchemaProperty> definitions; public JSONSchemaDocument(Reader reader) throws IOException { super(JsonObject.readFrom(reader), null); - this.definitions = new HashMap<String, IJSONSchemaProperty>(); - addDefinitions(getJsonObject()); - resolveReferences(); - } - - private void resolveReferences() { - resolveReference(this); - for (IJSONSchemaProperty definition : definitions.values()) { - resolveReference(definition); - } - Collection<IJSONSchemaProperty> props = getProperties().values(); - for (IJSONSchemaProperty property:props) { - resolveReference(property); - } - } - - private void resolveReference(IJSONSchemaProperty node) { - String reference = node.getReference(); - if (reference != null) { - String ref = reference.substring(DEFINITIONS.length()); - IJSONSchemaProperty property = definitions.get(ref); - if (property != null) { - for (IJSONSchemaProperty p : property.getProperties().values()) { - node.addProperty(p); - } - Collection<IJSONSchemaProperty> props = property.getProperties().values(); - for (IJSONSchemaProperty p : props) { - resolveReference(p); - } - } - } - Collection<IJSONSchemaProperty> props = node.getProperties().values(); - for (IJSONSchemaProperty p:props) { - resolveReference(p); - } - List<String> references = node.getReferences(); - for (String ref:references) { - String r = ref.substring(DEFINITIONS.length()); - IJSONSchemaProperty property = definitions.get(r); - if (property != null) { - for (IJSONSchemaProperty p:property.getProperties().values()) { - node.addProperty(p); - } - } - } - } - - private void addDefinitions(JsonObject json) { - Member member = null; - JsonObject definitions = (JsonObject) json.get("definitions"); - if (definitions != null) { - Iterator<Member> members = definitions.iterator(); - while (members.hasNext()) { - member = members.next(); - addDefinition(new JSONSchemaProperty(member.getName(), - (JsonObject) member.getValue(), this)); - } - } - } - private void addDefinition(IJSONSchemaProperty property) { - definitions.put(property.getName(), property); } @Override @@ -128,39 +60,14 @@ return null; } - @Override - public String getName() { - return null; - } - - @Override - public String getDescription() { - return null; - } - - @Override - public JSONSchemaType[] getType() { - return null; - } - - @Override - public JSONSchemaType getFirstType() { - return null; - } - public void validate(JsonValue value, IValidationReporter reporter) { // TODO Auto-generated method stub } @Override - public List<String> getEnumList() { - return null; - } - - @Override - public String getDefaultValue() { - return null; + public String getName() { + return ""; //$NON-NLS-1$ } }
diff --git a/bundles/org.eclipse.json/src/org/eclipse/json/impl/schema/JSONSchemaNode.java b/bundles/org.eclipse.json/src/org/eclipse/json/impl/schema/JSONSchemaNode.java index edf5edf..8f02638 100644 --- a/bundles/org.eclipse.json/src/org/eclipse/json/impl/schema/JSONSchemaNode.java +++ b/bundles/org.eclipse.json/src/org/eclipse/json/impl/schema/JSONSchemaNode.java
@@ -1,5 +1,5 @@ /** - * Copyright (c) 2013-2016 Angelo ZERR and others. + * Copyright (c) 2013-2016 Angelo ZERR. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -13,33 +13,133 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; +import java.util.LinkedList; import java.util.List; import java.util.Map; import org.eclipse.json.provisonnal.com.eclipsesource.json.JsonArray; import org.eclipse.json.provisonnal.com.eclipsesource.json.JsonObject; import org.eclipse.json.provisonnal.com.eclipsesource.json.JsonValue; +import org.eclipse.json.schema.IJSONSchemaDocument; import org.eclipse.json.schema.IJSONSchemaNode; import org.eclipse.json.schema.IJSONSchemaProperty; +import org.eclipse.json.schema.JSONSchemaType; @SuppressWarnings("serial") public class JSONSchemaNode extends JsonObject implements IJSONSchemaNode { - private static final String PROPERTIES = "properties"; //$NON-NLS-1$ - private static final String REF = "$ref"; //$NON-NLS-1$ + private Map<String, JsonValue> definitions; private final IJSONSchemaNode parent; private final Map<String, IJSONSchemaProperty> properties; - private final String reference; - private final List<String> references; private JsonObject jsonObject; + protected final String description; + protected JSONSchemaType[] type; + protected String defaultValue; + private List<String> enumList; public JSONSchemaNode(JsonObject jsonObject, IJSONSchemaNode parent) { this.parent = parent; this.jsonObject = jsonObject; + if (this instanceof IJSONSchemaDocument) { + definitions = new HashMap<String, JsonValue>(); + addDefinitions(); + resolveReferences(jsonObject); + } this.properties = new HashMap<String, IJSONSchemaProperty>(); - this.references = new ArrayList<String>(); - this.reference = jsonObject.getString(REF, null); - walk(jsonObject, this, true); + walk(jsonObject, this); + this.description = jsonObject.getString("description", null); //$NON-NLS-1$ + this.type = getType(jsonObject.get(TYPE)); + JsonValue value = jsonObject.get("default"); //$NON-NLS-1$ + if (value != null) { + defaultValue = removeQuote(value); + } + value = jsonObject.get(ENUM); + if (value instanceof JsonArray) { + JsonArray array = (JsonArray) value; + List<JsonValue> items = array.values(); + for (JsonValue v:items) { + if (v != null) { + if (type == null || type.length == 0) { + String str = v.toString(); + if (str.startsWith(QUOTE) && str.endsWith(QUOTE)) { + type = new JSONSchemaType[] {JSONSchemaType.String}; + } + } + addEnum(removeQuote(v)); + } + } + } + } + + private void resolveReferences(JsonObject json) { + Iterator<Member> members = json.iterator(); + while (members.hasNext()) { + Member member = members.next(); + JsonValue value = member.getValue(); + resolveReferences(json, member.getName(), value); + } + } + + private void resolveReferences(JsonObject parent, String name, JsonValue value) { + if (value instanceof JsonObject) { + JsonObject json = value.asObject(); + String ref = json.getString(REF, null); + if (ref != null && ref.startsWith(DEFINITIONS)) { + String r = ref.substring(DEFINITIONS.length()); + JsonValue v = definitions.get(r); + parent.set(name, v); + //json.remove(REF); + } else { + Iterator<Member> members = json.iterator(); + while (members.hasNext()) { + Member member = members.next(); + JsonValue v = member.getValue(); + resolveReferences(json, member.getName(), v); + } + } + } else if (value instanceof JsonArray) { + JsonArray jsonArray = (JsonArray) value; + for (int i = 0; i < jsonArray.size(); i++) { + JsonValue item = jsonArray.get(i); + if (item instanceof JsonObject) { + JsonObject json = item.asObject(); + String ref = json.getString(REF, null); + if (ref != null && ref.startsWith(DEFINITIONS)) { + String r = ref.substring(DEFINITIONS.length()); + JsonValue v = definitions.get(r); + jsonArray.set(i, v); + } else { + resolveReferences(json); + } + } + } + } + } + + private void addDefinitions() { + JsonValue defs = jsonObject.get("definitions"); //$NON-NLS-1$ + if (defs instanceof JsonObject) { + Iterator<Member> members = ((JsonObject) defs).iterator(); + while (members.hasNext()) { + Member member = members.next(); + JsonValue value = member.getValue(); + if (value instanceof JsonObject) { + definitions.put(member.getName(), member.getValue()); + } + } + } + } + + private void walk(JsonObject json, IJSONSchemaNode schemaNode) { + JsonObject properties = (JsonObject) json.get(PROPERTIES); + addProperties(schemaNode, properties); + add(json, schemaNode, ALL_OF); + add(json, schemaNode, ANY_OF); + add(json, schemaNode, ONE_OF); + JsonValue notMember = json.get(NOT); + if (notMember != null) { + walk(notMember.asObject(), schemaNode); + } } private void add(JsonObject jsonObject, IJSONSchemaNode schemaNode, String pref) { @@ -50,59 +150,21 @@ while (iter.hasNext()) { JsonValue value = iter.next(); if (value != null) { - String ref = value.asObject().getString(REF, null); - if (ref != null) { - references.add(ref); - } else { - walk(value.asObject(), schemaNode, true); - } + walk(value.asObject(), schemaNode); } } } } - private void walk(JsonObject json, IJSONSchemaNode schemaNode, boolean add) { - JsonObject properties = (JsonObject) json.get(PROPERTIES); - addProperties(schemaNode, properties, add); - if (properties == null) { - JsonObject items = (JsonObject) json.get("items"); //$NON-NLS-1$ - if (items != null) { - properties = (JsonObject) items.get(PROPERTIES); - addProperties(schemaNode, properties, add); - String ref = items.getString(REF, null); - if (ref != null) { - if (add) { - schemaNode.getReferences().add(ref); - } else { - schemaNode.getReferences().remove(ref); - } - } else { - walk(items, schemaNode, add); - } - } - } - add(json, schemaNode, "allOf"); //$NON-NLS-1$ - add(json, schemaNode, "anyOf"); //$NON-NLS-1$ - add(json, schemaNode, "oneOf"); //$NON-NLS-1$ - JsonValue notMember = json.get("not"); //$NON-NLS-1$ - if (notMember != null) { - walk(notMember.asObject(), schemaNode, false); - } - } - - private void addProperties(IJSONSchemaNode schemaNode, JsonObject properties, boolean add) { + private void addProperties(IJSONSchemaNode schemaNode, JsonObject properties) { if (properties == null) { return; } Iterator<Member> members = properties.iterator(); while (members.hasNext()) { Member member = members.next(); - if (add) { - schemaNode.addProperty( - new JSONSchemaProperty(member.getName(), (JsonObject) member.getValue(), schemaNode)); - } else { - schemaNode.getProperties().remove(member.getName()); - } + schemaNode + .addProperty(new JSONSchemaProperty(member.getName(), (JsonObject) member.getValue(), schemaNode)); } } @@ -132,17 +194,112 @@ } @Override - public String getReference() { - return reference; - } - - @Override - public List<String> getReferences() { - return references; - } - - @Override public Map<String, IJSONSchemaProperty> getProperties() { return properties; } + + protected String[] getRequired(JsonValue value) { + if (value == null) { + return null; + } + List<String> names = new ArrayList<String>(); + if (value.isString()) { + String s = value.asString(); + names.add(s); + } else if (value.isArray()) { + JsonArray array = (JsonArray) value; + for (JsonValue item : array) { + if (item.isString()) { + String s = item.asString(); + names.add(s); + } + } + } + return names.toArray(new String[0]); + } + + protected String removeQuote(JsonValue value) { + String str = value.toString(); + if (str.startsWith(QUOTE)) { + str = str.substring(1); + } + if (str.endsWith(QUOTE)) { + str = str.substring(0, str.length()-1); + } + return str; + } + + public static JSONSchemaType[] getType(JsonValue value) { + if (value == null) { + return JSONSchemaType.EMPTY_TYPES; + } + JSONSchemaType t = null; + List<JSONSchemaType> types = new ArrayList<JSONSchemaType>(); + if (value.isString()) { + t = JSONSchemaType.getType(value.asString()); + if (t != null) { + types.add(t); + } + } else if (value.isArray()) { + JsonArray array = (JsonArray) value; + for (JsonValue item : array) { + t = JSONSchemaType.getType(item.asString()); + if (t != null) { + types.add(t); + } + } + } + return types.toArray(JSONSchemaType.EMPTY_TYPES); + } + + @Override + public String getDescription() { + return description; + } + + @Override + public JSONSchemaType[] getType() { + return type; + } + + @Override + public JSONSchemaType getFirstType() { + if (type == null) { + return null; + } + if (type.length == 0) { + return null; + } + return type[0]; + } + + @Override + public String getDefaultValue() { + return defaultValue; + } + + @Override + public List<String> getEnumList() { + return enumList; + } + + public void addEnum(String item) { + if (enumList == null) { + enumList = new LinkedList<String>(); + } + this.enumList.add(item); + } + + @Override + public IJSONSchemaDocument getSchemaDocument() { + if (this instanceof IJSONSchemaDocument) { + return (IJSONSchemaDocument) this; + } + IJSONSchemaNode p = getParent(); + if (p != null) { + return p.getSchemaDocument(); + } + return null; + } + }
diff --git a/bundles/org.eclipse.json/src/org/eclipse/json/impl/schema/JSONSchemaProperty.java b/bundles/org.eclipse.json/src/org/eclipse/json/impl/schema/JSONSchemaProperty.java index 8ed90b2..0595212 100644 --- a/bundles/org.eclipse.json/src/org/eclipse/json/impl/schema/JSONSchemaProperty.java +++ b/bundles/org.eclipse.json/src/org/eclipse/json/impl/schema/JSONSchemaProperty.java
@@ -10,88 +10,19 @@ */ package org.eclipse.json.impl.schema; -import java.util.ArrayList; -import java.util.LinkedList; -import java.util.List; - -import org.eclipse.json.provisonnal.com.eclipsesource.json.JsonArray; import org.eclipse.json.provisonnal.com.eclipsesource.json.JsonObject; -import org.eclipse.json.provisonnal.com.eclipsesource.json.JsonValue; import org.eclipse.json.schema.IJSONSchemaNode; import org.eclipse.json.schema.IJSONSchemaProperty; -import org.eclipse.json.schema.JSONSchemaType; @SuppressWarnings("serial") public class JSONSchemaProperty extends JSONSchemaNode implements IJSONSchemaProperty { - private static final String QUOTE = "\""; //$NON-NLS-1$ - private final String name; - private final String description; - private JSONSchemaType[] type; - private String defaultValue; - private List<String> enumList; - + protected final String name; public JSONSchemaProperty(String name, JsonObject jsonObject, IJSONSchemaNode parent) { super(jsonObject, parent); this.name = name; - this.description = jsonObject.getString("description", null); //$NON-NLS-1$ - this.type = getType(jsonObject.get("type")); //$NON-NLS-1$ - JsonValue value = jsonObject.get("default"); //$NON-NLS-1$ - if (value != null) { - defaultValue = removeQuote(value); - } - value = jsonObject.get("enum"); //$NON-NLS-1$ - if (value instanceof JsonArray) { - JsonArray array = (JsonArray) value; - List<JsonValue> items = array.values(); - for (JsonValue v:items) { - if (v != null) { - if (type == null || type.length == 0) { - String str = v.toString(); - if (str.startsWith(QUOTE) && str.endsWith(QUOTE)) { - type = new JSONSchemaType[] {JSONSchemaType.String}; - } - } - addEnum(removeQuote(v)); - } - } - } - } - - private String removeQuote(JsonValue value) { - String str = value.toString(); - if (str.startsWith(QUOTE)) { - str = str.substring(1); - } - if (str.endsWith(QUOTE)) { - str = str.substring(0, str.length()-1); - } - return str; - } - - private JSONSchemaType[] getType(JsonValue value) { - if (value == null) { - return JSONSchemaType.EMPTY_TYPES; - } - JSONSchemaType t = null; - List<JSONSchemaType> types = new ArrayList<JSONSchemaType>(); - if (value.isString()) { - t = JSONSchemaType.getType(value.asString()); - if (t != null) { - types.add(t); - } - } else if (value.isArray()) { - JsonArray array = (JsonArray) value; - for (JsonValue item : array) { - t = JSONSchemaType.getType(item.asString()); - if (t != null) { - types.add(t); - } - } - } - return types.toArray(JSONSchemaType.EMPTY_TYPES); } @Override @@ -99,39 +30,4 @@ return name; } - @Override - public String getDescription() { - return description; - } - - @Override - public JSONSchemaType[] getType() { - return type; - } - - @Override - public JSONSchemaType getFirstType() { - if (type == null) { - return null; - } - if (type.length == 0) { - return null; - } - return type[0]; - } - - public String getDefaultValue() { - return defaultValue; - } - - public List<String> getEnumList() { - return enumList; - } - - public void addEnum(String item) { - if (enumList == null) { - enumList = new LinkedList<String>(); - } - this.enumList.add(item); - } }
diff --git a/bundles/org.eclipse.json/src/org/eclipse/json/schema/IJSONSchemaNode.java b/bundles/org.eclipse.json/src/org/eclipse/json/schema/IJSONSchemaNode.java index 1b12a8d..040c669 100644 --- a/bundles/org.eclipse.json/src/org/eclipse/json/schema/IJSONSchemaNode.java +++ b/bundles/org.eclipse.json/src/org/eclipse/json/schema/IJSONSchemaNode.java
@@ -1,5 +1,5 @@ /** - * Copyright (c) 2013-2016 Angelo ZERR and others. + * Copyright (c) 2013-2016 Angelo ZERR. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -17,19 +17,56 @@ public interface IJSONSchemaNode { + static final String NOT = "not"; //$NON-NLS-1$ + static final String ONE_OF = "oneOf"; //$NON-NLS-1$ + static final String ANY_OF = "anyOf"; //$NON-NLS-1$ + static final String ALL_OF = "allOf"; //$NON-NLS-1$ + static final String TYPE = "type"; //$NON-NLS-1$ + static final String REQUIRED = "required"; //$NON-NLS-1$ + static final String MIN_PROPERTIES = "minProperties"; //$NON-NLS-1$ + static final String MAX_PROPERTIES = "maxProperties"; //$NON-NLS-1$ + static final String PATTERN = "pattern"; //$NON-NLS-1$ + static final String MIN_LENGTH = "minLength"; //$NON-NLS-1$ + static final String MAX_LENGTH = "maxLength"; //$NON-NLS-1$ + static final String MULTIPLEOF = "multipleOf"; //$NON-NLS-1$ + static final String MAXIMUM = "maximum"; //$NON-NLS-1$ + static final String EXCLUSIVE_MAXIMUM = "exclusiveMaximum"; //$NON-NLS-1$ + static final String MINIMUM = "minimum"; //$NON-NLS-1$ + static final String EXCLUSIVE_MINIMUM = "exclusiveMinimum"; //$NON-NLS-1$ + static final String MIN_ITEMS = "minItems"; //$NON-NLS-1$ + static final String MAX_ITEMS = "maxItems"; //$NON-NLS-1$ + static final String UNIQUE_ITEMS = "uniqueItems"; //$NON-NLS-1$ + static final String ADDITIONAL_ITEMS = "additionalItems"; //$NON-NLS-1$ + static final String ITEMS = "items"; //$NON-NLS-1$ + static final String ADDITIONAL_PROPERTIES = "additionalProperties"; //$NON-NLS-1$ + static final String PATTERN_PROPERTIES = "patternProperties"; //$NON-NLS-1$ + static final String ENUM = "enum"; //$NON-NLS-1$ + String PROPERTIES = "properties"; //$NON-NLS-1$ + String REF = "$ref"; //$NON-NLS-1$ + String QUOTE = "\""; //$NON-NLS-1$ + String DEFINITIONS = "#/definitions/"; //$NON-NLS-1$ + + String getDescription(); + + JSONSchemaType[] getType(); + + JSONSchemaType getFirstType(); + + List<String> getEnumList(); + + String getDefaultValue(); + IJSONSchemaNode getParent(); IJSONSchemaProperty[] getPropertyValues(); - List<String> getReferences(); - Map<String, IJSONSchemaProperty> getProperties(); - String getReference(); - void setJsonObject(JsonObject jsonObject); JsonObject getJsonObject(); void addProperty(IJSONSchemaProperty property); + + IJSONSchemaDocument getSchemaDocument(); }
diff --git a/bundles/org.eclipse.json/src/org/eclipse/json/schema/IJSONSchemaProperty.java b/bundles/org.eclipse.json/src/org/eclipse/json/schema/IJSONSchemaProperty.java index dffaa6c..4ec26dd 100644 --- a/bundles/org.eclipse.json/src/org/eclipse/json/schema/IJSONSchemaProperty.java +++ b/bundles/org.eclipse.json/src/org/eclipse/json/schema/IJSONSchemaProperty.java
@@ -10,22 +10,10 @@ */ package org.eclipse.json.schema; -import java.util.List; - public interface IJSONSchemaProperty extends IJSONSchemaNode { IJSONSchemaProperty[] EMPTY_PROPERTY = new IJSONSchemaProperty[0]; - + String getName(); - String getDescription(); - - JSONSchemaType[] getType(); - - JSONSchemaType getFirstType(); - - List<String> getEnumList(); - - public String getDefaultValue(); - }
diff --git a/bundles/org.eclipse.wst.json.core/src/org/eclipse/wst/json/core/internal/preferences/JSONCorePreferenceInitializer.java b/bundles/org.eclipse.wst.json.core/src/org/eclipse/wst/json/core/internal/preferences/JSONCorePreferenceInitializer.java index 3c38a36..4144811 100644 --- a/bundles/org.eclipse.wst.json.core/src/org/eclipse/wst/json/core/internal/preferences/JSONCorePreferenceInitializer.java +++ b/bundles/org.eclipse.wst.json.core/src/org/eclipse/wst/json/core/internal/preferences/JSONCorePreferenceInitializer.java
@@ -30,6 +30,7 @@ // Validation preferences node.putBoolean(JSONCorePreferenceNames.SYNTAX_VALIDATION, false); + node.putBoolean(JSONCorePreferenceNames.SCHEMA_VALIDATION, false); node.putInt(JSONCorePreferenceNames.MISSING_BRACKET, 2); // formatting preferences
diff --git a/bundles/org.eclipse.wst.json.core/src/org/eclipse/wst/json/core/internal/schema/SchemaProcessorRegistryReader.java b/bundles/org.eclipse.wst.json.core/src/org/eclipse/wst/json/core/internal/schema/SchemaProcessorRegistryReader.java index c350eb1..fdaec89 100644 --- a/bundles/org.eclipse.wst.json.core/src/org/eclipse/wst/json/core/internal/schema/SchemaProcessorRegistryReader.java +++ b/bundles/org.eclipse.wst.json.core/src/org/eclipse/wst/json/core/internal/schema/SchemaProcessorRegistryReader.java
@@ -1,5 +1,5 @@ /** - * Copyright (c) 2013-2014 Angelo ZERR. + * Copyright (c) 2013, 2016 Angelo ZERR. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -11,6 +11,8 @@ package org.eclipse.wst.json.core.internal.schema; import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IConfigurationElement; @@ -22,9 +24,13 @@ import org.eclipse.wst.common.uriresolver.internal.provisional.URIResolverPlugin; import org.eclipse.wst.common.uriresolver.internal.util.URIHelper; import org.eclipse.wst.json.core.JSONCorePlugin; +import org.eclipse.wst.json.core.document.IJSONDocument; import org.eclipse.wst.json.core.document.IJSONModel; import org.eclipse.wst.json.core.document.IJSONNode; +import org.eclipse.wst.json.core.document.IJSONPair; +import org.eclipse.wst.json.core.document.IJSONValue; import org.eclipse.wst.json.core.internal.Logger; +import org.eclipse.wst.json.core.util.JSONUtil; public class SchemaProcessorRegistryReader { @@ -58,6 +64,30 @@ if (processor == null) { return null; } + IJSONDocument document = model.getDocument(); + IJSONNode jsonObject = document.getFirstChild(); + if (jsonObject != null) { + IJSONNode child = jsonObject.getFirstChild(); + while (child != null) { + if (child instanceof IJSONPair) { + IJSONPair pair = (IJSONPair) child; + String name = pair.getName(); + IJSONValue valueNode = pair.getValue(); + if (valueNode != null && "$schema".equals(name)) { //$NON-NLS-1$ + String schema = JSONUtil.getString(valueNode); + try { + if (schema != null) { + schema = URIHelper.addImpliedFileProtocol(schema); + new URL(schema); + return processor.getSchema(schema); + } + } catch (MalformedURLException e) { + } + } + } + child = child.getNextSibling(); + } + } String base = model == null || model.getResolver() == null ? null : model.getResolver().getFileBaseLocation(); /**
diff --git a/bundles/org.eclipse.wst.json.core/src/org/eclipse/wst/json/core/internal/validation/eclipse/Validator.java b/bundles/org.eclipse.wst.json.core/src/org/eclipse/wst/json/core/internal/validation/eclipse/Validator.java index f200af1..f16f034 100644 --- a/bundles/org.eclipse.wst.json.core/src/org/eclipse/wst/json/core/internal/validation/eclipse/Validator.java +++ b/bundles/org.eclipse.wst.json.core/src/org/eclipse/wst/json/core/internal/validation/eclipse/Validator.java
@@ -12,30 +12,73 @@ *******************************************************************************/ package org.eclipse.wst.json.core.internal.validation.eclipse; +import java.io.IOException; import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.ProjectScope; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.Platform; +import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.preferences.DefaultScope; +import org.eclipse.core.runtime.preferences.IEclipsePreferences; import org.eclipse.core.runtime.preferences.IScopeContext; import org.eclipse.core.runtime.preferences.InstanceScope; +import org.eclipse.json.impl.schema.JSONSchemaNode; +import org.eclipse.json.provisonnal.com.eclipsesource.json.JsonArray; +import org.eclipse.json.provisonnal.com.eclipsesource.json.JsonObject; +import org.eclipse.json.provisonnal.com.eclipsesource.json.JsonObject.Member; +import org.eclipse.json.provisonnal.com.eclipsesource.json.JsonValue; +import org.eclipse.json.schema.IJSONSchemaDocument; +import org.eclipse.json.schema.IJSONSchemaNode; +import org.eclipse.json.schema.IJSONSchemaProperty; +import org.eclipse.json.schema.JSONSchemaType; import org.eclipse.wst.json.core.JSONCorePlugin; +import org.eclipse.wst.json.core.document.IJSONArray; +import org.eclipse.wst.json.core.document.IJSONDocument; +import org.eclipse.wst.json.core.document.IJSONModel; +import org.eclipse.wst.json.core.document.IJSONNode; +import org.eclipse.wst.json.core.document.IJSONObject; +import org.eclipse.wst.json.core.document.IJSONPair; +import org.eclipse.wst.json.core.document.IJSONStringValue; +import org.eclipse.wst.json.core.document.IJSONValue; +import org.eclipse.wst.json.core.internal.schema.SchemaProcessorRegistryReader; import org.eclipse.wst.json.core.internal.validation.JSONNestedValidatorContext; import org.eclipse.wst.json.core.internal.validation.JSONValidationConfiguration; +import org.eclipse.wst.json.core.internal.validation.JSONValidationInfo; import org.eclipse.wst.json.core.internal.validation.JSONValidationReport; import org.eclipse.wst.json.core.internal.validation.core.AbstractNestedValidator; import org.eclipse.wst.json.core.internal.validation.core.NestedValidatorContext; import org.eclipse.wst.json.core.internal.validation.core.ValidationMessage; import org.eclipse.wst.json.core.internal.validation.core.ValidationReport; import org.eclipse.wst.json.core.preferences.JSONCorePreferenceNames; +import org.eclipse.wst.json.core.util.JSONUtil; +import org.eclipse.wst.sse.core.StructuredModelManager; +import org.eclipse.wst.sse.core.internal.provisional.IModelManager; +import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel; import org.eclipse.wst.validation.ValidationResult; import org.eclipse.wst.validation.ValidationState; import org.eclipse.wst.validation.internal.provisional.core.IMessage; public class Validator extends AbstractNestedValidator { + private static final String CLOSE_BRACKET = "]"; //$NON-NLS-1$ + private static final String OPEN_BRACKET = "["; //$NON-NLS-1$ + private static final String COMMA = ","; //$NON-NLS-1$ private static final String JSON_VALIDATOR_CONTEXT = "org.eclipse.wst.json.core.validatorContext"; //$NON-NLS-1$ protected int indicateNoGrammar = 0; private IScopeContext[] fPreferenceScopes = null; @@ -45,81 +88,786 @@ super.setupValidation(context); fPreferenceScopes = createPreferenceScopes(context); indicateNoGrammar = Platform.getPreferencesService().getInt( - JSONCorePlugin.getDefault().getBundle().getSymbolicName(), - JSONCorePreferenceNames.INDICATE_NO_GRAMMAR, 0, - fPreferenceScopes); + JSONCorePlugin.getDefault().getBundle().getSymbolicName(), JSONCorePreferenceNames.INDICATE_NO_GRAMMAR, + 0, fPreferenceScopes); } - protected IScopeContext[] createPreferenceScopes( - NestedValidatorContext context) { + protected IScopeContext[] createPreferenceScopes(NestedValidatorContext context) { if (context != null) { final IProject project = context.getProject(); if (project != null && project.isAccessible()) { final ProjectScope projectScope = new ProjectScope(project); - if (projectScope.getNode( - JSONCorePlugin.getDefault().getBundle() - .getSymbolicName()).getBoolean( - JSONCorePreferenceNames.USE_PROJECT_SETTINGS, false)) - return new IScopeContext[] { projectScope, - new InstanceScope(), new DefaultScope() }; + if (projectScope.getNode(JSONCorePlugin.getDefault().getBundle().getSymbolicName()) + .getBoolean(JSONCorePreferenceNames.USE_PROJECT_SETTINGS, false)) + return new IScopeContext[] { projectScope, new InstanceScope(), new DefaultScope() }; } } return new IScopeContext[] { new InstanceScope(), new DefaultScope() }; } @Override - public ValidationReport validate(String uri, InputStream inputstream, - NestedValidatorContext context) { + public ValidationReport validate(String uri, InputStream inputstream, NestedValidatorContext context) { return validate(uri, inputstream, context, null); } @Override - public ValidationReport validate(String uri, InputStream inputstream, - NestedValidatorContext context, ValidationResult result) { + public ValidationReport validate(String uri, InputStream inputstream, NestedValidatorContext context, + ValidationResult result) { JSONValidator validator = JSONValidator.getInstance(); - JSONValidationConfiguration configuration = new JSONValidationConfiguration(); - // try { - // // Preferences pluginPreferences = - // // JSONCorePlugin.getDefault().getPluginPreferences(); - // configuration.setFeature( - // JSONValidationConfiguration.INDICATE_NO_GRAMMAR, - // indicateNoGrammar); - // final IPreferencesService preferencesService = Platform - // .getPreferencesService(); - // configuration - // .setFeature( - // JSONValidationConfiguration.INDICATE_NO_DOCUMENT_ELEMENT, - // preferencesService - // .getInt(JSONCorePlugin.getDefault() - // .getBundle().getSymbolicName(), - // JSONCorePreferenceNames.INDICATE_NO_DOCUMENT_ELEMENT, - // -1, fPreferenceScopes)); - // configuration.setFeature(JSONValidationConfiguration.USE_XINCLUDE, - // preferencesService.getBoolean(JSONCorePlugin.getDefault() - // .getBundle().getSymbolicName(), - // JSONCorePreferenceNames.USE_XINCLUDE, false, - // fPreferenceScopes)); - // configuration - // .setFeature( - // JSONValidationConfiguration.HONOUR_ALL_SCHEMA_LOCATIONS, - // preferencesService - // .getBoolean( - // JSONCorePlugin.getDefault() - // .getBundle() - // .getSymbolicName(), - // JSONCorePreferenceNames.HONOUR_ALL_SCHEMA_LOCATIONS, - // true, fPreferenceScopes)); - // } catch (Exception e) { - // // TODO: Unable to set the preference. Log this problem. - // } - - JSONValidationReport valreport = validator.validate(uri, inputstream, - configuration, result, context); - + JSONValidationReport valreport = validator.validate(uri, inputstream, configuration, result, context); + String prefs = JSONCorePlugin.getDefault().getBundle().getSymbolicName(); + IEclipsePreferences modelPreferences = InstanceScope.INSTANCE.getNode(prefs); + boolean validateSchema = modelPreferences.getBoolean(JSONCorePreferenceNames.SCHEMA_VALIDATION, false); + if (validateSchema) { + IJSONModel model = null; + try { + IStructuredModel temp = getModel(uri); + if (!(temp instanceof IJSONModel)) { + return valreport; + } + model = (IJSONModel) temp; + IJSONSchemaDocument schemaDocument = SchemaProcessorRegistryReader.getInstance() + .getSchemaDocument(model); + if (schemaDocument != null) { + JSONValidationInfo valinfo = null; + if (valreport instanceof JSONValidationInfo) { + valinfo = (JSONValidationInfo) valreport; + } else { + valinfo = new JSONValidationInfo(uri); + } + validate(model, schemaDocument, valinfo); + // ValidationMessage[] messages = + // valreport.getValidationMessages(); + return valreport; + } + } catch (IOException e) { + logWarning(e); + return valreport; + } finally { + if (model != null) { + model.releaseFromRead(); + } + } + } return valreport; } + private void validate(IJSONModel model, IJSONSchemaProperty schemaProperty, JSONValidationInfo valinfo) { + IJSONDocument document = model.getDocument(); + IJSONNode node = document.getFirstChild(); + while (node != null) { + validate(node, schemaProperty, valinfo); + node = node.getNextSibling(); + } + } + + private void validate(IJSONNode node, IJSONSchemaProperty schemaProperty, JSONValidationInfo valinfo) { + if (node == null || schemaProperty == null) { + return; + } + JsonObject schema = schemaProperty.getJsonObject(); + validate(node, schema, valinfo); + IJSONNode child = node.getFirstChild(); + while (child != null) { + IJSONSchemaProperty property = schemaProperty.getSchemaDocument().getProperty(child.getPath()); + validate(child, property, valinfo); + if (child instanceof IJSONPair) { + IJSONValue value = ((IJSONPair) child).getValue(); + if (value instanceof IJSONObject) { + IJSONSchemaProperty prop = schemaProperty.getSchemaDocument().getProperty(value.getPath()); + validate(value, prop, valinfo); + } + } + child = child.getNextSibling(); + } + } + + private void validate(IJSONNode node, JsonObject schema, JSONValidationInfo valinfo) { + Iterator<Member> members = schema.iterator(); + while (members.hasNext()) { + Member member = members.next(); + validate(node, schema, member, valinfo); + } + } + + private void validate(IJSONNode node, JsonObject schema, Member member, JSONValidationInfo valinfo) { + if (IJSONSchemaNode.ALL_OF.equals(member.getName()) && member.getValue() instanceof JsonArray) { + JsonArray jsonArray = (JsonArray) member.getValue(); + Iterator<JsonValue> iter = jsonArray.iterator(); + while (iter.hasNext()) { + JsonValue value = iter.next(); + if (value instanceof JsonObject) { + validate(node, (JsonObject) value, valinfo); + } + } + } + if (IJSONSchemaNode.ANY_OF.equals(member.getName()) && member.getValue() instanceof JsonArray) { + JsonArray jsonArray = (JsonArray) member.getValue(); + Iterator<JsonValue> iter = jsonArray.iterator(); + while (iter.hasNext()) { + JsonValue value = iter.next(); + if (value instanceof JsonObject) { + JSONValidationInfo info = new JSONValidationInfo(""); + validate(node, (JsonObject) value, info); + if (info.getValidationMessages() == null || info.getValidationMessages().length == 0) { + return; + } + } + } + int offset = node.getStartOffset(); + int line = node.getModel().getStructuredDocument().getLineOfOffset(offset); + valinfo.addMessage("Matches a schema that is not allowed", line, 0, offset == 0 ? 1 : offset); + } + if (IJSONSchemaNode.ONE_OF.equals(member.getName()) && member.getValue() instanceof JsonArray) { + JsonArray jsonArray = (JsonArray) member.getValue(); + Iterator<JsonValue> iter = jsonArray.iterator(); + int count = 0; + while (iter.hasNext()) { + JsonValue value = iter.next(); + if (value instanceof JsonObject) { + JSONValidationInfo info = new JSONValidationInfo(""); + validate(node, (JsonObject) value, info); + if (info.getValidationMessages() == null || info.getValidationMessages().length == 0) { + count = count + 1; + } + } + } + if (count != 1) { + int offset = node.getStartOffset(); + int line = node.getModel().getStructuredDocument().getLineOfOffset(offset); + valinfo.addMessage("Matches a schema that is not allowed", line, 0, offset == 0 ? 1 : offset); + } + } + if (IJSONSchemaNode.NOT.equals(member.getName()) && member.getValue() instanceof JsonObject) { + JsonObject json = (JsonObject) member.getValue(); + JSONValidationInfo info = new JSONValidationInfo(""); + validate(node, json, info); + if (info.getValidationMessages() == null || info.getValidationMessages().length == 0) { + int offset = node.getStartOffset(); + int line = node.getModel().getStructuredDocument().getLineOfOffset(offset); + valinfo.addMessage("Matches a schema that is not allowed", line, 0, offset == 0 ? 1 : offset); + } + } + if (IJSONSchemaNode.TYPE.equals(member.getName())) { + validateType(node, member, valinfo); + } + if (IJSONSchemaNode.ENUM.equals(member.getName())) { + validateEnum(node, schema, valinfo); + } + if (node.getNodeType() == IJSONNode.OBJECT_NODE) { + if (IJSONSchemaNode.REQUIRED.equals(member.getName())) { + validateRequired(node, schema, valinfo); + } + if (IJSONSchemaNode.MAX_PROPERTIES.equals(member.getName())) { + validateMaxProperties(node, schema, valinfo); + } + if (IJSONSchemaNode.MIN_PROPERTIES.equals(member.getName())) { + validateMinProperties(node, schema, valinfo); + } + if (IJSONSchemaNode.ADDITIONAL_PROPERTIES.equals(member.getName())) { + validateAdditionalProperties(node, schema, member.getValue(), valinfo); + } + } + if (node.getNodeType() == IJSONNode.PAIR_NODE) { + IJSONValue value = ((IJSONPair) node).getValue(); + JSONSchemaType[] types = JSONSchemaNode.getType(schema.get(IJSONSchemaNode.TYPE)); + if (value != null) { + if (value.getNodeType() == IJSONNode.VALUE_STRING_NODE && isType(types, JSONSchemaType.String)) { + validateString(node, schema, member, valinfo, value); + } + if (value.getNodeType() == IJSONNode.VALUE_NUMBER_NODE + && (isType(types, JSONSchemaType.Integer) || isType(types, JSONSchemaType.Number))) { + validateNumber(node, schema, member, valinfo, value); + } + if (value.getNodeType() == IJSONNode.ARRAY_NODE && isType(types, JSONSchemaType.Array)) { + validateArray(node, schema, member, valinfo, value); + } + } + } + } + + private void validateAdditionalProperties(IJSONNode node, JsonObject schema, JsonValue value, + JSONValidationInfo valinfo) { + if (value != null && value.isBoolean() && !value.asBoolean()) { + Set<String> s = getProperties(node); + Set<String> p = getProperties(schema.get(IJSONSchemaNode.PROPERTIES)); + Set<String> pp = getProperties(schema.get(IJSONSchemaNode.PATTERN_PROPERTIES)); + for (String string : p) { + if (s.contains(string)) { + s.remove(string); + } + } + for (String patternStr : pp) { + Pattern pattern = Pattern.compile(patternStr); + Iterator<String> iter = s.iterator(); + while (iter.hasNext()) { + String ss = iter.next(); + if (ss != null) { + Matcher matcher = pattern.matcher(ss); + if (matcher.find()) { + iter.remove(); + } + } + } + } + for (String string : s) { + if ("$schema".equals(string)) { //$NON-NLS-1$ + continue; + } + IJSONNode n = node.getFirstChild(); + while (n != null) { + if (n instanceof IJSONPair) { + IJSONPair pair = (IJSONPair) n; + if (string.equals(pair.getName())) { + break; + } + } + n = n.getNextSibling(); + } + if (n == null) { + node = n; + } + int offset = n.getStartOffset(); + int line = n.getModel().getStructuredDocument().getLineOfOffset(offset); + valinfo.addMessage("Property " + string + " is not allowed", line, 0, offset == 0 ? 1 : offset); + } + } + } + + private Set<String> getProperties(JsonValue value) { + Set<String> result = new HashSet<String>(); + if (value instanceof JsonObject) { + Iterator<Member> members = ((JsonObject) value).iterator(); + while (members.hasNext()) { + result.add(members.next().getName()); + } + } + return result; + } + + private void validateArray(IJSONNode node, JsonObject schema, Member member, JSONValidationInfo valinfo, + IJSONValue value) { + if (IJSONSchemaNode.ITEMS.equals(member.getName())) { + validateItems(node, schema, value, member.getValue(), valinfo); + } + if (IJSONSchemaNode.MAX_ITEMS.equals(member.getName())) { + validateMaxItems(node, schema, value, member.getValue(), valinfo); + } + if (IJSONSchemaNode.MIN_ITEMS.equals(member.getName())) { + validateMinItems(node, schema, value, member.getValue(), valinfo); + } + if (IJSONSchemaNode.UNIQUE_ITEMS.equals(member.getName())) { + validateUniqueItems(node, schema, value, member.getValue(), valinfo); + } + } + + private void validateMaxItems(IJSONNode node, JsonObject schema, IJSONValue value, JsonValue memberValue, + JSONValidationInfo valinfo) { + if (memberValue != null && memberValue.isNumber() && memberValue.asInt() > 0) { + if (value instanceof IJSONArray) { + int instanceSize = getSize((IJSONArray) value); + int size = memberValue.asInt(); + if (instanceSize > size) { + int offset = node.getStartOffset(); + int line = node.getModel().getStructuredDocument().getLineOfOffset(offset); + valinfo.addMessage("Array has too many items. Expected " + size + " or fewer", line, 0, + offset == 0 ? 1 : offset); + } + } + } + } + + private void validateMinItems(IJSONNode node, JsonObject schema, IJSONValue value, JsonValue memberValue, + JSONValidationInfo valinfo) { + if (memberValue != null && memberValue.isNumber() && memberValue.asInt() > 0) { + if (value instanceof IJSONArray) { + int instanceSize = getSize((IJSONArray) value); + int size = memberValue.asInt(); + if (instanceSize < size) { + int offset = node.getStartOffset(); + int line = node.getModel().getStructuredDocument().getLineOfOffset(offset); + valinfo.addMessage("Array has too few items. Expected " + size + " or more", line, 0, + offset == 0 ? 1 : offset); + } + } + } + } + + private void validateUniqueItems(IJSONNode node, JsonObject schema, IJSONValue value, JsonValue memberValue, + JSONValidationInfo valinfo) { + if (memberValue != null && memberValue.isBoolean() && memberValue.asBoolean()) { + if (value instanceof IJSONArray) { + Set<String> instanceValues = new HashSet<String>(); + IJSONNode child = value.getFirstChild(); + int instanceSize = 0; + while (child != null) { + instanceSize = instanceSize + 1; + instanceValues.add(JSONUtil.getString(child)); + child = child.getNextSibling(); + } + ; + if (instanceSize != instanceValues.size()) { + int offset = node.getStartOffset(); + int line = node.getModel().getStructuredDocument().getLineOfOffset(offset); + valinfo.addMessage("Array has duplicate items", line, 0, offset == 0 ? 1 : offset); + } + } + } + } + + private void validateItems(IJSONNode node, JsonObject schema, IJSONValue value, JsonValue memberValue, + JSONValidationInfo valinfo) { + JsonValue additionalItems = schema.get(IJSONSchemaNode.ADDITIONAL_ITEMS); + if (additionalItems != null && additionalItems.isBoolean() && !additionalItems.asBoolean()) { + if (memberValue != null && memberValue.isArray()) { + if (value instanceof IJSONArray) { + int instanceSize = getSize((IJSONArray) value); + int size = memberValue.asArray().size(); + if (instanceSize > size) { + int offset = node.getStartOffset(); + int line = node.getModel().getStructuredDocument().getLineOfOffset(offset); + valinfo.addMessage("Array has too many items. Expected " + size + " or fewer", line, 0, + offset == 0 ? 1 : offset); + } + } + } + } + } + + private int getSize(IJSONArray instance) { + if (instance == null) { + return 0; + } + int instanceSize = 0; + IJSONNode child = instance.getFirstChild(); + while (child != null) { + instanceSize = instanceSize + 1; + child = child.getNextSibling(); + } + return instanceSize; + } + + private void validateNumber(IJSONNode node, JsonObject schema, Member member, JSONValidationInfo valinfo, + IJSONValue value) { + if (IJSONSchemaNode.MULTIPLEOF.equals(member.getName())) { + validateMultipleOf(node, schema, value, valinfo); + } + if (IJSONSchemaNode.MAXIMUM.equals(member.getName())) { + validateMaximum(node, schema, value, valinfo); + } + if (IJSONSchemaNode.MINIMUM.equals(member.getName())) { + validateMinimum(node, schema, value, valinfo); + } + } + + private void validateMaximum(IJSONNode node, JsonObject schema, IJSONValue valueNode, JSONValidationInfo valinfo) { + double maximum; + try { + maximum = schema.getDouble(IJSONSchemaNode.MAXIMUM, Double.MIN_VALUE); + } catch (Exception e) { + maximum = Double.MIN_VALUE; + } + if (maximum > Double.MIN_VALUE) { + boolean exclusiveMaximum; + try { + exclusiveMaximum = schema.getBoolean(IJSONSchemaNode.EXCLUSIVE_MAXIMUM, false); + } catch (Exception e1) { + exclusiveMaximum = false; + } + String valueStr = JSONUtil.getString(valueNode); + try { + double value = new Double(valueStr).doubleValue(); + boolean valid = exclusiveMaximum ? value < maximum : value <= maximum; + if (!valid) { + int offset = node.getStartOffset(); + int line = node.getModel().getStructuredDocument().getLineOfOffset(offset); + if (exclusiveMaximum) { + valinfo.addMessage("Value is above the exclusive maximum of " + maximum, line, 0, + offset == 0 ? 1 : offset); + } else { + valinfo.addMessage("Value is above the maximum of " + maximum, line, 0, + offset == 0 ? 1 : offset); + } + } + } catch (NumberFormatException e) { + // ignore + return; + } + } + } + + private void validateMinimum(IJSONNode node, JsonObject schema, IJSONValue valueNode, JSONValidationInfo valinfo) { + double minimum; + try { + minimum = schema.getDouble(IJSONSchemaNode.MINIMUM, Double.MAX_VALUE); + } catch (Exception e) { + minimum = Double.MAX_VALUE; + } + if (minimum < Double.MAX_VALUE) { + boolean exclusiveMinimum; + try { + exclusiveMinimum = schema.getBoolean(IJSONSchemaNode.EXCLUSIVE_MINIMUM, false); + } catch (Exception e1) { + exclusiveMinimum = false; + } + String valueStr = JSONUtil.getString(valueNode); + try { + double value = new Double(valueStr).doubleValue(); + boolean valid = exclusiveMinimum ? value > minimum : value >= minimum; + if (!valid) { + int offset = node.getStartOffset(); + int line = node.getModel().getStructuredDocument().getLineOfOffset(offset); + if (exclusiveMinimum) { + valinfo.addMessage("Value is below the exclusive minimum of " + minimum, line, 0, + offset == 0 ? 1 : offset); + } else { + valinfo.addMessage("Value is below the minimum of " + minimum, line, 0, + offset == 0 ? 1 : offset); + } + } + } catch (NumberFormatException e) { + // ignore + return; + } + } + } + + private void validateMultipleOf(IJSONNode node, JsonObject schema, IJSONValue valueNode, + JSONValidationInfo valinfo) { + int multipleOff; + try { + multipleOff = schema.getInt(IJSONSchemaNode.MULTIPLEOF, -1); + } catch (Exception e) { + multipleOff = -1; + } + if (multipleOff > 0) { + String value = JSONUtil.getString(valueNode); + double n; + try { + n = new Double(value).doubleValue(); + } catch (NumberFormatException e) { + // ignore + return; + } + long div = Math.round(n / multipleOff); + if (Math.abs(div * multipleOff - n) > 1e-12) { + int offset = node.getStartOffset(); + int line = node.getModel().getStructuredDocument().getLineOfOffset(offset); + valinfo.addMessage("Value is not divisible by " + multipleOff, line, 0, offset == 0 ? 1 : offset); + } + } + } + + private boolean isType(JSONSchemaType[] types, JSONSchemaType type) { + for (JSONSchemaType t : types) { + if (t == type) { + return true; + } + } + return false; + } + + private void validateString(IJSONNode node, JsonObject schema, Member member, JSONValidationInfo valinfo, + IJSONValue value) { + if (IJSONSchemaNode.MIN_LENGTH.equals(member.getName())) { + validateMinLength(node, schema, value, valinfo); + } + if (IJSONSchemaNode.MAX_LENGTH.equals(member.getName())) { + validateMaxLength(node, schema, value, valinfo); + } + if (IJSONSchemaNode.PATTERN.equals(member.getName())) { + validatePattern(node, schema, value, valinfo); + } + } + + private void validateMaxLength(IJSONNode node, JsonObject schema, IJSONValue valueNode, + JSONValidationInfo valinfo) { + int maxLength; + try { + maxLength = schema.getInt(IJSONSchemaNode.MAX_LENGTH, -1); + } catch (Exception e) { + maxLength = -1; + } + if (maxLength >= 0) { + String value = JSONUtil.getString(valueNode); + boolean valid = value == null || value.length() <= maxLength; + if (!valid) { + int offset = node.getStartOffset(); + int line = node.getModel().getStructuredDocument().getLineOfOffset(offset); + valinfo.addMessage("String is longer than the maximum length of " + maxLength, line, 0, + offset == 0 ? 1 : offset); + } + } + } + + private void validateMinLength(IJSONNode node, JsonObject schema, IJSONValue valueNode, + JSONValidationInfo valinfo) { + int minLength; + try { + minLength = schema.getInt(IJSONSchemaNode.MIN_LENGTH, -1); + } catch (Exception e) { + minLength = -1; + } + if (minLength >= 0) { + String value = JSONUtil.getString(valueNode); + boolean valid = value == null || value.length() >= minLength; + if (!valid) { + int offset = node.getStartOffset(); + int line = node.getModel().getStructuredDocument().getLineOfOffset(offset); + valinfo.addMessage("String is shorter than the minimum length of " + minLength, line, 0, + offset == 0 ? 1 : offset); + } + } + } + + private void validatePattern(IJSONNode node, JsonObject schema, IJSONValue valueNode, JSONValidationInfo valinfo) { + String patternStr; + try { + patternStr = schema.getString(IJSONSchemaNode.PATTERN, null); + } catch (Exception e) { + patternStr = null; + } + if (patternStr != null) { + String value = JSONUtil.getString(valueNode); + if (value != null) { + Pattern pattern = Pattern.compile(patternStr); + Matcher matcher = pattern.matcher(value); + if (!matcher.matches()) { + int offset = node.getStartOffset(); + int line = node.getModel().getStructuredDocument().getLineOfOffset(offset); + valinfo.addMessage("String does not match the pattern of " + patternStr, line, 0, + offset == 0 ? 1 : offset); + } + } + } + } + + private void validateType(IJSONNode node, Member member, JSONValidationInfo valinfo) { + if (IJSONSchemaNode.TYPE.equals(member.getName())) { + Set<String> types = new HashSet<String>(); + if (member.getValue().isString()) { + types.add(member.getValue().asString()); + } else if (member.getValue().isArray()) { + JsonArray array = (JsonArray) member.getValue(); + for (JsonValue item : array) { + types.add(item.asString()); + } + } + boolean valid = false; + for (String type : types) { + if (node.getNodeType() == IJSONNode.OBJECT_NODE && JSONSchemaType.Object.getName().equals(type)) { + valid = true; + break; + } + if (node.getNodeType() == IJSONNode.PAIR_NODE) { + IJSONValue value = ((IJSONPair) node).getValue(); + if (value == null && JSONSchemaType.Null.getName().equals(type)) { + valid = true; + break; + } + if (value == null) { + valid = false; + break; + } + if (value.getNodeType() == IJSONNode.OBJECT_NODE && JSONSchemaType.Object.getName().equals(type)) { + valid = true; + break; + } + if (value.getNodeType() == IJSONNode.VALUE_STRING_NODE + && JSONSchemaType.String.getName().equals(type)) { + valid = true; + break; + } + if (value.getNodeType() == IJSONNode.ARRAY_NODE && JSONSchemaType.Array.getName().equals(type)) { + valid = true; + break; + } + if (value.getNodeType() == IJSONNode.VALUE_BOOLEAN_NODE + && JSONSchemaType.Boolean.getName().equals(type)) { + valid = true; + break; + } + if (value.getNodeType() == IJSONNode.VALUE_NULL_NODE + && JSONSchemaType.Null.getName().equals(type)) { + valid = true; + break; + } + if (value.getNodeType() == IJSONNode.VALUE_NUMBER_NODE + && JSONSchemaType.Number.getName().equals(type)) { + valid = true; + break; + } + if (value.getNodeType() == IJSONNode.VALUE_NUMBER_NODE + && JSONSchemaType.Integer.getName().equals(type)) { + valid = true; + break; + } + } + } + if (!valid) { + int offset = node.getStartOffset(); + int line = node.getModel().getStructuredDocument().getLineOfOffset(offset); + StringBuffer buffer = new StringBuffer(); + Iterator<String> iter = types.iterator(); + buffer.append(OPEN_BRACKET); + while (iter.hasNext()) { + buffer.append(iter.next()); + if (iter.hasNext()) { + buffer.append(COMMA); + } + } + buffer.append(CLOSE_BRACKET); + valinfo.addMessage("Incorrect type. Expected " + buffer.toString(), line, 0, offset == 0 ? 1 : offset); + } + } + } + + private void validateEnum(IJSONNode node, JsonObject schema, JSONValidationInfo valinfo) { + JsonValue value = schema.get(IJSONSchemaNode.ENUM); + if (value instanceof JsonArray) { + JsonArray array = value.asArray(); + Iterator<JsonValue> iter = array.iterator(); + Set<String> values = new HashSet<String>(); + while (iter.hasNext()) { + String v = iter.next().toString(); + values.add(JSONUtil.removeQuote(v)); + } + if (node instanceof IJSONPair) { + IJSONPair pair = (IJSONPair) node; + String v = JSONUtil.getString(pair.getValue()); + if (!values.contains(v)) { + int offset = node.getStartOffset(); + int line = node.getModel().getStructuredDocument().getLineOfOffset(offset); + valinfo.addMessage("Value is not an accepted value. Valid values " + values + "'", line, 0, + offset == 0 ? 1 : offset); + } + } + } + } + + private void validateRequired(IJSONNode node, JsonObject schema, JSONValidationInfo valinfo) { + JsonValue required = schema.get(IJSONSchemaNode.REQUIRED); + if (required instanceof JsonArray) { + JsonArray array = required.asArray(); + Iterator<JsonValue> iter = array.iterator(); + Set<String> values = new HashSet<String>(); + while (iter.hasNext()) { + JsonValue v = iter.next(); + if (v.isString()) { + values.add(v.asString()); + } + } + Set<String> properties = getProperties(node); + for (String property : properties) { + if (property != null && values.contains(property)) { + values.remove(property); + } + } + for (String value : values) { + int offset = node.getStartOffset(); + int line = node.getModel().getStructuredDocument().getLineOfOffset(offset); + valinfo.addMessage("Missing property '" + value + "'", line, 0, offset == 0 ? 1 : offset); + } + } + } + + private void validateMinProperties(IJSONNode node, JsonObject schema, JSONValidationInfo valinfo) { + int value; + try { + value = schema.getInt(IJSONSchemaNode.MIN_PROPERTIES, -1); + } catch (Exception e) { + value = -1; + } + if (value >= 0) { + Set<String> properties = getProperties(node); + if (properties.size() < value) { + int offset = node.getStartOffset(); + int line = node.getModel().getStructuredDocument().getLineOfOffset(offset); + valinfo.addMessage("Object has fewer properties than the required number of" + value, line, 0, + offset == 0 ? 1 : offset); + } + } + } + + private void validateMaxProperties(IJSONNode node, JsonObject schema, JSONValidationInfo valinfo) { + int value; + try { + value = schema.getInt(IJSONSchemaNode.MAX_PROPERTIES, -1); + } catch (Exception e) { + value = -1; + } + if (value >= 0) { + Set<String> properties = getProperties(node); + if (properties.size() > value) { + int offset = node.getStartOffset(); + int line = node.getModel().getStructuredDocument().getLineOfOffset(offset); + valinfo.addMessage("Object has more properties than limit of" + value, line, 0, + offset == 0 ? 1 : offset); + } + } + } + + private Set<String> getProperties(IJSONNode node) { + Set<String> properties = new HashSet<String>(); + IJSONNode child = node.getFirstChild(); + while (child != null) { + if (child instanceof IJSONPair) { + IJSONPair pair = (IJSONPair) child; + if (pair.getName() != null) { + properties.add(pair.getName()); + } + } + child = child.getNextSibling(); + } + return properties; + } + + protected IStructuredModel getModel(String uriString) { + URI uri; + try { + uri = new URI(uriString); + } catch (URISyntaxException e) { + logWarning(e); + return null; + } + IFile[] files = ResourcesPlugin.getWorkspace().getRoot().findFilesForLocationURI(uri); + if (files == null || files.length <= 0 || !files[0].exists()) { + return null; + } + IFile file = files[0]; + IModelManager manager = StructuredModelManager.getModelManager(); + if (manager == null) + return null; + + IStructuredModel model = null; + try { + file.refreshLocal(IResource.DEPTH_ZERO, new NullProgressMonitor()); + } catch (CoreException e) { + logWarning(e); + } + try { + try { + model = manager.getModelForRead(file); + } catch (UnsupportedEncodingException ex) { + // retry ignoring META charset for invalid META charset + // specification + // recreate input stream, because it is already partially read + model = manager.getModelForRead(file, new String(), null); + } + } catch (UnsupportedEncodingException ex) { + } catch (IOException ex) { + } catch (CoreException e) { + logWarning(e); + } + return model; + } + + private static void logWarning(Exception e) { + IStatus status = new Status(IStatus.WARNING, JSONCorePlugin.PLUGIN_ID, e.getMessage(), e); + JSONCorePlugin.getDefault().getLog().log(status); + } + /** * Store additional information in the message parameters. For JSON * validation there are three additional pieces of information to store: @@ -131,21 +879,19 @@ * org.eclipse.wst.validation.internal.provisional.core.IMessage) */ @Override - protected void addInfoToMessage(ValidationMessage validationMessage, - IMessage message) { + protected void addInfoToMessage(ValidationMessage validationMessage, IMessage message) { String key = validationMessage.getKey(); if (key != null) { JSONMessageInfoHelper messageInfoHelper = new JSONMessageInfoHelper(); - String[] messageInfo = messageInfoHelper.createMessageInfo(key, - validationMessage.getMessageArguments()); + String[] messageInfo = messageInfoHelper.createMessageInfo(key, validationMessage.getMessageArguments()); - message.setAttribute(COLUMN_NUMBER_ATTRIBUTE, new Integer( - validationMessage.getColumnNumber())); - /*message.setAttribute(SQUIGGLE_SELECTION_STRATEGY_ATTRIBUTE, - messageInfo[0]); - message.setAttribute(SQUIGGLE_NAME_OR_VALUE_ATTRIBUTE, - messageInfo[1]); - */ + message.setAttribute(COLUMN_NUMBER_ATTRIBUTE, new Integer(validationMessage.getColumnNumber())); + /* + * message.setAttribute(SQUIGGLE_SELECTION_STRATEGY_ATTRIBUTE, + * messageInfo[0]); + * message.setAttribute(SQUIGGLE_NAME_OR_VALUE_ATTRIBUTE, + * messageInfo[1]); + */ } } @@ -159,8 +905,7 @@ * @return the nested validation context. */ @Override - protected NestedValidatorContext getNestedContext(ValidationState state, - boolean create) { + protected NestedValidatorContext getNestedContext(ValidationState state, boolean create) { NestedValidatorContext context = null; Object o = state.get(JSON_VALIDATOR_CONTEXT); if (o instanceof JSONNestedValidatorContext) @@ -172,8 +917,7 @@ } @Override - public void validationStarting(IProject project, ValidationState state, - IProgressMonitor monitor) { + public void validationStarting(IProject project, ValidationState state, IProgressMonitor monitor) { if (project != null) { NestedValidatorContext context = getNestedContext(state, false); if (context == null) { @@ -188,8 +932,7 @@ } @Override - public void validationFinishing(IProject project, ValidationState state, - IProgressMonitor monitor) { + public void validationFinishing(IProject project, ValidationState state, IProgressMonitor monitor) { if (project != null) { super.validationFinishing(project, state, monitor); NestedValidatorContext context = getNestedContext(state, false);
diff --git a/bundles/org.eclipse.wst.json.core/src/org/eclipse/wst/json/core/preferences/JSONCorePreferenceNames.java b/bundles/org.eclipse.wst.json.core/src/org/eclipse/wst/json/core/preferences/JSONCorePreferenceNames.java index 79546b5..f3a8db1 100644 --- a/bundles/org.eclipse.wst.json.core/src/org/eclipse/wst/json/core/preferences/JSONCorePreferenceNames.java +++ b/bundles/org.eclipse.wst.json.core/src/org/eclipse/wst/json/core/preferences/JSONCorePreferenceNames.java
@@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2005, 2011 IBM Corporation and others. + * Copyright (c) 2005, 2016 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -32,6 +32,7 @@ * </p> */ public static final String SYNTAX_VALIDATION = "syntaxValidation"; //$NON-NLS-1$ + public static final String SCHEMA_VALIDATION = "schemaValidation"; //$NON-NLS-1$ /** * Indicates whether or not a message should be produced when validating a
diff --git a/bundles/org.eclipse.wst.json.core/src/org/eclipse/wst/json/core/util/JSONUtil.java b/bundles/org.eclipse.wst.json.core/src/org/eclipse/wst/json/core/util/JSONUtil.java index 759a0ba..266a306 100644 --- a/bundles/org.eclipse.wst.json.core/src/org/eclipse/wst/json/core/util/JSONUtil.java +++ b/bundles/org.eclipse.wst.json.core/src/org/eclipse/wst/json/core/util/JSONUtil.java
@@ -1,5 +1,5 @@ /** - * Copyright (c) 2013-2014 Angelo ZERR. + * Copyright (c) 2013-2016 Angelo ZERR. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -14,6 +14,8 @@ import java.util.Enumeration; import java.util.Iterator; +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.wst.json.core.document.IJSONNode; import org.eclipse.wst.json.core.internal.Logger; import org.eclipse.wst.json.core.regions.JSONRegionContexts; import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegion; @@ -23,6 +25,8 @@ public class JSONUtil { + public static final String QUOTE = "\""; //$NON-NLS-1$ + public static void debugOut(String str) { Logger.log(Logger.WARNING, "json warning: " + str); //$NON-NLS-1$ } @@ -273,4 +277,29 @@ public static boolean isEndJSONStructure(String regionType) { return (regionType == JSONRegionContexts.JSON_ARRAY_CLOSE || regionType == JSONRegionContexts.JSON_OBJECT_CLOSE); } + + public static String getString(IJSONNode node) { + String value; + try { + value = node.getModel().getStructuredDocument().get(node.getStartOffset(), node.getEndOffset()-node.getStartOffset()); + } catch (BadLocationException e) { + // ignore + return null; + } + value = removeQuote(value); + return value; + } + + public static String removeQuote(String value) { + if (value != null) { + value = value.trim(); + if (value.startsWith(QUOTE)) { + value = value.substring(1); + } + if (value.endsWith(QUOTE)) { + value = value.substring(0, value.length() - 1); + } + } + return value; + } }
diff --git a/bundles/org.eclipse.wst.json.schemaprocessor/src/org/eclipse/wst/json/schemaprocessor/internal/JSONSchemaProcessor.java b/bundles/org.eclipse.wst.json.schemaprocessor/src/org/eclipse/wst/json/schemaprocessor/internal/JSONSchemaProcessor.java index 20adfb1..b55ea39 100644 --- a/bundles/org.eclipse.wst.json.schemaprocessor/src/org/eclipse/wst/json/schemaprocessor/internal/JSONSchemaProcessor.java +++ b/bundles/org.eclipse.wst.json.schemaprocessor/src/org/eclipse/wst/json/schemaprocessor/internal/JSONSchemaProcessor.java
@@ -47,15 +47,21 @@ is = url.openStream(); } else { File f = HttpClientProvider.getFile(url); - is = new FileInputStream(f); + if (f != null) { + is = new FileInputStream(f); + } } - schemaDocument = new JSONSchemaDocument(new InputStreamReader(is)); + if (is != null) { + schemaDocument = new JSONSchemaDocument(new InputStreamReader(is)); + } } finally { if (is != null) { is.close(); } } - schemaDocuments.put(uriString, schemaDocument); + if (schemaDocument != null) { + schemaDocuments.put(uriString, schemaDocument); + } return schemaDocument; }
diff --git a/bundles/org.eclipse.wst.json.ui/src/org/eclipse/wst/json/ui/internal/JSONUIMessages.java b/bundles/org.eclipse.wst.json.ui/src/org/eclipse/wst/json/ui/internal/JSONUIMessages.java index c8ab168..8208501 100644 --- a/bundles/org.eclipse.wst.json.ui/src/org/eclipse/wst/json/ui/internal/JSONUIMessages.java +++ b/bundles/org.eclipse.wst.json.ui/src/org/eclipse/wst/json/ui/internal/JSONUIMessages.java
@@ -23,6 +23,8 @@ private static final String BUNDLE_NAME = "org.eclipse.wst.json.ui.internal.JSONUIMessages";//$NON-NLS-1$ + public static String EnableSchemaValidation; + public static String Invalid_URL; public static String The_name_field_is_required; public static String The_entry_already_exists;
diff --git a/bundles/org.eclipse.wst.json.ui/src/org/eclipse/wst/json/ui/internal/JSONUIMessages.properties b/bundles/org.eclipse.wst.json.ui/src/org/eclipse/wst/json/ui/internal/JSONUIMessages.properties index f6ca05f..56d9dbb 100644 --- a/bundles/org.eclipse.wst.json.ui/src/org/eclipse/wst/json/ui/internal/JSONUIMessages.properties +++ b/bundles/org.eclipse.wst.json.ui/src/org/eclipse/wst/json/ui/internal/JSONUIMessages.properties
@@ -25,6 +25,7 @@ ## Validation preferences page SyntaxValidation_files=&Enable syntax validation +EnableSchemaValidation=Enable schema validation SyntaxValidation_files_label=Errors/Warnings Severity_error=Error Severity_warning=Warning
diff --git a/bundles/org.eclipse.wst.json.ui/src/org/eclipse/wst/json/ui/internal/preferences/JSONValidatorPreferencePage.java b/bundles/org.eclipse.wst.json.ui/src/org/eclipse/wst/json/ui/internal/preferences/JSONValidatorPreferencePage.java index 9b99574..18855f4 100644 --- a/bundles/org.eclipse.wst.json.ui/src/org/eclipse/wst/json/ui/internal/preferences/JSONValidatorPreferencePage.java +++ b/bundles/org.eclipse.wst.json.ui/src/org/eclipse/wst/json/ui/internal/preferences/JSONValidatorPreferencePage.java
@@ -19,6 +19,7 @@ import org.eclipse.core.runtime.preferences.DefaultScope; import org.eclipse.core.runtime.preferences.IEclipsePreferences; import org.eclipse.core.runtime.preferences.IScopeContext; +import org.eclipse.core.runtime.preferences.InstanceScope; import org.eclipse.jface.dialogs.ControlEnableState; import org.eclipse.jface.dialogs.IDialogSettings; import org.eclipse.swt.SWT; @@ -38,6 +39,7 @@ import org.eclipse.wst.json.ui.internal.JSONUIPlugin; import org.eclipse.wst.sse.core.internal.validate.ValidationMessage; import org.eclipse.wst.sse.ui.internal.preferences.ui.AbstractValidationSettingsPage; +import org.osgi.service.prefs.BackingStoreException; public class JSONValidatorPreferencePage extends AbstractValidationSettingsPage { private static final String SETTINGS_SECTION_NAME = "JSONValidationSeverities";//$NON-NLS-1$ @@ -75,6 +77,10 @@ private Group fSyntaxValidationGroup; private ControlEnableState fSyntaxState; + private boolean fOriginalUseSchemaValidation; + + private Button fSchemaValidation; + private static final int[] JSON_SEVERITIES = { ValidationMessage.WARNING, ValidationMessage.ERROR, ValidationMessage.IGNORE }; @@ -122,6 +128,13 @@ } }); + fOriginalUseSchemaValidation = getBooleanPreference( + JSONCorePreferenceNames.SCHEMA_VALIDATION, false, contexts); + fSchemaValidation = createCheckBox(parent, + JSONUIMessages.EnableSchemaValidation); + ((GridData) fSchemaValidation.getLayoutData()).horizontalSpan = 2; + fSchemaValidation + .setSelection(fOriginalUseSchemaValidation); fSyntaxValidationGroup = createGroup(parent, 3); ((GridLayout) fSyntaxValidationGroup.getLayout()).makeColumnsEqualWidth = false; fSyntaxValidationGroup @@ -219,6 +232,12 @@ boolean useExtendedSyntaxValidation = modelPreferences.getBoolean( JSONCorePreferenceNames.SYNTAX_VALIDATION, false); + boolean useSchemaValidation = modelPreferences.getBoolean( + JSONCorePreferenceNames.SCHEMA_VALIDATION, false); + if (fSchemaValidation != null) { + fSchemaValidation.setSelection(useSchemaValidation); + } + if (fExtendedSyntaxValidation != null) { if (fExtendedSyntaxValidation.getSelection() != useExtendedSyntaxValidation) { handleSyntaxSeveritySelection(useExtendedSyntaxValidation); @@ -251,6 +270,11 @@ JSONCorePreferenceNames.SYNTAX_VALIDATION, extendedSyntaxValidation); } + if (fSchemaValidation != null) { + contexts[0].getNode(getPreferenceNodeQualifier()).putBoolean( + JSONCorePreferenceNames.SCHEMA_VALIDATION, + fSchemaValidation.getSelection()); + } } @Override @@ -367,8 +391,9 @@ @Override protected boolean shouldRevalidateOnSettingsChange() { - return fOriginalUseExtendedSyntaxValidation != fExtendedSyntaxValidation - .getSelection() + return fOriginalUseSchemaValidation != fSchemaValidation.getSelection() + || fOriginalUseExtendedSyntaxValidation != fExtendedSyntaxValidation.getSelection() || super.shouldRevalidateOnSettingsChange(); } + }