diff --git a/json/src/com/jetbrains/jsonSchema/impl/JsonBySchemaObjectAnnotator.java b/json/src/com/jetbrains/jsonSchema/impl/JsonBySchemaObjectAnnotator.java index 2836ace42b7b..4ab4992178ce 100644 --- a/json/src/com/jetbrains/jsonSchema/impl/JsonBySchemaObjectAnnotator.java +++ b/json/src/com/jetbrains/jsonSchema/impl/JsonBySchemaObjectAnnotator.java @@ -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'"); }*/ diff --git a/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaObject.java b/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaObject.java index 36f1f6212982..dc912c2d5803 100644 --- a/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaObject.java +++ b/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaObject.java @@ -26,6 +26,7 @@ public class JsonSchemaObject { private Map myProperties; private Map 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 myCachedPatternProperties; @Nullable - public JsonSchemaObject getMatchingPatternPropertySchema(@Nullable final Map patternProperties, - @NotNull final String name) { - if (patternProperties == null || patternProperties.isEmpty()) return null; - final Map patterns; + public String selectMatchingPattern(@Nullable final Collection patterns, @NotNull final String name) { + if (patterns == null || patterns.isEmpty()) return null; + final Map 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 patternProperties, - @NotNull Map patterns) { - final List strings = new ArrayList<>(patternProperties.keySet()); + private String matchPatternsToString(@NotNull final String name, @NotNull final Collection patterns, + @NotNull Map cachedPatterns) { + final List 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 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 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; } } } diff --git a/json/tests/test/com/jetbrains/jsonSchema/JsonSchemaHighlightingTest.java b/json/tests/test/com/jetbrains/jsonSchema/JsonSchemaHighlightingTest.java index 4c5dc44a8ac3..a136c99217da 100644 --- a/json/tests/test/com/jetbrains/jsonSchema/JsonSchemaHighlightingTest.java +++ b/json/tests/test/com/jetbrains/jsonSchema/JsonSchemaHighlightingTest.java @@ -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\": \"wrong\"\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(), "{\"a\": true," + "\"r1\": \"allowed!\"}");