diff --git a/json-view/pom.xml b/json-view/pom.xml
index 734c8cb..d44f21d 100644
--- a/json-view/pom.xml
+++ b/json-view/pom.xml
@@ -41,8 +41,8 @@
maven-compiler-plugin3.2
-
- 1.7
+
+ 1.8
@@ -167,5 +167,17 @@
1.6.2test
+
+ org.slf4j
+ slf4j-api
+ 1.7.21
+ test
+
+
+ org.slf4j
+ slf4j-log4j12
+ 1.7.21
+ test
+
diff --git a/json-view/src/main/java/com/monitorjbl/json/JsonViewSerializer.java b/json-view/src/main/java/com/monitorjbl/json/JsonViewSerializer.java
index d721318..e6bd2a3 100644
--- a/json-view/src/main/java/com/monitorjbl/json/JsonViewSerializer.java
+++ b/json-view/src/main/java/com/monitorjbl/json/JsonViewSerializer.java
@@ -32,13 +32,9 @@
public class JsonViewSerializer extends JsonSerializer {
- private final int maxCacheSize;
- private final Map[]> interfaceCache = new HashMap<>();
- private final Map classAnnotationCache = new HashMap<>();
- private final Map classFieldsCache = new HashMap<>();
- private final Map fieldAnnotationCache = new HashMap<>();
- private final Map, Map>> matchingCache = new HashMap<>();
- private final Map annotatedWithIgnoreCache = new HashMap<>();
+ private final Memoizer memoizer;
+ // private final Map, Map>> matchingCache = new HashMap<>();
+// private final Map annotatedWithIgnoreCache = new HashMap<>();
/**
* Map of custom serializers to take into account when serializing fields.
@@ -50,7 +46,7 @@ public JsonViewSerializer() {
}
public JsonViewSerializer(int maxCacheSize) {
- this.maxCacheSize = maxCacheSize;
+ this.memoizer = new Memoizer(maxCacheSize);
}
/**
@@ -89,7 +85,7 @@ public void registerCustomSerializer(Class cls, JsonSerializer forType
*
* @param cls The class type the serializer was registered for
*/
- public void unregisterCustomSerializer(Class cls) {
+ public void unregisterCustomSerializer(Class> cls) {
if(customSerializersMap != null) {
customSerializersMap.remove(cls);
}
@@ -447,17 +443,9 @@ E readClassAnnotation(Class cls, Class annotationType, String methodName) {
*
*
* This method is memoized to speed up execution time
- *
- * @param values
- * @param pattern
- * @return
*/
- int containsMatchingPattern(List values, String pattern, boolean matchPrefix) {
- Map> l1 = matchingCache.get(values);
- Map l2 = l1 == null ? null : l1.get(pattern);
- if(l1 != null && l2 != null) {
- return l2.get(matchPrefix);
- } else {
+ int containsMatchingPattern(Set values, String pattern, boolean matchPrefix) {
+ return memoizer.matches(values, pattern, matchPrefix, () -> {
int match = -1;
for(String val : values) {
String replaced = val.replaceAll("\\.", "\\\\.").replaceAll("\\*", ".*");
@@ -466,31 +454,16 @@ int containsMatchingPattern(List values, String pattern, boolean matchPr
break;
}
}
-
- //save result to avoid having to do an expensive regex check every time
- synchronized (matchingCache) {
- Map> first = matchingCache.containsKey(values) ? matchingCache.get(values) : new HashMap>();
- Map second = first.containsKey(pattern) ? first.get(pattern) : new HashMap();
-
- second.put(matchPrefix, match);
- first.put(pattern, second);
- matchingCache.put(values, first);
- }
-
return match;
- }
+ });
}
/**
* Returns a boolean indicating whether the provided field is annotated with
* some form of ignore. This method is memoized to speed up execution time
- *
- * @param f
- * @return
*/
boolean annotatedWithIgnore(Field f) {
- boolean annotated;
- if(!annotatedWithIgnoreCache.containsKey(f)) {
+ return memoizer.ignoreAnnotations(f, () -> {
JsonIgnore jsonIgnore = getAnnotation(f, JsonIgnore.class);
JsonIgnoreProperties classIgnoreProperties = getAnnotation(f.getDeclaringClass(), JsonIgnoreProperties.class);
JsonIgnoreProperties fieldIgnoreProperties = null;
@@ -512,52 +485,27 @@ boolean annotatedWithIgnore(Field f) {
}
}
- annotated = (jsonIgnore != null && jsonIgnore.value()) ||
+ return (jsonIgnore != null && jsonIgnore.value()) ||
(classIgnoreProperties != null && Arrays.asList(classIgnoreProperties.value()).contains(f.getName())) ||
(fieldIgnoreProperties != null && Arrays.asList(fieldIgnoreProperties.value()).contains(f.getName())) ||
backReferenced;
- fitToMaxSize(annotatedWithIgnoreCache).put(f, annotated);
- } else {
- annotated = annotatedWithIgnoreCache.get(f);
- }
-
- return annotated;
+ });
}
private Class>[] getInterfaces(Class cls) {
- Class>[] interfaces = interfaceCache.get(cls);
- if(interfaces == null) {
- interfaces = cls.getInterfaces();
- fitToMaxSize(interfaceCache).put(cls, interfaces);
- }
- return interfaces;
+ return cls.getInterfaces();
}
private Field[] getDeclaredFields(Class cls) {
- Field[] fields = classFieldsCache.get(cls);
- if(fields == null) {
- fields = cls.getDeclaredFields();
- fitToMaxSize(classFieldsCache).put(cls, fields);
- }
- return fields;
+ return cls.getDeclaredFields();
}
private Annotation[] getAnnotations(Class cls) {
- Annotation[] annotations = classAnnotationCache.get(cls);
- if(annotations == null) {
- annotations = cls.getAnnotations();
- fitToMaxSize(classAnnotationCache).put(cls, annotations);
- }
- return annotations;
+ return cls.getAnnotations();
}
private Annotation[] getAnnotations(Field field) {
- Annotation[] annotations = fieldAnnotationCache.get(field);
- if(annotations == null) {
- annotations = field.getAnnotations();
- fitToMaxSize(fieldAnnotationCache).put(field, annotations);
- }
- return annotations;
+ return field.getAnnotations();
}
@SuppressWarnings("unchecked")
@@ -589,7 +537,7 @@ private T getAnnotation(Field field, Class annotation)
//synchronizes on the provided map to provide threadsafety
private Map fitToMaxSize(Map map) {
synchronized (map) {
- if(map.size() > maxCacheSize) {
+ if(map.size() > 1) {
map.remove(map.keySet().iterator().next());
}
}
diff --git a/json-view/src/main/java/com/monitorjbl/json/Match.java b/json-view/src/main/java/com/monitorjbl/json/Match.java
index ab4def4..0c1b41f 100644
--- a/json-view/src/main/java/com/monitorjbl/json/Match.java
+++ b/json-view/src/main/java/com/monitorjbl/json/Match.java
@@ -2,11 +2,13 @@
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
public class Match {
- private final List includes = new ArrayList<>();
- private final List excludes = new ArrayList<>();
+ private final Set includes = new HashSet<>();
+ private final Set excludes = new HashSet<>();
Match() {
@@ -26,11 +28,11 @@ public Match exclude(String... fields) {
return this;
}
- List getIncludes() {
+ Set getIncludes() {
return includes;
}
- List getExcludes() {
+ Set getExcludes() {
return excludes;
}
@@ -45,4 +47,23 @@ public String toString() {
", excludes=" + excludes +
'}';
}
+
+ @Override
+ public boolean equals(Object o) {
+ if(this == o) return true;
+ if(o == null || getClass() != o.getClass()) return false;
+
+ Match match = (Match) o;
+
+ if(includes != null ? !includes.equals(match.includes) : match.includes != null) return false;
+ return excludes != null ? excludes.equals(match.excludes) : match.excludes == null;
+
+ }
+
+ @Override
+ public int hashCode() {
+ int result = includes != null ? includes.hashCode() : 0;
+ result = 31 * result + (excludes != null ? excludes.hashCode() : 0);
+ return result;
+ }
}
diff --git a/json-view/src/main/java/com/monitorjbl/json/Memoizer.java b/json-view/src/main/java/com/monitorjbl/json/Memoizer.java
new file mode 100644
index 0000000..04cd7a0
--- /dev/null
+++ b/json-view/src/main/java/com/monitorjbl/json/Memoizer.java
@@ -0,0 +1,95 @@
+package com.monitorjbl.json;
+
+import java.lang.reflect.Field;
+import java.util.EnumMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Supplier;
+
+import static com.monitorjbl.json.Memoizer.FunctionCache.IGNORE_ANNOTATIONS;
+import static com.monitorjbl.json.Memoizer.FunctionCache.MATCHES;
+
+@SuppressWarnings("unchecked")
+class Memoizer {
+ private final int maxCacheSize;
+ private final Map> cache = new EnumMap<>(FunctionCache.class);
+
+ public Memoizer(int maxCacheSize) {
+ this.maxCacheSize = maxCacheSize;
+ for(FunctionCache key : FunctionCache.class.getEnumConstants()) {
+ cache.put(key, new ConcurrentHashMap<>());
+ }
+ }
+
+ public T ignoreAnnotations(Field f, Supplier compute) {
+ return (T) fitToMaxSize(IGNORE_ANNOTATIONS).computeIfAbsent(new MonoArg(f), (k) -> compute.get());
+ }
+
+ public T matches(Set values, String pattern, boolean matchPrefix, Supplier compute) {
+ return (T) fitToMaxSize(MATCHES).computeIfAbsent(new TriArg(values, pattern, matchPrefix), (k) -> compute.get());
+ }
+
+ private Map fitToMaxSize(FunctionCache key) {
+ Map map = cache.get(key);
+ if(map.size() > maxCacheSize) {
+ map.remove(map.keySet().iterator().next());
+ }
+ return map;
+ }
+
+ enum FunctionCache {
+ IGNORE_ANNOTATIONS, MATCHES
+ }
+
+ private interface Arg {}
+
+ private class MonoArg implements Arg {
+ private final Object arg1;
+
+ public MonoArg(Object arg1) {
+ this.arg1 = arg1;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ MonoArg monoArg = (MonoArg) o;
+
+ return arg1 != null ? arg1.equals(monoArg.arg1) : monoArg.arg1 == null;
+ }
+
+ @Override
+ public int hashCode() {
+ return arg1 != null ? arg1.hashCode() : 0;
+ }
+ }
+
+ private class TriArg implements Arg {
+ private final Object arg1;
+ private final Object arg2;
+ private final Object arg3;
+
+ public TriArg(Object arg1, Object arg2, Object arg3) {
+ this.arg1 = arg1;
+ this.arg2 = arg2;
+ this.arg3 = arg3;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ TriArg triArg = (TriArg) o;
+
+ if(arg1 != null ? !arg1.equals(triArg.arg1) : triArg.arg1 != null) return false;
+ if(arg2 != null ? !arg2.equals(triArg.arg2) : triArg.arg2 != null) return false;
+ return arg3 != null ? arg3.equals(triArg.arg3) : triArg.arg3 == null;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = arg1 != null ? arg1.hashCode() : 0;
+ result = 31 * result + (arg2 != null ? arg2.hashCode() : 0);
+ result = 31 * result + (arg3 != null ? arg3.hashCode() : 0);
+ return result;
+ }
+ }
+}
diff --git a/json-view/src/test/java/com/monitorjbl/json/JsonViewSerializerPerformanceTest.java b/json-view/src/test/java/com/monitorjbl/json/JsonViewSerializerPerformanceTest.java
index c78f7b6..a318baa 100644
--- a/json-view/src/test/java/com/monitorjbl/json/JsonViewSerializerPerformanceTest.java
+++ b/json-view/src/test/java/com/monitorjbl/json/JsonViewSerializerPerformanceTest.java
@@ -11,7 +11,11 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import java.math.RoundingMode;
+import java.text.DecimalFormat;
import java.util.Arrays;
import java.util.Collection;
@@ -20,6 +24,7 @@
@RunWith(Parameterized.class)
public class JsonViewSerializerPerformanceTest {
+ private static final Logger log = LoggerFactory.getLogger(JsonViewSerializerPerformanceTest.class);
private int repetitions;
private JsonViewSerializer serializer = new JsonViewSerializer();
private ObjectMapper sut;
@@ -28,7 +33,7 @@ public class JsonViewSerializerPerformanceTest {
@Parameterized.Parameters
public static Collection
diff --git a/spring-json-view/src/main/java/com/monitorjbl/json/JsonViewMessageConverter.java b/spring-json-view/src/main/java/com/monitorjbl/json/JsonViewMessageConverter.java
index b17b696..af5650c 100644
--- a/spring-json-view/src/main/java/com/monitorjbl/json/JsonViewMessageConverter.java
+++ b/spring-json-view/src/main/java/com/monitorjbl/json/JsonViewMessageConverter.java
@@ -1,19 +1,18 @@
package com.monitorjbl.json;
-import java.io.IOException;
-
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.module.SimpleModule;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
-import com.fasterxml.jackson.databind.JsonSerializer;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.module.SimpleModule;
+import java.io.IOException;
public class JsonViewMessageConverter extends MappingJackson2HttpMessageConverter {
- private JsonViewSerializer serializer=new JsonViewSerializer();
-
+ private JsonViewSerializer serializer = new JsonViewSerializer();
+
public JsonViewMessageConverter() {
super();
ObjectMapper defaultMapper = new ObjectMapper();
@@ -37,27 +36,28 @@ public JsonViewMessageConverter(ObjectMapper mapper) {
* Thus, when JSonView find a field of that type (DateTime), it will delegate the serialization to the serializer specified.
* Example:
*
- * JsonViewSupportFactoryBean bean = new JsonViewSupportFactoryBean( mapper );
- * bean.registerCustomSerializer( DateTime.class, new DateTimeSerializer() );
+ * JsonViewSupportFactoryBean bean = new JsonViewSupportFactoryBean( mapper );
+ * bean.registerCustomSerializer( DateTime.class, new DateTimeSerializer() );
*
- * @param Type class of the serializer
- * @param class1 {@link Class} the class type you want to add a custom serializer
+ *
+ * @param Type class of the serializer
+ * @param class1 {@link Class} the class type you want to add a custom serializer
* @param forType {@link JsonSerializer} the serializer you want to apply for that type
*/
- public void registerCustomSerializer( Class class1, JsonSerializer forType )
- {
- this.serializer.registerCustomSerializer(class1, forType );
+ public void registerCustomSerializer(Class class1, JsonSerializer forType) {
+ this.serializer.registerCustomSerializer(class1, forType);
}
-
+
/**
* Unregister a previously registtered serializer. @see registerCustomSerializer
- * @param class1
+ *
+ * @param Type class of the serializer
+ * @param class1 {@link Class} the class type for which you want to remove a custom serializer
*/
- public void unregisterCustomSerializer( Class class1 )
- {
- this.serializer.unregisterCustomSerializer(class1);
+ public void unregisterCustomSerializer(Class class1) {
+ this.serializer.unregisterCustomSerializer(class1);
}
-
+
@Override
protected void writeInternal(Object object, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
super.writeInternal(object, outputMessage);
diff --git a/spring-json-view/src/main/java/com/monitorjbl/json/JsonViewSupportFactoryBean.java b/spring-json-view/src/main/java/com/monitorjbl/json/JsonViewSupportFactoryBean.java
index b185ea8..7e08452 100644
--- a/spring-json-view/src/main/java/com/monitorjbl/json/JsonViewSupportFactoryBean.java
+++ b/spring-json-view/src/main/java/com/monitorjbl/json/JsonViewSupportFactoryBean.java
@@ -98,22 +98,22 @@ private void decorateHandlers(List handlers) {
* bean.registerCustomSerializer( DateTime.class, new DateTimeSerializer() );
*
* @param Type class of the serializer
- * @param class1 {@link Class} the class type you want to add a custom serializer
+ * @param cls {@link Class} the class type you want to add a custom serializer
* @param forType {@link JsonSerializer} the serializer you want to apply for that type
*/
- public void registerCustomSerializer( Class class1, JsonSerializer forType )
+ public void registerCustomSerializer( Class cls, JsonSerializer forType )
{
- this.converter.registerCustomSerializer( class1, forType );
+ this.converter.registerCustomSerializer( cls, forType );
}
/**
* Unregister a previously registtered serializer. @see registerCustomSerializer
- * @param class1
+ * @param cls The class type the serializer was registered for
*/
- public void unregisterCustomSerializer( Class class1 )
+ public void unregisterCustomSerializer( Class> cls )
{
- this.converter.unregisterCustomSerializer(class1);
+ this.converter.unregisterCustomSerializer(cls);
}
}
\ No newline at end of file