Merge "Bug 735 - Part 1: Update ietf-restconf and ietf-yangtypes to newer versions"
[yangtools.git] / yang / yang-binding / src / main / java / org / opendaylight / yangtools / yang / binding / util / BindingReflections.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.yangtools.yang.binding.util;
9
10 import static com.google.common.base.Preconditions.checkArgument;
11 import static com.google.common.base.Preconditions.checkState;
12
13 import java.lang.reflect.Field;
14 import java.lang.reflect.InvocationTargetException;
15 import java.lang.reflect.Method;
16 import java.lang.reflect.Type;
17 import java.util.LinkedList;
18 import java.util.List;
19 import java.util.ServiceLoader;
20 import java.util.concurrent.Callable;
21 import java.util.concurrent.Future;
22 import java.util.concurrent.TimeUnit;
23 import java.util.regex.Matcher;
24 import java.util.regex.Pattern;
25
26 import org.opendaylight.yangtools.yang.binding.Augmentable;
27 import org.opendaylight.yangtools.yang.binding.Augmentation;
28 import org.opendaylight.yangtools.yang.binding.BaseIdentity;
29 import org.opendaylight.yangtools.yang.binding.BindingMapping;
30 import org.opendaylight.yangtools.yang.binding.ChildOf;
31 import org.opendaylight.yangtools.yang.binding.DataContainer;
32 import org.opendaylight.yangtools.yang.binding.DataObject;
33 import org.opendaylight.yangtools.yang.binding.Notification;
34 import org.opendaylight.yangtools.yang.binding.RpcService;
35 import org.opendaylight.yangtools.yang.binding.YangModelBindingProvider;
36 import org.opendaylight.yangtools.yang.binding.YangModuleInfo;
37 import org.opendaylight.yangtools.yang.common.QName;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
40
41 import com.google.common.base.Optional;
42 import com.google.common.cache.CacheBuilder;
43 import com.google.common.cache.CacheLoader;
44 import com.google.common.cache.LoadingCache;
45 import com.google.common.collect.ImmutableSet;
46 import com.google.common.collect.ImmutableSet.Builder;
47
48 public class BindingReflections {
49
50     private static final long EXPIRATION_TIME = 60;
51     private static final String ROOT_PACKAGE_PATTERN_STRING = "(org.opendaylight.yang.gen.v1.[a-z0-9_\\.]*\\.rev[0-9][0-9][0-1][0-9][0-3][0-9])";
52     private static final Pattern ROOT_PACKAGE_PATTERN = Pattern.compile(ROOT_PACKAGE_PATTERN_STRING);
53     private static final Logger LOG = LoggerFactory.getLogger(BindingReflections.class);
54
55     private static final LoadingCache<Class<?>, Optional<QName>> classToQName = CacheBuilder.newBuilder() //
56             .weakKeys() //
57             .expireAfterAccess(EXPIRATION_TIME, TimeUnit.SECONDS) //
58             .build(new ClassToQNameLoader());
59
60
61     private BindingReflections() {
62         throw new UnsupportedOperationException("Utility class.");
63     }
64
65     /**
66      *
67      * Find augmentation target class from concrete Augmentation class
68      *
69      * This method uses first generic argument of
70      * implemented {@link Augmentation} interface.
71      *
72      * @param augmentation
73      *            {@link Augmentation} subclass for which we want to determine
74      *            augmentation target.
75      * @return Augmentation target - class which augmentation provides
76      *         additional extensions.
77      */
78     public static Class<? extends Augmentable<?>> findAugmentationTarget(
79             final Class<? extends Augmentation<?>> augmentation) {
80         return ClassLoaderUtils.findFirstGenericArgument(augmentation, Augmentation.class);
81     }
82
83     /**
84      * Find data hierarchy parent from concrete Data class
85      *
86      * This method uses first generic argument of
87      * implemented {@link ChildOf} interface.
88      *
89      * @param augmentation
90      *            {@link Augmentation} subclass for which we want to determine
91      *            augmentation target.
92      * @return Augmentation target - class which augmentation provides
93      *         additional extensions.
94      */
95     public static Class<?> findHierarchicalParent(final Class<? extends ChildOf<?>> childClass) {
96         return ClassLoaderUtils.findFirstGenericArgument(childClass, ChildOf.class);
97     }
98
99     /**
100      * Find data hierarchy parent from concrete Data class
101      *
102      * This method is shorthand which gets DataObject class by invoking
103      * {@link DataObject#getImplementedInterface()} and uses {@link #findHierarchicalParent(Class)}.
104      *
105      * @param childClass
106      *            {@link Augmentation} subclass for which we want to determine
107      *            augmentation target.
108      * @return Augmentation target - class which augmentation provides
109      *         additional extensions.
110      */
111     public static Class<?> findHierarchicalParent(final DataObject child) {
112         if (child instanceof ChildOf) {
113             return ClassLoaderUtils.findFirstGenericArgument(child.getImplementedInterface(), ChildOf.class);
114         }
115         return null;
116     }
117
118     /**
119      * Returns a QName associated to supplied type
120      *
121      * @param dataType
122      * @return QName associated to supplied dataType. If dataType is
123      *         Augmentation method does not return canonical QName, but QName
124      *         with correct namespace revision, but virtual local name, since
125      *         augmentations do not have name.
126      *
127      *         May return null if QName is not present.
128      */
129     public static final QName findQName(final Class<?> dataType) {
130         return classToQName.getUnchecked(dataType).orNull();
131     }
132
133     /**
134      * Checks if method is RPC invocation
135      *
136      *
137      *
138      * @param possibleMethod Method to check
139      * @return true if method is RPC invocation, false otherwise.
140      */
141     public static boolean isRpcMethod(final Method possibleMethod) {
142         return possibleMethod != null && RpcService.class.isAssignableFrom(possibleMethod.getDeclaringClass())
143                 && Future.class.isAssignableFrom(possibleMethod.getReturnType())
144                 && possibleMethod.getParameterTypes().length <= 1;
145     }
146
147     /**
148      *
149      * Extracts Output class for RPC method
150      *
151      * @param targetMethod method to scan
152      * @return Optional.absent() if result type could not be get,
153      *         or return type is Void.
154      */
155     @SuppressWarnings("rawtypes")
156     public static Optional<Class<?>> resolveRpcOutputClass(final Method targetMethod) {
157         checkState(isRpcMethod(targetMethod), "Supplied method is not Rpc invocation method");
158         Type futureType = targetMethod.getGenericReturnType();
159         Type rpcResultType = ClassLoaderUtils.getFirstGenericParameter(futureType);
160         Type rpcResultArgument = ClassLoaderUtils.getFirstGenericParameter(rpcResultType);
161         if (rpcResultArgument instanceof Class && !Void.class.equals(rpcResultArgument)) {
162             return Optional.<Class<?>> of((Class) rpcResultArgument);
163         }
164         return Optional.absent();
165     }
166
167     /**
168      *
169      * Extracts input class for RPC method
170      *
171      * @param targetMethod method to scan
172      * @return Optional.absent() if rpc has no input, Rpc input type otherwise.
173      */
174     @SuppressWarnings("unchecked")
175     public static Optional<Class<? extends DataContainer>> resolveRpcInputClass(final Method targetMethod) {
176         @SuppressWarnings("rawtypes")
177         Class[] types = targetMethod.getParameterTypes();
178         if (types.length == 0) {
179             return Optional.absent();
180         }
181         if (types.length == 1) {
182             return Optional.<Class<? extends DataContainer>> of(types[0]);
183         }
184         throw new IllegalArgumentException("Method has 2 or more arguments.");
185     }
186
187     public static QName getQName(final Class<? extends BaseIdentity> context) {
188         return findQName(context);
189     }
190
191     /**
192      *
193      * Checks if class is child of augmentation.
194      *
195      *
196      * @param clazz
197      * @return
198      */
199     public static boolean isAugmentationChild(final Class<?> clazz) {
200         // FIXME: Current resolver could be still confused when
201         // child node was added by grouping
202         checkArgument(clazz != null);
203
204         @SuppressWarnings({ "rawtypes", "unchecked" })
205         Class<?> parent = findHierarchicalParent((Class) clazz);
206         if (parent == null) {
207             LOG.debug("Did not find a parent for class {}", clazz);
208             return false;
209         }
210
211         String clazzModelPackage = getModelRootPackageName(clazz.getPackage());
212         String parentModelPackage = getModelRootPackageName(parent.getPackage());
213
214         return !clazzModelPackage.equals(parentModelPackage);
215     }
216
217     /**
218      * Returns root package name for suplied package.
219      *
220      * @param pkg Package for which find model root package.
221      * @return Package of model root.
222      */
223     public static String getModelRootPackageName(final Package pkg) {
224         return getModelRootPackageName(pkg.getName());
225     }
226
227     /**
228      * Returns root package name for suplied package name.
229      *
230      * @param pkg Package for which find model root package.
231      * @return Package of model root.
232      */
233     public static String getModelRootPackageName(final String name) {
234         checkArgument(name != null, "Package name should not be null.");
235         checkArgument(name.startsWith(BindingMapping.PACKAGE_PREFIX), "Package name not starting with %s, is: %s",
236                 BindingMapping.PACKAGE_PREFIX, name);
237         Matcher match = ROOT_PACKAGE_PATTERN.matcher(name);
238         checkArgument(match.find(), "Package name '%s' does not match required pattern '%s'", name,
239                 ROOT_PACKAGE_PATTERN_STRING);
240         return match.group(0);
241     }
242
243     /**
244      *
245      * Returns instance of {@link YangModuleInfo} of declaring model for specific class.
246      *
247      * @param cls
248      * @return Instance of {@link YangModuleInfo} associated with model, from which this class was derived.
249      * @throws Exception
250      */
251     public static YangModuleInfo getModuleInfo(final Class<?> cls) throws Exception {
252         checkArgument(cls != null);
253         String packageName = getModelRootPackageName(cls.getPackage());
254         final String potentialClassName = getModuleInfoClassName(packageName);
255         return ClassLoaderUtils.withClassLoader(cls.getClassLoader(), new Callable<YangModuleInfo>() {
256             @Override
257             public YangModuleInfo call() throws ClassNotFoundException, IllegalAccessException,
258                     IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException {
259                 Class<?> moduleInfoClass = Thread.currentThread().getContextClassLoader().loadClass(potentialClassName);
260                 return (YangModuleInfo) moduleInfoClass.getMethod("getInstance").invoke(null);
261             }
262         });
263     }
264
265     public static String getModuleInfoClassName(final String packageName) {
266         return packageName + "." + BindingMapping.MODULE_INFO_CLASS_NAME;
267     }
268
269     /**
270      *
271      * Check if supplied class is derived from YANG model.
272      *
273      * @param cls Class to check
274      * @return true if class is derived from YANG model.
275      */
276     public static boolean isBindingClass(final Class<?> cls) {
277         if (DataContainer.class.isAssignableFrom(cls) || Augmentation.class.isAssignableFrom(cls)) {
278             return true;
279         }
280         return (cls.getName().startsWith(BindingMapping.PACKAGE_PREFIX));
281     }
282
283     /**
284      *
285      * Checks if supplied method is callback for notifications.
286      *
287      * @param method
288      * @return true if method is notification callback.
289      */
290     public static boolean isNotificationCallback(final Method method) {
291         checkArgument(method != null);
292         if (method.getName().startsWith("on") && method.getParameterTypes().length == 1) {
293             Class<?> potentialNotification = method.getParameterTypes()[0];
294             if (isNotification(potentialNotification)
295                     && method.getName().equals("on" + potentialNotification.getSimpleName())) {
296                 return true;
297             }
298         }
299         return false;
300     }
301
302     /**
303      *
304      * Checks is supplied class is Notification.
305      *
306      * @param potentialNotification
307      * @return
308      */
309     public static boolean isNotification(final Class<?> potentialNotification) {
310         checkArgument(potentialNotification != null,"potentialNotification must not be null.");
311         return Notification.class.isAssignableFrom(potentialNotification);
312     }
313
314     /**
315      *
316      * Loads {@link YangModuleInfo} infos available on current classloader.
317      *
318      * This method is shorthand for {@link #loadModuleInfos(ClassLoader)} with
319      * {@link Thread#getContextClassLoader()} for current thread.
320      *
321      * @return Set of {@link YangModuleInfo} available for current classloader.
322      */
323     public static ImmutableSet<YangModuleInfo> loadModuleInfos() {
324         return loadModuleInfos(Thread.currentThread().getContextClassLoader());
325     }
326
327     /**
328     *
329     * Loads {@link YangModuleInfo} infos available on supplied classloader.
330     *
331     * {@link YangModuleInfo} are discovered using {@link ServiceLoader}
332     * for {@link YangModelBindingProvider}. {@link YangModelBindingProvider}
333     * are simple classes which holds only pointers to actual instance
334     * {@link YangModuleInfo}.
335     *
336     * When {@link YangModuleInfo} is available, all dependencies are recursivelly collected
337     * into returning set by collecting results of {@link YangModuleInfo#getImportedModules()}.
338     *
339     *
340     * @param loader Classloader for which {@link YangModuleInfo} should be retrieved.
341     * @return Set of {@link YangModuleInfo} available for supplied classloader.
342     */
343     public static ImmutableSet<YangModuleInfo> loadModuleInfos(final ClassLoader loader) {
344         Builder<YangModuleInfo> moduleInfoSet = ImmutableSet.<YangModuleInfo> builder();
345         ServiceLoader<YangModelBindingProvider> serviceLoader = ServiceLoader.load(YangModelBindingProvider.class,
346                 loader);
347         for (YangModelBindingProvider bindingProvider : serviceLoader) {
348             YangModuleInfo moduleInfo = bindingProvider.getModuleInfo();
349             checkState(moduleInfo != null, "Module Info for %s is not available.", bindingProvider.getClass());
350             collectYangModuleInfo(bindingProvider.getModuleInfo(), moduleInfoSet);
351         }
352         return moduleInfoSet.build();
353     }
354
355     private static void collectYangModuleInfo(final YangModuleInfo moduleInfo,
356             final Builder<YangModuleInfo> moduleInfoSet) {
357         moduleInfoSet.add(moduleInfo);
358         for (YangModuleInfo dependency : moduleInfo.getImportedModules()) {
359             collectYangModuleInfo(dependency, moduleInfoSet);
360         }
361     }
362
363     /**
364      *
365      * Checks if supplied class represents RPC Input / RPC Output.
366      *
367      * @param targetType Class to be checked
368      * @return true if class represents RPC Input or RPC Output class.
369      */
370     public static boolean isRpcType(final Class<? extends DataObject> targetType) {
371         return DataContainer.class.isAssignableFrom(targetType) //
372                 && !ChildOf.class.isAssignableFrom(targetType) //
373                 && !Notification.class.isAssignableFrom(targetType) //
374                 && (targetType.getName().endsWith("Input") || targetType.getName().endsWith("Output"));
375     }
376
377     /**
378      *
379      * Scans supplied class and returns an iterable of all data children classes.
380      *
381      * @param type YANG Modeled Entity derived from DataContainer
382      * @return Iterable of all data children, which have YANG modeled entity
383      */
384     @SuppressWarnings("unchecked")
385     public static Iterable<Class<? extends DataObject>> getChildrenClasses(final Class<? extends DataContainer> type) {
386         checkArgument(type != null, "Target type must not be null");
387         checkArgument(DataContainer.class.isAssignableFrom(type), "Supplied type must be derived from DataContainer");
388         List<Class<? extends DataObject>> ret = new LinkedList<>();
389         for (Method method : type.getMethods()) {
390             Optional<Class<? extends DataContainer>> entity = getYangModeledReturnType(method);
391             if (entity.isPresent()) {
392                 ret.add((Class<? extends DataObject>) entity.get());
393             }
394         }
395         return ret;
396     }
397
398     @SuppressWarnings("unchecked")
399     private static Optional<Class<? extends DataContainer>> getYangModeledReturnType(final Method method) {
400         if (method.getName().equals("getClass") || !method.getName().startsWith("get")
401                 || method.getParameterTypes().length > 0) {
402             return Optional.absent();
403         }
404
405         @SuppressWarnings("rawtypes")
406         Class returnType = method.getReturnType();
407         if (DataContainer.class.isAssignableFrom(returnType)) {
408             return Optional.<Class<? extends DataContainer>> of(returnType);
409         } else if (List.class.isAssignableFrom(returnType)) {
410             try {
411                 return ClassLoaderUtils.withClassLoader(method.getDeclaringClass().getClassLoader(),
412                         new Callable<Optional<Class<? extends DataContainer>>>() {
413                             @SuppressWarnings("rawtypes")
414                             @Override
415                             public Optional<Class<? extends DataContainer>> call() {
416                                 Type listResult = ClassLoaderUtils.getFirstGenericParameter(method
417                                         .getGenericReturnType());
418                                 if (listResult instanceof Class
419                                         && DataContainer.class.isAssignableFrom((Class) listResult)) {
420                                     return Optional.<Class<? extends DataContainer>> of((Class) listResult);
421                                 }
422                                 return Optional.absent();
423                             }
424
425                         });
426             } catch (Exception e) {
427                 /*
428                  *
429                  * It is safe to log this this exception on debug, since
430                  * this method should not fail. Only failures are possible if
431                  * the runtime / backing.
432                  *
433                  */
434                 LOG.debug("Unable to find YANG modeled return type for {}", method, e);
435             }
436         }
437         return Optional.absent();
438     }
439
440     private static class ClassToQNameLoader extends CacheLoader<Class<?>, Optional<QName>> {
441
442         @Override
443         public Optional<QName> load(final Class<?> key) throws Exception {
444             try {
445                 Field field = key.getField(BindingMapping.QNAME_STATIC_FIELD_NAME);
446                 Object obj = field.get(null);
447                 if (obj instanceof QName) {
448                     return Optional.of((QName) obj);
449                 }
450             } catch (NoSuchFieldException e) {
451                 if (Augmentation.class.isAssignableFrom(key)) {
452                     YangModuleInfo moduleInfo = getModuleInfo(key);
453                     return Optional.of(QName.create(moduleInfo.getNamespace(), moduleInfo.getRevision(),
454                             moduleInfo.getName()));
455                 }
456             } catch (SecurityException | IllegalArgumentException | IllegalAccessException e) {
457                 /*
458                  *
459                  * It is safe to log this this exception on debug, since
460                  * this method should not fail. Only failures are possible if
461                  * the runtime / backing.
462                  *
463                  */
464                 LOG.debug("Unexpected exception during extracting QName for {}",key,e);
465             }
466             return Optional.absent();
467         }
468     }
469 }