Move BindingReflections.isNotificationCallback()
[mdsal.git] / binding / mdsal-binding-dom-adapter / src / main / java / org / opendaylight / mdsal / binding / dom / adapter / invoke / NotificationListenerInvoker.java
1 /*
2  * Copyright (c) 2013 Cisco Systems, Inc. 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.invoke;
9
10 import static com.google.common.base.Preconditions.checkArgument;
11 import static java.util.Objects.requireNonNull;
12
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 com.google.common.collect.ImmutableMap;
18 import com.google.common.collect.ImmutableMap.Builder;
19 import java.lang.invoke.MethodHandle;
20 import java.lang.invoke.MethodHandles;
21 import java.lang.invoke.MethodType;
22 import java.lang.reflect.Method;
23 import org.eclipse.jdt.annotation.NonNull;
24 import org.eclipse.jdt.annotation.Nullable;
25 import org.opendaylight.mdsal.binding.spec.reflect.BindingReflections;
26 import org.opendaylight.yangtools.yang.binding.DataContainer;
27 import org.opendaylight.yangtools.yang.binding.Notification;
28 import org.opendaylight.yangtools.yang.binding.NotificationListener;
29 import org.opendaylight.yangtools.yang.common.QName;
30
31 /**
32  * Provides single method invocation of notificatoin callbacks on supplied instance.
33  *
34  * <p>
35  * Notification Listener invoker provides common invocation interface for any subtype of {@link NotificationListener}.
36  * via {@link #invokeNotification(NotificationListener, QName, DataContainer)} method.
37  */
38 public final class NotificationListenerInvoker {
39     private static final LoadingCache<Class<? extends NotificationListener>, NotificationListenerInvoker> INVOKERS =
40             CacheBuilder.newBuilder().weakKeys()
41             .build(new CacheLoader<Class<? extends NotificationListener>, NotificationListenerInvoker>() {
42                 @Override
43                 public NotificationListenerInvoker load(final Class<? extends NotificationListener> key) {
44                     return new NotificationListenerInvoker(createInvokerMap(key));
45                 }
46
47                 private ImmutableMap<QName, MethodHandle> createInvokerMap(
48                         final Class<? extends NotificationListener> key) {
49                     final Builder<QName, MethodHandle> ret = ImmutableMap.builder();
50                     for (final Method method : key.getMethods()) {
51                         if (isNotificationCallback(method)) {
52                             final Class<?> notification = method.getParameterTypes()[0];
53                             final QName name = BindingReflections.findQName(notification);
54                             MethodHandle handle;
55                             try {
56                                 handle = MethodHandles.publicLookup().unreflect(method).asType(
57                                     MethodType.methodType(void.class, NotificationListener.class, DataContainer.class));
58                                 ret.put(name, handle);
59                             } catch (final IllegalAccessException e) {
60                                 throw new IllegalStateException("Can not access public method.", e);
61                             }
62                         }
63
64                     }
65                     return ret.build();
66                 }
67             });
68
69     private final ImmutableMap<QName, MethodHandle> methodInvokers;
70
71     NotificationListenerInvoker(final ImmutableMap<QName, MethodHandle> map) {
72         methodInvokers = map;
73     }
74
75     /**
76      * Creates RPCServiceInvoker for specified RpcService type.
77      *
78      * @param type
79      *            RpcService interface, which was generated from model.
80      * @return Cached instance of {@link NotificationListenerInvoker} for
81      *         supplied RPC type.
82      */
83     public static NotificationListenerInvoker from(final Class<? extends NotificationListener> type) {
84         checkArgument(type.isInterface());
85         checkArgument(BindingReflections.isBindingClass(type));
86         return INVOKERS.getUnchecked(type);
87     }
88
89     /**
90      * Invokes supplied RPC on provided implementation of RPC Service.
91      *
92      * @param impl Implementation on which notification callback should be invoked.
93      * @param rpcName Name of RPC to be invoked.
94      * @param input Input data for RPC.
95      */
96     @SuppressWarnings("checkstyle:illegalCatch")
97     public void invokeNotification(final @NonNull NotificationListener impl, final @NonNull QName rpcName,
98             final @Nullable DataContainer input) {
99         requireNonNull(impl, "implemetation must be supplied");
100         final MethodHandle invoker = methodInvokers.get(rpcName);
101         checkArgument(invoker != null, "Supplied notification is not valid for implementation %s", impl);
102         try {
103             invoker.invokeExact(impl, input);
104         } catch (final Throwable e) {
105             Throwables.throwIfUnchecked(e);
106             throw new IllegalStateException(e);
107         }
108     }
109
110     /**
111      * Checks if supplied method is callback for notifications.
112      *
113      * @param method method to check
114      * @return true if method is notification callback.
115      */
116     public static boolean isNotificationCallback(final Method method) {
117         if (method.getName().startsWith("on") && method.getParameterCount() == 1) {
118             Class<?> potentialNotification = method.getParameterTypes()[0];
119             if (Notification.class.isAssignableFrom(potentialNotification)
120                     && method.getName().equals("on" + potentialNotification.getSimpleName())) {
121                 return true;
122             }
123         }
124         return false;
125     }
126 }