/*
 * Copyright (c) 2002-2022 "Neo4j,"
 * Neo4j Sweden AB [http://neo4j.com]
 *
 * This file is part of Neo4j.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.neo4j.ogm.driver;

import static java.util.Collections.*;

import java.util.Map;
import java.util.Optional;
import java.util.function.Function;

/**
 * To be used by a driver to lookup type adapters, both native to mapped and mapped to native. This lookup wraps all
 * returned adapters to make resilient against null values.
 *
 * @author Michael J. Simons
 */
public final class TypeAdapterLookupDelegate {
    private final Map<Class<?>, Function> registeredTypeAdapter;

    public TypeAdapterLookupDelegate(Map<Class<?>, Function> registeredTypeAdapter) {

        this.registeredTypeAdapter = unmodifiableMap(registeredTypeAdapter);
    }

    /**
     * Retrieves an adapter for the specified class. Can be either native or mapped class.
     *
     * @param clazz The class for which an adapter is needed.
     * @return An adapter to convert an object of clazz to native or mapped, identity function if there's no adapter
     */
    public Function<Object, Object> getAdapterFor(Class<?> clazz) {

        Function<Object, Object> adapter = findAdapterFor(clazz).orElseGet(Function::identity);
        return object -> object == null ? null : adapter.apply(object);
    }

    public boolean hasAdapterFor(Class<?> clazz) {
        return findAdapterFor(clazz).isPresent();
    }

    private Optional<Function<Object, Object>> findAdapterFor(Class<?> clazz) {

        if (clazz == null) {
            return Optional.empty();
        } else if (this.registeredTypeAdapter.containsKey(clazz)) {
            return Optional.of(registeredTypeAdapter.get(clazz));
        } else {
            return registeredTypeAdapter.entrySet()
                .stream().filter(e -> e.getKey().isAssignableFrom(clazz))
                .findFirst()
                .map(Map.Entry::getValue);
        }
    }
}

@@ -776,24 +776,44 @@
 va.util.
-Optional
+concurrent.ConcurrentHashMap
 ;%0Aimport
@@ -1106,16 +1106,119 @@
 legate %7B
+%0A%0A    private final Map%3CClass%3C?%3E, Function%3CObject, Object%3E%3E cachedAdapters = new ConcurrentHashMap%3C%3E();
 %0A    pri
@@ -1821,165 +1821,35 @@
     
-Function%3CObject, Object%3E adapter = findAdapterFor(clazz).orElseGet(Function::identity);%0A        return object -%3E object == null ? null : adapter.apply(object
+return findAdapterFor(clazz
 );%0A 
@@ -1901,24 +1901,25 @@
 ?%3E clazz) %7B%0A
+%0A
         retu
@@ -1925,40 +1925,149 @@
 urn 
-findAdapterFor(clazz).isPresent(
+this.registeredTypeAdapter.containsKey(clazz) %7C%7C registeredTypeAdapter.keySet().stream()%0A            .anyMatch(c -%3E c.isAssignableFrom(clazz)
 );%0A 
@@ -2088,17 +2088,8 @@
 ate 
-Optional%3C
 Func
@@ -2108,17 +2108,16 @@
  Object%3E
-%3E
  findAda
@@ -2155,17 +2155,117 @@
     
+return cachedAdapters.computeIfAbsent(clazz, c -%3E %7B%0A            Function%3CObject, Object%3E f;%0A            
 if (c
-lazz
  == 
@@ -2288,27 +2288,31 @@
     
-return Optional.emp
+    f = Function.identi
 ty()
@@ -2317,24 +2317,28 @@
 ();%0A        
+   
+ 
 %7D else if (t
@@ -2376,20 +2376,16 @@
 insKey(c
-lazz
 )) %7B%0A   
@@ -2397,27 +2397,16 @@
     
-return Optional.of(
+    f = 
 regi
@@ -2428,24 +2428,23 @@
 er.get(c
-lazz));%0A
+);%0A    
         
@@ -2464,22 +2464,23 @@
         
-return
+    f =
  registe
@@ -2521,16 +2521,20 @@
         
+    
 .stream(
@@ -2534,16 +2534,37 @@
 stream()
+%0A                    
 .filter(
@@ -2601,15 +2601,15 @@
 om(c
-lazz))%0A
+))%0A    
     
@@ -2649,16 +2649,20 @@
         
+    
 .map(Map
@@ -2678,16 +2678,136 @@
 etValue)
+%0A                    .orElseGet(Function::identity);%0A            %7D%0A            return o -%3E o == null ? null : f.apply(o)
 ;%0A      
@@ -2805,21 +2805,23 @@
 );%0A        %7D
+);
 %0A    %7D%0A%7D%0A

Improve performance of `TypeAdapterLookupDelegate`.