From 82284d476199f0998b4c6b47486da2bdcd5e316f Mon Sep 17 00:00:00 2001 From: irengrig Date: Mon, 14 Nov 2016 12:33:10 +0100 Subject: [PATCH] json schema: pattern properties: better caching - fix review comment for WEB-22649 JSON Schema: patternProperties does not work. --- .../jsonSchema/impl/JsonSchemaObject.java | 109 ++++++++++++++---- 1 file changed, 87 insertions(+), 22 deletions(-) diff --git a/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaObject.java b/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaObject.java index 5b6b128783af..51424d00baff 100644 --- a/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaObject.java +++ b/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaObject.java @@ -1,12 +1,14 @@ package com.jetbrains.jsonSchema.impl; import com.intellij.openapi.progress.ProcessCanceledException; +import com.intellij.openapi.util.Computable; import com.intellij.openapi.util.text.StringUtil; import com.intellij.util.containers.SLRUMap; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.*; +import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.regex.Pattern; /** @@ -17,7 +19,7 @@ public class JsonSchemaObject { private Map myDefinitions; private Map myProperties; private Map myPatternProperties; - private final SLRUMap myCachedPatternProperties = new SLRUMap<>(100, 100); + private final PatternCalculator myPatternCalculator = new PatternCalculator(); private String myId; private String mySchema; @@ -81,7 +83,7 @@ public class JsonSchemaObject { myProperties = other.myProperties; myDefinitions = other.myDefinitions; myPatternProperties = other.myPatternProperties; - myCachedPatternProperties.clear(); + myPatternCalculator.clear(); myType = other.myType; myDefault = other.myDefault; @@ -124,7 +126,7 @@ public class JsonSchemaObject { myProperties.putAll(other.myProperties); myDefinitions = copyMap(myDefinitions, other.myDefinitions); myPatternProperties = copyMap(myPatternProperties, other.myPatternProperties); - myCachedPatternProperties.clear(); + myPatternCalculator.clear(); if (!StringUtil.isEmptyOrSpaces(other.myDescription)) { myDescription = other.myDescription; } @@ -199,7 +201,7 @@ public class JsonSchemaObject { public void setPatternProperties(Map patternProperties) { myPatternProperties = patternProperties; - myCachedPatternProperties.clear(); + myPatternCalculator.clear(); } public JsonSchemaType getType() { @@ -504,24 +506,7 @@ public class JsonSchemaObject { @Nullable public JsonSchemaObject getMatchingPatternPropertySchema(@NotNull String name) { - if (myPatternProperties == null || myPatternProperties.isEmpty()) return null; - final String s = myCachedPatternProperties.get(name); - if (s != null) return myPatternProperties.get(s); - final List strings = new ArrayList<>(myPatternProperties.keySet()); - Collections.sort(strings); - for (final String pattern : strings) { - try { - final boolean matches = Pattern.compile(adaptSchemaPattern(pattern)).matcher(StringUtil.newBombedCharSequence(name, 1000)).matches(); - if (matches) { - myCachedPatternProperties.put(name, pattern); - return myPatternProperties.get(pattern); - } - } catch (ProcessCanceledException e) { - //ignored - } - } - myCachedPatternProperties.put(name, ""); - return null; + return myPatternCalculator.getMatchingPatternPropertySchema(myPatternProperties, name); } @NotNull @@ -530,4 +515,84 @@ public class JsonSchemaObject { pattern = pattern.endsWith("+") || pattern.endsWith("*") ? pattern : (pattern + ".*"); return pattern; } + + private static class PatternCalculator { + private final ReentrantReadWriteLock myLock = new ReentrantReadWriteLock(); + private Map myCachedPatterns; + private SLRUMap myCachedPatternProperties; + + @Nullable + public JsonSchemaObject getMatchingPatternPropertySchema(@Nullable final Map patternProperties, + @NotNull final String name) { + if (patternProperties == null || patternProperties.isEmpty()) return null; + myLock.readLock().lock(); + try { + if (myCachedPatterns == null) { + initPatternCache(patternProperties); + } + assert myCachedPatternProperties != null; + final String s = myCachedPatternProperties.get(name); + if (s != null) return patternProperties.get(s); + return matchPatternsToString(name, patternProperties); + } finally { + myLock.readLock().unlock(); + } + } + + public void clear() { + myLock.writeLock().lock(); + try { + myCachedPatterns = null; + myCachedPatternProperties = null; + } finally { + myLock.writeLock().unlock(); + } + } + + private JsonSchemaObject matchPatternsToString(@NotNull final String name, @NotNull final Map patternProperties) { + final List strings = new ArrayList<>(patternProperties.keySet()); + Collections.sort(strings); + + return underWrite(() -> { + for (final String pattern : strings) { + try { + final Pattern compiledPattern = myCachedPatterns.get(pattern); + assert compiledPattern != null; + final boolean matches = compiledPattern.matcher(StringUtil.newBombedCharSequence(name, 300)).matches(); + if (matches) { + myCachedPatternProperties.put(name, pattern); + return patternProperties.get(pattern); + } + } catch (ProcessCanceledException e) { + //ignored + } + } + myCachedPatternProperties.put(name, ""); + return null; + }); + } + + private T underWrite(@NotNull final Computable computable) { + myLock.readLock().unlock(); + myLock.writeLock().lock(); + try { + final T t = computable.compute(); + myLock.readLock().lock(); + return t; + } finally { + myLock.writeLock().unlock(); + } + } + + private void initPatternCache(@NotNull final Map patternProperties) { + underWrite(() -> { + myCachedPatterns = new HashMap<>(patternProperties.size(), 1.0f); + myCachedPatternProperties = new SLRUMap<>(100, 100); + for (String pattern : patternProperties.keySet()) { + myCachedPatterns.put(pattern, Pattern.compile(adaptSchemaPattern(pattern))); + } + return true; + }); + } + } }