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.query.binding.adapter;
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.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;
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.
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.
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:
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>
47 final class LambdaDecoder {
48 static final class LambdaTarget implements Immutable {
49 final String targetClass;
50 final String targetMethod;
52 LambdaTarget(final String targetClass, final String targetMethod) {
53 this.targetClass = requireNonNull(targetClass);
54 this.targetMethod = requireNonNull(targetMethod);
58 public String toString() {
59 return MoreObjects.toStringHelper(this).add("class", targetClass).add("method", targetMethod).toString();
63 private static final LoadingCache<Class<?>, Method> REPLACE_CACHE = CacheBuilder.newBuilder()
64 .weakKeys().weakValues().build(new CacheLoader<Class<?>, Method>() {
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);
74 private static final LoadingCache<LeafReference<?, ?>, LambdaTarget> LAMBDA_CACHE = CacheBuilder.newBuilder()
75 .weakKeys().build(new CacheLoader<LeafReference<?, ?>, LambdaTarget>() {
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());
86 private LambdaDecoder() {
90 static LambdaTarget resolveLambda(final LeafReference<?, ?> lambda) {
91 return LAMBDA_CACHE.getUnchecked(lambda);