Cache leaf lookups
[mdsal.git] / binding / mdsal-binding-dom-adapter / src / main / java / org / opendaylight / mdsal / binding / dom / adapter / query / DefaultQueryFactory.java
1 /*
2  * Copyright (c) 2020 PANTHEON.tech, s.r.o. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.mdsal.binding.dom.adapter.query;
9
10 import static java.util.Objects.requireNonNull;
11
12 import com.google.common.annotations.Beta;
13 import com.google.common.base.Throwables;
14 import com.google.common.cache.CacheBuilder;
15 import com.google.common.cache.CacheLoader;
16 import com.google.common.cache.LoadingCache;
17 import java.util.ServiceLoader;
18 import java.util.concurrent.ExecutionException;
19 import javax.inject.Inject;
20 import javax.inject.Singleton;
21 import org.eclipse.jdt.annotation.NonNull;
22 import org.kohsuke.MetaInfServices;
23 import org.opendaylight.mdsal.binding.api.query.DescendantQueryBuilder;
24 import org.opendaylight.mdsal.binding.api.query.QueryFactory;
25 import org.opendaylight.mdsal.binding.api.query.QueryStructureException;
26 import org.opendaylight.mdsal.binding.dom.codec.api.BindingCodecTree;
27 import org.opendaylight.mdsal.binding.dom.codec.spi.BindingSchemaMapping;
28 import org.opendaylight.yangtools.yang.binding.DataObject;
29 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
30 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
31 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
32 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
33 import org.slf4j.Logger;
34 import org.slf4j.LoggerFactory;
35
36 @Beta
37 @MetaInfServices
38 @Singleton
39 public final class DefaultQueryFactory implements QueryFactory {
40     private static final class MethodId {
41         final DataNodeContainer parent;
42         final String methodName;
43
44         MethodId(final DataNodeContainer parent, final String methodName) {
45             this.parent = requireNonNull(parent);
46             this.methodName = requireNonNull(methodName);
47         }
48
49         @Override
50         public int hashCode() {
51             return parent.hashCode() * 31 + methodName.hashCode();
52         }
53
54         @Override
55         public boolean equals(final Object obj) {
56             if (!(obj instanceof MethodId)) {
57                 return false;
58             }
59             final MethodId other = (MethodId) obj;
60             return methodName.equals(other.methodName) && parent.equals(other.parent);
61         }
62     }
63
64     private static final Logger LOG = LoggerFactory.getLogger(DefaultQueryFactory.class);
65
66     private final LoadingCache<MethodId, NodeIdentifier> knownMethods = CacheBuilder.newBuilder().build(
67         new CacheLoader<MethodId, NodeIdentifier>() {
68             @Override
69             public NodeIdentifier load(final MethodId key) {
70                 final DataNodeContainer parent = key.parent;
71                 final String methodName = key.methodName;
72
73                 for (DataSchemaNode child : parent.getChildNodes()) {
74                     if (methodName.equals(BindingSchemaMapping.getGetterMethodName(child))) {
75                         return NodeIdentifier.create(child.getQName());
76                     }
77                 }
78                 throw new QueryStructureException("Failed to find schema matching " + methodName + " in " + parent);
79             }
80         });
81     private final @NonNull BindingCodecTree codec;
82
83     public DefaultQueryFactory() {
84         this(ServiceLoader.load(BindingCodecTree.class).findFirst().orElseThrow());
85     }
86
87     @Inject
88     public DefaultQueryFactory(final BindingCodecTree codec) {
89         this.codec = requireNonNull(codec);
90     }
91
92     @Override
93     public <T extends DataObject> DescendantQueryBuilder<T> querySubtree(final InstanceIdentifier<T> rootPath) {
94         return new DefaultDescendantQueryBuilder<>(this, rootPath);
95     }
96
97     @NonNull BindingCodecTree codec() {
98         return codec;
99     }
100
101     @NonNull NodeIdentifier findChild(final DataNodeContainer parent, final String methodName) {
102         try {
103             return knownMethods.get(new MethodId(parent, methodName));
104         } catch (ExecutionException e) {
105             LOG.debug("Failed to find method for {}", methodName, e);
106             final Throwable cause = e.getCause();
107             Throwables.throwIfUnchecked(cause);
108             throw new IllegalStateException("Failed to load cache", e);
109         }
110     }
111 }