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