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 import static org.opendaylight.yangtools.concepts.util.ClassLoaderUtils.withClassLoader;
13
14 import java.lang.reflect.Field;
15 import java.lang.reflect.Method;
16 import java.lang.reflect.Type;
17 import java.util.ServiceLoader;
18 import java.util.concurrent.Callable;
19 import java.util.concurrent.Future;
20 import java.util.concurrent.TimeUnit;
21 import java.util.regex.Matcher;
22 import java.util.regex.Pattern;
23
24 import org.opendaylight.yangtools.concepts.util.ClassLoaderUtils;
25 import org.opendaylight.yangtools.yang.binding.Augmentable;
26 import org.opendaylight.yangtools.yang.binding.Augmentation;
27 import org.opendaylight.yangtools.yang.binding.BaseIdentity;
28 import org.opendaylight.yangtools.yang.binding.BindingMapping;
29 import org.opendaylight.yangtools.yang.binding.ChildOf;
30 import org.opendaylight.yangtools.yang.binding.DataContainer;
31 import org.opendaylight.yangtools.yang.binding.DataObject;
32 import org.opendaylight.yangtools.yang.binding.Notification;
33 import org.opendaylight.yangtools.yang.binding.RpcService;
34 import org.opendaylight.yangtools.yang.binding.YangModelBindingProvider;
35 import org.opendaylight.yangtools.yang.binding.YangModuleInfo;
36 import org.opendaylight.yangtools.yang.common.QName;
37
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      *
62      * @param augmentation
63      *            {@link Augmentation} subclass for which we want to determine
64      *            augmentation target.
65      * @return Augmentation target - class which augmentation provides
66      *         additional extensions.
67      */
68     public static Class<? extends Augmentable<?>> findAugmentationTarget(
69             final Class<? extends Augmentation<?>> augmentation) {
70         return ClassLoaderUtils.findFirstGenericArgument(augmentation, Augmentation.class);
71     }
72
73     /**
74      *
75      * @param augmentation
76      *            {@link Augmentation} subclass for which we want to determine
77      *            augmentation target.
78      * @return Augmentation target - class which augmentation provides
79      *         additional extensions.
80      */
81     public static Class<?> findHierarchicalParent(final Class<? extends ChildOf<?>> childClass) {
82         return ClassLoaderUtils.findFirstGenericArgument(childClass, ChildOf.class);
83     }
84
85     /**
86      *
87      * @param augmentation
88      *            {@link Augmentation} subclass for which we want to determine
89      *            augmentation target.
90      * @return Augmentation target - class which augmentation provides
91      *         additional extensions.
92      */
93     public static Class<?> findHierarchicalParent(final DataObject childClass) {
94         if (childClass instanceof ChildOf) {
95             return ClassLoaderUtils.findFirstGenericArgument(childClass.getImplementedInterface(), ChildOf.class);
96         }
97         return null;
98     }
99
100     /**
101      * Returns a QName associated to supplied type
102      *
103      * @param dataType
104      * @return QName associated to supplied dataType. If dataType is Augmentation
105      *    method does not return canonical QName, but QName with correct namespace
106      *    revision, but virtual local name, since augmentations do not have name.
107      */
108     public static final QName findQName(final Class<?> dataType) {
109         return classToQName.getUnchecked(dataType).orNull();
110     }
111
112     private static class ClassToQNameLoader extends CacheLoader<Class<?>, Optional<QName>> {
113
114         @Override
115         public Optional<QName> load(final Class<?> key) throws Exception {
116             try {
117                 Field field = key.getField(BindingMapping.QNAME_STATIC_FIELD_NAME);
118                 Object obj = field.get(null);
119                 if (obj instanceof QName) {
120                     return Optional.of((QName) obj);
121                 }
122             } catch (NoSuchFieldException e) {
123                 if(Augmentation.class.isAssignableFrom(key)) {
124                     YangModuleInfo moduleInfo = getModuleInfo(key);
125                     return Optional.of(QName.create(moduleInfo.getNamespace(), moduleInfo.getRevision(), moduleInfo.getName()));
126                 }
127
128             } catch (SecurityException | IllegalArgumentException | IllegalAccessException e) {
129                 // NOOP
130             }
131             return Optional.absent();
132         }
133     }
134
135     public static boolean isRpcMethod(final Method possibleMethod) {
136         return possibleMethod != null && RpcService.class.isAssignableFrom(possibleMethod.getDeclaringClass())
137                 && Future.class.isAssignableFrom(possibleMethod.getReturnType())
138                 && possibleMethod.getParameterTypes().length <= 1;
139     }
140
141     @SuppressWarnings("rawtypes")
142     public static Optional<Class<?>> resolveRpcOutputClass(final Method targetMethod) {
143         checkState(isRpcMethod(targetMethod), "Supplied method is not Rpc invocation method");
144         Type futureType = targetMethod.getGenericReturnType();
145         Type rpcResultType = ClassLoaderUtils.getFirstGenericParameter(futureType);
146         Type rpcResultArgument = ClassLoaderUtils.getFirstGenericParameter(rpcResultType);
147         if (rpcResultArgument instanceof Class && !Void.class.equals(rpcResultArgument)) {
148             return Optional.<Class<?>> of((Class) rpcResultArgument);
149         }
150         return Optional.absent();
151     }
152
153     @SuppressWarnings("unchecked")
154     public static Optional<Class<? extends DataContainer>> resolveRpcInputClass(final Method targetMethod) {
155         @SuppressWarnings("rawtypes")
156         Class[] types = targetMethod.getParameterTypes();
157         if (types.length == 0) {
158             return Optional.absent();
159         }
160         if (types.length == 1) {
161             return Optional.<Class<? extends DataContainer>> of(types[0]);
162         }
163         throw new IllegalArgumentException("Method has 2 or more arguments.");
164     }
165
166     public static QName getQName(final Class<? extends BaseIdentity> context) {
167         return findQName(context);
168     }
169
170     public static boolean isAugmentationChild(final Class<?> clazz) {
171         // FIXME: Current resolver could be still confused when
172         // child node was added by grouping
173         checkArgument(clazz != null);
174
175         @SuppressWarnings({ "rawtypes", "unchecked" })
176         Class<?> parent = findHierarchicalParent((Class) clazz);
177         if (parent == null) {
178             LOG.debug("Did not find a parent for class {}", clazz);
179             return false;
180         }
181
182         String clazzModelPackage = getModelRootPackageName(clazz.getPackage());
183         String parentModelPackage = getModelRootPackageName(parent.getPackage());
184
185         return !clazzModelPackage.equals(parentModelPackage);
186     }
187
188     public static String getModelRootPackageName(final Package pkg) {
189         return getModelRootPackageName(pkg.getName());
190     }
191
192     public static String getModelRootPackageName(final String name) {
193         checkArgument(name != null, "Package name should not be null.");
194         checkArgument(name.startsWith(BindingMapping.PACKAGE_PREFIX), "Package name not starting with %s, is: %s",
195                 BindingMapping.PACKAGE_PREFIX, name);
196         Matcher match = ROOT_PACKAGE_PATTERN.matcher(name);
197         checkArgument(match.find(),"Package name '%s' does not match required pattern '%s'",name,ROOT_PACKAGE_PATTERN_STRING);
198         return match.group(0);
199     }
200
201     public static YangModuleInfo getModuleInfo(final Class<?> cls) throws Exception {
202         checkArgument(cls != null);
203         String packageName = getModelRootPackageName(cls.getPackage());
204         final String potentialClassName = getModuleInfoClassName(packageName);
205         return withClassLoader(cls.getClassLoader(), new Callable<YangModuleInfo>() {
206
207             @Override
208             public YangModuleInfo call() throws Exception {
209                 Class<?> moduleInfoClass = Thread.currentThread().getContextClassLoader().loadClass(potentialClassName);
210                 return (YangModuleInfo) moduleInfoClass.getMethod("getInstance").invoke(null);
211             }
212         });
213     }
214
215     public static String getModuleInfoClassName(final String packageName) {
216         return packageName + "." + BindingMapping.MODULE_INFO_CLASS_NAME;
217     }
218
219     public static boolean isBindingClass(final Class<?> cls) {
220         if (DataContainer.class.isAssignableFrom(cls) || Augmentation.class.isAssignableFrom(cls)) {
221             return true;
222         }
223         return (cls.getName().startsWith(BindingMapping.PACKAGE_PREFIX));
224     }
225
226     public static boolean isNotificationCallback(final Method method) {
227         checkArgument(method != null);
228         if (method.getName().startsWith("on") && method.getParameterTypes().length == 1) {
229             Class<?> potentialNotification = method.getParameterTypes()[0];
230             if (isNotification(potentialNotification)
231                     && method.getName().equals("on" + potentialNotification.getSimpleName())) {
232                 return true;
233             }
234         }
235         return false;
236     }
237
238     public static boolean isNotification(final Class<?> potentialNotification) {
239         checkArgument(potentialNotification != null);
240         return Notification.class.isAssignableFrom(potentialNotification);
241     }
242
243     public static ImmutableSet<YangModuleInfo> loadModuleInfos() {
244         return loadModuleInfos(Thread.currentThread().getContextClassLoader());
245     }
246
247     public static ImmutableSet<YangModuleInfo> loadModuleInfos(final ClassLoader loader) {
248         Builder<YangModuleInfo> moduleInfoSet = ImmutableSet.<YangModuleInfo>builder();
249         ServiceLoader<YangModelBindingProvider> serviceLoader = ServiceLoader.load(YangModelBindingProvider.class, loader);
250         for(YangModelBindingProvider bindingProvider : serviceLoader) {
251             YangModuleInfo moduleInfo = bindingProvider.getModuleInfo();
252             checkState(moduleInfo != null, "Module Info for %s is not available.",bindingProvider.getClass());
253             collectYangModuleInfo(bindingProvider.getModuleInfo(),moduleInfoSet);
254         }
255         return  moduleInfoSet.build();
256     }
257
258     private static void collectYangModuleInfo(final YangModuleInfo moduleInfo, final Builder<YangModuleInfo> moduleInfoSet) {
259         moduleInfoSet.add(moduleInfo);
260         for(YangModuleInfo dependency : moduleInfo.getImportedModules()) {
261             collectYangModuleInfo(dependency, moduleInfoSet);
262         }
263     }
264
265     public static boolean isRpcType(final Class<? extends DataObject> targetType) {
266         return  DataContainer.class.isAssignableFrom(targetType) //
267                 && !ChildOf.class.isAssignableFrom(targetType) //
268                 && !Notification.class.isAssignableFrom(targetType) //
269                 && (targetType.getName().endsWith("Input") || targetType.getName().endsWith("Output"));
270     }
271
272 }