Skip to content

Commit

Permalink
GH-738 - Improve performance of fully qualified class name lookup.
Browse files Browse the repository at this point in the history
GH-678 and the changes related to GH-391 introduced a performance issue
with regards of looking up fully qualified class names.

This change addresse it by:

* Not using the stream api (again)
* Caching the result of the lookup

As the later should happens thread safe, we use a `ConcurrentHashMap`,
so we cannot store null values, but optional references.
  • Loading branch information
michael-simons committed Jan 13, 2020
1 parent 86e2e23 commit 2df1b3d
Showing 1 changed file with 18 additions and 11 deletions.
29 changes: 18 additions & 11 deletions core/src/main/java/org/neo4j/ogm/metadata/DomainInfo.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@
import io.github.classgraph.ScanResult;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import org.neo4j.ogm.annotation.NodeEntity;
import org.neo4j.ogm.annotation.RelationshipEntity;
Expand Down Expand Up @@ -61,6 +61,9 @@ public class DomainInfo {
private Map<String, List<ClassInfo>> nodeEntitiesByLabel;
private Map<String, List<ClassInfo>> relationshipEntitiesByType;
private final Map<String, List<ClassInfo>> interfaceNameToClassInfo = new HashMap<>();
// Yep, Optionals as field values are said to be evil, but in this case useful. DomainInfo isn't serializable anyway
// and we need a marker for a lookup that couldn't be found.
private final Map<String, Optional<String>> fqnLookup = new ConcurrentHashMap<>();
private final Set<Class> enumTypes = new HashSet<>();
private final ConversionCallbackRegistry conversionCallbackRegistry = new ConversionCallbackRegistry();

Expand Down Expand Up @@ -341,16 +344,20 @@ private ClassInfo getClassInfo(String fullOrPartialClassName, Map<String, ClassI
return infos.get(fullOrPartialClassName);
}

Pattern partialClassNamePattern = Pattern.compile(".+[\\\\.\\$]" + Pattern.quote(fullOrPartialClassName) + "$");
List<String> foundKeys = infos.keySet().stream().filter(partialClassNamePattern.asPredicate())
.collect(Collectors.toList());
if (foundKeys.isEmpty()) {
return null;
} else if (foundKeys.size() > 1) {
throw new MappingException("More than one class has simple name: " + fullOrPartialClassName);
} else {
return infos.get(foundKeys.get(0));
}
Optional<String> foundKey = fqnLookup.computeIfAbsent(fullOrPartialClassName, k -> {
Pattern partialClassNamePattern = Pattern.compile(".+[\\\\.\\$]" + Pattern.quote(k) + "$");
String matchingKey = null;
for (String key : infos.keySet()) {
if (partialClassNamePattern.matcher(key).matches()) {
if (matchingKey != null) {
throw new MappingException("More than one class has simple name: " + fullOrPartialClassName);
}
matchingKey = key;
}
}
return Optional.ofNullable(matchingKey);
});
return foundKey.map(infos::get).orElse(null);
}

Map<String, List<ClassInfo>> getNodeEntitiesByLabel() {
Expand Down

0 comments on commit 2df1b3d

Please sign in to comment.