code: /*
   * Copyright (c) 2021 the Eclipse Milo Authors
   *
   * This program and the accompanying materials are made
   * available under the terms of the Eclipse Public License 2.0
   * which is available at https://www.eclipse.org/legal/epl-2.0/
   *
   * SPDX-License-Identifier: EPL-2.0
   */
  
  package org.eclipse.milo.opcua.sdk.client;
  
  import java.util.concurrent.ConcurrentHashMap;
  import java.util.concurrent.ConcurrentMap;
  import java.util.concurrent.TimeUnit;
  import java.util.function.Consumer;
  
  import com.google.common.cache.Cache;
  import com.google.common.cache.CacheBuilder;
  import org.eclipse.milo.opcua.sdk.client.nodes.UaNode;
  import org.eclipse.milo.opcua.stack.core.types.builtin.NodeId;
  import org.jetbrains.annotations.Nullable;
  
  public class NodeCache {
  
      private final Cache<NodeId, UaNode> cachedNodes;
      private final ConcurrentMap<NodeId, UaNode> canonicalNodes;
  
      public NodeCache() {
          this(builder -> {
              builder.expireAfterWrite(2, TimeUnit.MINUTES);
              builder.maximumSize(16384);
              builder.recordStats();
          });
      }
  
      public NodeCache(Consumer<CacheBuilder<Object, Object>> consumer) {
          CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder();
  
          consumer.accept(builder);
  
          this.cachedNodes = builder.build();
          this.canonicalNodes = new ConcurrentHashMap<>();
      }
  
      /**
       * Canonicalize {@code node} in the NodeCache and return it.
       * <p>
       * If a canonical {@link UaNode} by the same {@link NodeId} already exists that instance is
       * returned.
       *
       * @param node the {@link UaNode} to canonicalize.
       * @return the canonical node instance.
       */
      public UaNode canonicalize(UaNode node) {
          NodeId nodeId = node.getNodeId();
  
          UaNode prev = canonicalNodes.putIfAbsent(nodeId, node);
  
          return prev != null ? prev : node;
      }
  
      @Nullable
      public UaNode getIfPresent(NodeId nodeId) {
          return canonicalNodes.getOrDefault(
              nodeId,
              cachedNodes.getIfPresent(nodeId)
          );
      }
  
      public void put(NodeId nodeId, UaNode node) {
          cachedNodes.put(nodeId, node);
      }
  
      public void invalidate(NodeId nodeId) {
          canonicalNodes.remove(nodeId);
          cachedNodes.invalidate(nodeId);
      }
  
  }
  