2 * Copyright (c) 2020 PANTHEON.tech, s.r.o. and others. All rights reserved.
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
8 package org.opendaylight.mdsal.binding.dom.adapter.query;
10 import static com.google.common.base.Verify.verify;
11 import static java.util.Objects.requireNonNull;
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;
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.
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.
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:
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>
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.
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;
57 LambdaTarget(final String targetClass, final String targetMethod) {
58 this.targetClass = requireNonNull(targetClass);
59 this.targetMethod = requireNonNull(targetMethod);
63 public String toString() {
64 return MoreObjects.toStringHelper(this).add("class", targetClass).add("method", targetMethod).toString();
68 private static final LoadingCache<Class<?>, Method> REPLACE_CACHE = CacheBuilder.newBuilder()
69 .weakKeys().weakValues().build(new CacheLoader<Class<?>, Method>() {
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);
79 private static final LoadingCache<LeafReference<?, ?>, LambdaTarget> LAMBDA_CACHE = CacheBuilder.newBuilder()
80 .weakKeys().build(new CacheLoader<LeafReference<?, ?>, LambdaTarget>() {
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());
91 private LambdaDecoder() {
95 static LambdaTarget resolveLambda(final LeafReference<?, ?> lambda) {
96 return LAMBDA_CACHE.getUnchecked(lambda);