Skip to content

Commit

Permalink
Moving memoized code to separate class
Browse files Browse the repository at this point in the history
  • Loading branch information
monitorjbl committed Nov 25, 2016
1 parent 2dc5681 commit ace471b
Show file tree
Hide file tree
Showing 10 changed files with 227 additions and 137 deletions.
16 changes: 14 additions & 2 deletions json-view/pom.xml
Expand Up @@ -41,8 +41,8 @@
<artifactId>maven-compiler-plugin</artifactId>
<version>3.2</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
Expand Down Expand Up @@ -167,5 +167,17 @@
<version>1.6.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.21</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.21</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
84 changes: 16 additions & 68 deletions json-view/src/main/java/com/monitorjbl/json/JsonViewSerializer.java
Expand Up @@ -32,13 +32,9 @@

public class JsonViewSerializer extends JsonSerializer<JsonView> {

private final int maxCacheSize;
private final Map<Class, Class<?>[]> interfaceCache = new HashMap<>();
private final Map<Class, Annotation[]> classAnnotationCache = new HashMap<>();
private final Map<Class, Field[]> classFieldsCache = new HashMap<>();
private final Map<Field, Annotation[]> fieldAnnotationCache = new HashMap<>();
private final Map<List<String>, Map<String, Map<Boolean, Integer>>> matchingCache = new HashMap<>();
private final Map<Field, Boolean> annotatedWithIgnoreCache = new HashMap<>();
private final Memoizer memoizer;
// private final Map<List<String>, Map<String, Map<Boolean, Integer>>> matchingCache = new HashMap<>();
// private final Map<Field, Boolean> annotatedWithIgnoreCache = new HashMap<>();

/**
* Map of custom serializers to take into account when serializing fields.
Expand All @@ -50,7 +46,7 @@ public JsonViewSerializer() {
}

public JsonViewSerializer(int maxCacheSize) {
this.maxCacheSize = maxCacheSize;
this.memoizer = new Memoizer(maxCacheSize);
}

/**
Expand Down Expand Up @@ -89,7 +85,7 @@ public <T> void registerCustomSerializer(Class<T> cls, JsonSerializer<T> forType
*
* @param cls The class type the serializer was registered for
*/
public <T> void unregisterCustomSerializer(Class<T> cls) {
public void unregisterCustomSerializer(Class<?> cls) {
if(customSerializersMap != null) {
customSerializersMap.remove(cls);
}
Expand Down Expand Up @@ -447,17 +443,9 @@ <E> E readClassAnnotation(Class cls, Class annotationType, String methodName) {
* </pre>
* <p>
* This method is memoized to speed up execution time
*
* @param values
* @param pattern
* @return
*/
int containsMatchingPattern(List<String> values, String pattern, boolean matchPrefix) {
Map<String, Map<Boolean, Integer>> l1 = matchingCache.get(values);
Map<Boolean, Integer> l2 = l1 == null ? null : l1.get(pattern);
if(l1 != null && l2 != null) {
return l2.get(matchPrefix);
} else {
int containsMatchingPattern(Set<String> values, String pattern, boolean matchPrefix) {
return memoizer.matches(values, pattern, matchPrefix, () -> {
int match = -1;
for(String val : values) {
String replaced = val.replaceAll("\\.", "\\\\.").replaceAll("\\*", ".*");
Expand All @@ -466,31 +454,16 @@ int containsMatchingPattern(List<String> values, String pattern, boolean matchPr
break;
}
}

//save result to avoid having to do an expensive regex check every time
synchronized (matchingCache) {
Map<String, Map<Boolean, Integer>> first = matchingCache.containsKey(values) ? matchingCache.get(values) : new HashMap<String, Map<Boolean, Integer>>();
Map<Boolean, Integer> second = first.containsKey(pattern) ? first.get(pattern) : new HashMap<Boolean, Integer>();

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;
Expand All @@ -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")
Expand Down Expand Up @@ -589,7 +537,7 @@ private <T extends Annotation> T getAnnotation(Field field, Class<T> annotation)
//synchronizes on the provided map to provide threadsafety
private <T, V> Map<T, V> fitToMaxSize(Map<T, V> map) {
synchronized (map) {
if(map.size() > maxCacheSize) {
if(map.size() > 1) {
map.remove(map.keySet().iterator().next());
}
}
Expand Down
29 changes: 25 additions & 4 deletions json-view/src/main/java/com/monitorjbl/json/Match.java
Expand Up @@ -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<String> includes = new ArrayList<>();
private final List<String> excludes = new ArrayList<>();
private final Set<String> includes = new HashSet<>();
private final Set<String> excludes = new HashSet<>();

Match() {

Expand All @@ -26,11 +28,11 @@ public Match exclude(String... fields) {
return this;
}

List<String> getIncludes() {
Set<String> getIncludes() {
return includes;
}

List<String> getExcludes() {
Set<String> getExcludes() {
return excludes;
}

Expand All @@ -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;
}
}
95 changes: 95 additions & 0 deletions 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<FunctionCache, Map<Arg, Object>> 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> T ignoreAnnotations(Field f, Supplier<T> compute) {
return (T) fitToMaxSize(IGNORE_ANNOTATIONS).computeIfAbsent(new MonoArg(f), (k) -> compute.get());
}

public <T> T matches(Set<String> values, String pattern, boolean matchPrefix, Supplier<T> compute) {
return (T) fitToMaxSize(MATCHES).computeIfAbsent(new TriArg(values, pattern, matchPrefix), (k) -> compute.get());
}

private Map<Arg, Object> fitToMaxSize(FunctionCache key) {
Map<Arg, Object> 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;
}
}
}

0 comments on commit ace471b

Please sign in to comment.