Repository: bval Updated Branches: refs/heads/master b3afb92fa -> ca0104bd4
BVAL-170 more aggressive caching of interpolated messages Project: http://git-wip-us.apache.org/repos/asf/bval/repo Commit: http://git-wip-us.apache.org/repos/asf/bval/commit/ca0104bd Tree: http://git-wip-us.apache.org/repos/asf/bval/tree/ca0104bd Diff: http://git-wip-us.apache.org/repos/asf/bval/diff/ca0104bd Branch: refs/heads/master Commit: ca0104bd4d3068b6b405e39e4247940afd0274e6 Parents: b3afb92 Author: Romain Manni-Bucau <[email protected]> Authored: Thu Feb 7 10:14:58 2019 +0100 Committer: Romain Manni-Bucau <[email protected]> Committed: Thu Feb 7 10:14:58 2019 +0100 ---------------------------------------------------------------------- .../bval/jsr/DefaultMessageInterpolator.java | 113 ++++++++++++++----- .../apache/bval/jsr/util/AnnotationProxy.java | 9 +- 2 files changed, 89 insertions(+), 33 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/bval/blob/ca0104bd/bval-jsr/src/main/java/org/apache/bval/jsr/DefaultMessageInterpolator.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/DefaultMessageInterpolator.java b/bval-jsr/src/main/java/org/apache/bval/jsr/DefaultMessageInterpolator.java index a3eb78a..ca3e8ab 100644 --- a/bval-jsr/src/main/java/org/apache/bval/jsr/DefaultMessageInterpolator.java +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/DefaultMessageInterpolator.java @@ -21,6 +21,7 @@ import static java.util.Optional.empty; import static java.util.Optional.of; import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.util.Arrays; import java.util.Enumeration; import java.util.Locale; @@ -84,6 +85,8 @@ public class DefaultMessageInterpolator implements MessageInterpolator { private final Map<Locale, ResourceBundle> defaultBundlesMap = new ConcurrentHashMap<>(); private final ConcurrentMap<MessageKey, String> cachedMessages = new ConcurrentHashMap<>(); + private final ConcurrentMap<Class<?>, Method> toStringMethods = new ConcurrentHashMap<>(); + private final ConcurrentMap<MessageWithParamsKey, String> interpolations = new ConcurrentHashMap<>(); private final MessageEvaluator evaluator; @@ -270,38 +273,62 @@ public class DefaultMessageInterpolator implements MessageInterpolator { } private String replaceAnnotationAttributes(final String message, final Map<String, Object> annotationParameters) { - final Matcher matcher = MESSAGE_PARAMETER.matcher(message); - final StringBuilder sb = new StringBuilder(64); - int prev = 0; - while (matcher.find()) { - int start = matcher.start(); - String resolvedParameterValue; - String parameter = matcher.group(1); - Object variable = annotationParameters.get(parameter); - if (variable == null) { - resolvedParameterValue = matcher.group(); - } else if (Object[].class.isInstance(variable)) { - resolvedParameterValue = Arrays.toString((Object[]) variable); - } else if (variable.getClass().isArray()) { - try { - resolvedParameterValue = (String) Reflection - .getDeclaredMethod(Arrays.class, "toString", variable.getClass()).invoke(null, variable); - } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { - throw new IllegalStateException("Could not expand array " + variable); - } + final MessageWithParamsKey key = new MessageWithParamsKey(message, annotationParameters); + String result = interpolations.get(key); + if (result == null) { + final Matcher matcher = MESSAGE_PARAMETER.matcher(message); + if (!matcher.find()) { + result = message; } else { - resolvedParameterValue = variable.toString(); - } - if (start > prev) { - sb.append(message, prev, start); + final StringBuilder sb = new StringBuilder(64); + int prev = 0; + do { + int start = matcher.start(); + String resolvedParameterValue; + String parameter = matcher.group(1); + Object variable = annotationParameters.get(parameter); + if (variable == null) { + resolvedParameterValue = matcher.group(); + } else if (Object[].class.isInstance(variable)) { + resolvedParameterValue = Arrays.toString((Object[]) variable); + } else if (variable.getClass().isArray()) { + try { + resolvedParameterValue = (String) getToStringMethod(variable).invoke(null, variable); + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + throw new IllegalStateException("Could not expand array " + variable); + } + } else { + resolvedParameterValue = variable.toString(); + } + if (start > prev) { + sb.append(message, prev, start); + } + sb.append(resolvedParameterValue); + prev = matcher.end(); + } while (matcher.find()); + if (prev < message.length()) { + sb.append(message, prev, message.length()); + } + result = sb.toString(); } - sb.append(resolvedParameterValue); - prev = matcher.end(); + interpolations.putIfAbsent(key, result); } - if (prev < message.length()) { - sb.append(message, prev, message.length()); + return result; + } + + public void clearCache() { + cachedMessages.clear(); + interpolations.clear(); + } + + private Method getToStringMethod(final Object variable) { + final Class<?> variableClass = variable.getClass(); + Method method = toStringMethods.get(variableClass); + if (method == null) { + method = Reflection.getDeclaredMethod(Arrays.class, "toString", variableClass);; + toStringMethods.putIfAbsent(variableClass, method); } - return sb.toString(); + return method; } private Optional<String> resolveParameter(String parameterName, ResourceBundle bundle, Locale locale, @@ -329,6 +356,7 @@ public class DefaultMessageInterpolator implements MessageInterpolator { */ public void setLocale(Locale locale) { defaultLocale = locale; + clearCache(); // cause there is a probability of 50% the old cache will be replaced by the new locale } private static class MessageKey { @@ -361,4 +389,33 @@ public class DefaultMessageInterpolator implements MessageInterpolator { return hash; } } + + private static class MessageWithParamsKey { + private final String message; + private final Map<String, Object> annotationParameters; + private final int hash; + + private MessageWithParamsKey(final String message, final Map<String, Object> annotationParameters) { + this.message = message; + this.annotationParameters = annotationParameters; + this.hash = Objects.hash(message, annotationParameters); + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final MessageWithParamsKey that = MessageWithParamsKey.class.cast(o); + return message.equals(that.message) && annotationParameters.equals(that.annotationParameters); + } + + @Override + public int hashCode() { + return hash; + } + } } http://git-wip-us.apache.org/repos/asf/bval/blob/ca0104bd/bval-jsr/src/main/java/org/apache/bval/jsr/util/AnnotationProxy.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/util/AnnotationProxy.java b/bval-jsr/src/main/java/org/apache/bval/jsr/util/AnnotationProxy.java index 1d21189..2443f42 100644 --- a/bval-jsr/src/main/java/org/apache/bval/jsr/util/AnnotationProxy.java +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/util/AnnotationProxy.java @@ -43,8 +43,6 @@ class AnnotationProxy implements Annotation, InvocationHandler, Serializable { /** Serialization version */ private static final long serialVersionUID = 1L; - - private Signature EQUALS = new Signature("equals", Object.class); private final Class<? extends Annotation> annotationType; private final SortedMap<String, Object> values; @@ -80,10 +78,11 @@ class AnnotationProxy implements Annotation, InvocationHandler, Serializable { */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - if (values.containsKey(method.getName())) { - return values.get(method.getName()); + final Object value = values.get(method.getName()); + if (value != null) { + return value; } - if (EQUALS.equals(Signature.of(method))) { + if (method.getName().equals("equals")) { // this is an annotation, no need to be more fancy (and slower) return equalTo(args[0]); } return method.invoke(this, args);
