Add Binding/DOM Query language adapter
[mdsal.git] / binding / mdsal-binding-dom-adapter / src / main / java / org / opendaylight / mdsal / query / binding / adapter / LambdaDecoder.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.query.binding.adapter;
9
10 import static com.google.common.base.Verify.verify;
11 import static java.util.Objects.requireNonNull;
12
13 import com.google.common.base.MoreObjects;
14 import com.google.common.cache.CacheBuilder;
15 import com.google.common.cache.CacheLoader;
16 import com.google.common.cache.LoadingCache;
17 import java.lang.invoke.SerializedLambda;
18 import java.lang.reflect.Method;
19 import java.security.AccessController;
20 import java.security.PrivilegedActionException;
21 import java.security.PrivilegedExceptionAction;
22 import org.opendaylight.mdsal.binding.api.query.MatchBuilderPath.LeafReference;
23 import org.opendaylight.yangtools.concepts.Immutable;
24
25 /**
26  * Utility class for forcing decoding lambda instances to the method being invoked. The theory here is that
27  * {@code MatchBuilderPath.leaf()} methods are expected to be used with {@code method references}, which are converted
28  * to {@link LeafReference} lambdas.
29  *
30  * <p>
31  * We then assume runtime is following guidance around {@link SerializedLambda}, thus Serializable lambdas have a
32  * {@code writeReplace()} method and that it produces {@link SerializedLambda} -- which we use to get the information
33  * about what the lambda does at least in the single case we support.
34  *
35  * <p>
36  * An alternative approach to cracking the lambda would be to generate a dynamic proxy implementation of the base
37  * DataObject (we have the Class to do that), back it by a invocation handler which throws a private RuntimeException
38  * subclass containing the name of the invoked method. We then would invoke the lambda on such a proxy and intercept
39  * the exception raised. This unfortunately has multiple downsides:
40  * <ul>
41  *   <li>it requires a properly-managed ClassLoader (or pollutes original classloader with the proxy class)</li>
42  *   <li>it makes it appear we support something else than method references, which we do not</li>
43  *   <li>it creates additional implementation of the interface, bringing the system-wide total to 3, which can hurt
44  *       JIT's decisions</li>
45  * </ul>
46  */
47 final class LambdaDecoder {
48     static final class LambdaTarget implements Immutable {
49         final String targetClass;
50         final String targetMethod;
51
52         LambdaTarget(final String targetClass, final String targetMethod) {
53             this.targetClass = requireNonNull(targetClass);
54             this.targetMethod = requireNonNull(targetMethod);
55         }
56
57         @Override
58         public String toString() {
59             return MoreObjects.toStringHelper(this).add("class", targetClass).add("method", targetMethod).toString();
60         }
61     }
62
63     private static final LoadingCache<Class<?>, Method> REPLACE_CACHE = CacheBuilder.newBuilder()
64             .weakKeys().weakValues().build(new CacheLoader<Class<?>, Method>() {
65                 @Override
66                 public Method load(final Class<?> key) throws PrivilegedActionException {
67                     return AccessController.doPrivileged((PrivilegedExceptionAction<Method>) () -> {
68                         final Method method = key.getDeclaredMethod("writeReplace");
69                         method.setAccessible(true);
70                         return method;
71                     });
72                 }
73             });
74     private static final LoadingCache<LeafReference<?, ?>, LambdaTarget> LAMBDA_CACHE = CacheBuilder.newBuilder()
75             .weakKeys().build(new CacheLoader<LeafReference<?, ?>, LambdaTarget>() {
76                 @Override
77                 public LambdaTarget load(final LeafReference<?, ?> ref) throws Exception {
78                     final Object replaced = REPLACE_CACHE.get(ref.getClass()).invoke(ref);
79                     verify(replaced instanceof SerializedLambda, "Unexpected replaced object %s", replaced);
80                     final SerializedLambda serialized = (SerializedLambda) replaced;
81                     return new LambdaTarget(serialized.getImplClass().replace('/', '.'),
82                         serialized.getImplMethodName());
83                 }
84             });
85
86     private LambdaDecoder() {
87         // Hidden on purpose
88     }
89
90     static LambdaTarget resolveLambda(final LeafReference<?, ?> lambda) {
91         return LAMBDA_CACHE.getUnchecked(lambda);
92     }
93 }