WEB-25281 JSON Schema: support 'pattern' for checking property value

This commit is contained in:
irengrig
2017-02-03 14:29:16 +01:00
parent b210302118
commit 5c270bbcb0
3 changed files with 72 additions and 34 deletions
@@ -373,10 +373,13 @@ class JsonBySchemaObjectAnnotator implements Annotator {
return;
}
}
// todo: regular expressions, format
/*if (schema.getPattern() != null) {
LOG.info("Unsupported property used: 'pattern'");
if (schema.getPattern() != null) {
if (!schema.checkByPattern(value)) {
error("String is violating the pattern: '" + schema.getPattern() + "'", propValue);
}
}
// I think we are not gonna to support format, there are a couple of RFCs there to check upon..
/*
if (schema.getFormat() != null) {
LOG.info("Unsupported property used: 'format'");
}*/
@@ -26,6 +26,7 @@ public class JsonSchemaObject {
private Map<String, JsonSchemaObject> myProperties;
private Map<String, JsonSchemaObject> myPatternProperties;
private final PatternCalculator myPatternCalculator = new PatternCalculator();
private final PatternCalculator myValuesPatternCalculator = new PatternCalculator();
private String myId;
private String mySchema;
@@ -97,6 +98,7 @@ public class JsonSchemaObject {
myDefinitions = other.myDefinitions;
myPatternProperties = other.myPatternProperties;
myPatternCalculator.clear();
myValuesPatternCalculator.clear();
myType = other.myType;
myDefault = other.myDefault;
@@ -142,6 +144,7 @@ public class JsonSchemaObject {
myDefinitions = copyMap(myDefinitions, other.myDefinitions);
myPatternProperties = copyMap(myPatternProperties, other.myPatternProperties);
myPatternCalculator.clear();
myValuesPatternCalculator.clear();
if (!StringUtil.isEmptyOrSpaces(other.myDescription)) {
myDescription = other.myDescription;
}
@@ -305,6 +308,7 @@ public class JsonSchemaObject {
public void setPattern(String pattern) {
myPattern = pattern;
myValuesPatternCalculator.clear();
}
public Boolean getAdditionalPropertiesAllowed() {
@@ -538,14 +542,14 @@ public class JsonSchemaObject {
@Nullable
public JsonSchemaObject getMatchingPatternPropertySchema(@NotNull String name) {
return myPatternCalculator.getMatchingPatternPropertySchema(myPatternProperties, name);
if (myPatternProperties == null) return null;
final String pattern = myPatternCalculator.selectMatchingPattern(myPatternProperties.keySet(), name);
return pattern == null ? null : myPatternProperties.get(pattern);
}
@NotNull
private static String adaptSchemaPattern(String pattern) {
pattern = pattern.startsWith("^") || pattern.startsWith("*") || pattern.startsWith(".") ? pattern : (".*" + pattern);
pattern = pattern.endsWith("+") || pattern.endsWith("*") ? pattern : (pattern + ".*");
return pattern;
public boolean checkByPattern(@NotNull String value) {
if (getPattern() == null) return true;
return getPattern().equals(myValuesPatternCalculator.selectMatchingPattern(Collections.singletonList(getPattern()), value));
}
public static void iterateAllInnerSchemas(@NotNull final JsonSchemaObject object, @NotNull final SchemaConsumer schemaConsumer) {
@@ -603,22 +607,17 @@ public class JsonSchemaObject {
private SLRUMap<String, String> myCachedPatternProperties;
@Nullable
public JsonSchemaObject getMatchingPatternPropertySchema(@Nullable final Map<String, JsonSchemaObject> patternProperties,
@NotNull final String name) {
if (patternProperties == null || patternProperties.isEmpty()) return null;
final Map<String, Pattern> patterns;
public String selectMatchingPattern(@Nullable final Collection<String> patterns, @NotNull final String name) {
if (patterns == null || patterns.isEmpty()) return null;
final Map<String, Pattern> cachedPatterns;
synchronized (myLock) {
if (myCachedPatterns == null) {
initPatternCache(patternProperties);
} else {
assert myCachedPatternProperties != null;
final String s = myCachedPatternProperties.get(name);
if (s != null) return patternProperties.get(s);
}
patterns = new HashMap<>(myCachedPatterns);
initPatternCache(patterns);
final String s = myCachedPatternProperties.get(name);
if (s != null) return s;
cachedPatterns = new HashMap<>(myCachedPatterns);
}
return matchPatternsToString(name, patternProperties, patterns);
return matchPatternsToString(name, patterns, cachedPatterns);
}
public void clear() {
@@ -628,14 +627,13 @@ public class JsonSchemaObject {
}
}
private JsonSchemaObject matchPatternsToString(@NotNull final String name,
@NotNull final Map<String, JsonSchemaObject> patternProperties,
@NotNull Map<String, Pattern> patterns) {
final List<String> strings = new ArrayList<>(patternProperties.keySet());
private String matchPatternsToString(@NotNull final String name, @NotNull final Collection<String> patterns,
@NotNull Map<String, Pattern> cachedPatterns) {
final List<String> strings = new ArrayList<>(patterns);
Collections.sort(strings);
for (final String pattern : strings) {
final Pattern compiledPattern = patterns.get(pattern);
final Pattern compiledPattern = cachedPatterns.get(pattern);
assert compiledPattern != null;
try {
final boolean matches = compiledPattern.matcher(StringUtil.newBombedCharSequence(name, 300)).matches();
@@ -643,24 +641,35 @@ public class JsonSchemaObject {
synchronized (myLock) {
if (myCachedPatterns.containsKey(pattern)) myCachedPatternProperties.put(name, pattern);
}
return patternProperties.get(pattern);
return pattern;
}
} catch (ProcessCanceledException e) {
//ignored
}
}
synchronized (myLock) {
if (myCachedPatterns.equals(patterns)) myCachedPatternProperties.put(name, "");
if (myCachedPatterns.equals(cachedPatterns)) myCachedPatternProperties.put(name, "");
}
return null;
}
private void initPatternCache(@NotNull final Map<String, JsonSchemaObject> patternProperties) {
myCachedPatterns = new HashMap<>(patternProperties.size(), 1.0f);
myCachedPatternProperties = new SLRUMap<>(100, 100);
for (String pattern : patternProperties.keySet()) {
myCachedPatterns.put(pattern, Pattern.compile(adaptSchemaPattern(pattern)));
private void initPatternCache(@NotNull final Collection<String> patterns) {
if (myCachedPatterns == null) {
myCachedPatterns = new HashMap<>(patterns.size());
myCachedPatternProperties = new SLRUMap<>(100, 100);
}
for (String pattern : patterns) {
if (!myCachedPatterns.containsKey(pattern))
myCachedPatterns.put(pattern, Pattern.compile(adaptSchemaPattern(pattern)));
}
}
@NotNull
private static String adaptSchemaPattern(String pattern) {
pattern = pattern.startsWith("^") || pattern.startsWith("*") || pattern.startsWith(".") ? pattern : (".*" + pattern);
pattern = pattern.endsWith("+") || pattern.endsWith("*") || pattern.endsWith("$") ? pattern : (pattern + ".*");
pattern = pattern.replace("\\\\", "\\");
return pattern;
}
}
}
@@ -33,11 +33,14 @@ import com.intellij.testFramework.PlatformTestUtil;
import com.jetbrains.jsonSchema.ide.JsonSchemaAnnotator;
import com.jetbrains.jsonSchema.ide.JsonSchemaService;
import org.jetbrains.annotations.NotNull;
import org.junit.Assert;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author Irina.Chernushina on 9/21/2015.
@@ -360,6 +363,29 @@ public class JsonSchemaHighlightingTest extends DaemonAnalyzerTestCase {
"}");
}
public void testPatternForPropertyValue() throws Exception {
final String schema = "{\n" +
" \"properties\": {\n" +
" \"withPattern\": {\n" +
" \"pattern\": \"p[0-9]\"\n" +
" }\n" +
" }\n" +
"}";
final String correctText = "{\n" +
" \"withPattern\": \"p1\"\n" +
"}";
final String wrongText = "{\n" +
" \"withPattern\": <warning descr=\"String is violating the pattern: 'p[0-9]'\">\"wrong\"</warning>\n" +
"}";
testImpl(schema, correctText);
testImpl(schema, wrongText);
}
public void testRegexp() throws Exception {
final Matcher matcher = Pattern.compile("^(\\([0-9]{3}\\))?[0-9]{3}-[0-9]{4}$").matcher("(112)555-1212");
Assert.assertTrue(matcher.matches());
}
public void testRootObjectRedefinedAdditionalPropertiesForbidden() throws Exception {
testImpl(rootObjectRedefinedSchema(), "{<warning descr=\"Property 'a' is not allowed\">\"a\": true</warning>," +
"\"r1\": \"allowed!\"}");