789f9542ee4a051e3e85db114ba28e56f0e7705a
[mdsal.git] / binding / 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 com.google.common.base.Optional;
13 import com.google.common.cache.CacheBuilder;
14 import com.google.common.cache.CacheLoader;
15 import com.google.common.cache.LoadingCache;
16 import com.google.common.collect.ImmutableSet;
17 import com.google.common.collect.ImmutableSet.Builder;
18 import com.google.common.collect.Sets;
19 import java.lang.reflect.Field;
20 import java.lang.reflect.Method;
21 import java.lang.reflect.Type;
22 import java.net.URI;
23 import java.util.HashMap;
24 import java.util.HashSet;
25 import java.util.LinkedList;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.ServiceLoader;
29 import java.util.concurrent.Callable;
30 import java.util.concurrent.Future;
31 import java.util.concurrent.TimeUnit;
32 import java.util.regex.Matcher;
33 import java.util.regex.Pattern;
34 import org.opendaylight.yangtools.util.ClassLoaderUtils;
35 import org.opendaylight.yangtools.yang.binding.Augmentable;
36 import org.opendaylight.yangtools.yang.binding.Augmentation;
37 import org.opendaylight.yangtools.yang.binding.BaseIdentity;
38 import org.opendaylight.yangtools.yang.binding.BindingMapping;
39 import org.opendaylight.yangtools.yang.binding.ChildOf;
40 import org.opendaylight.yangtools.yang.binding.DataContainer;
41 import org.opendaylight.yangtools.yang.binding.DataObject;
42 import org.opendaylight.yangtools.yang.binding.Notification;
43 import org.opendaylight.yangtools.yang.binding.RpcService;
44 import org.opendaylight.yangtools.yang.binding.YangModelBindingProvider;
45 import org.opendaylight.yangtools.yang.binding.YangModuleInfo;
46 import org.opendaylight.yangtools.yang.common.QName;
47 import org.opendaylight.yangtools.yang.common.QNameModule;
48 import org.slf4j.Logger;
49 import org.slf4j.LoggerFactory;
50
51 public class BindingReflections {
52
53     private static final long EXPIRATION_TIME = 60;
54     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])";
55     private static final Pattern ROOT_PACKAGE_PATTERN = Pattern.compile(ROOT_PACKAGE_PATTERN_STRING);
56     private static final Logger LOG = LoggerFactory.getLogger(BindingReflections.class);
57
58     private static final LoadingCache<Class<?>, Optional<QName>> CLASS_TO_QNAME = CacheBuilder.newBuilder() //
59             .weakKeys() //
60             .expireAfterAccess(EXPIRATION_TIME, TimeUnit.SECONDS) //
61             .build(new ClassToQNameLoader());
62
63     private BindingReflections() {
64         throw new UnsupportedOperationException("Utility class.");
65     }
66
67     /**
68      * Find augmentation target class from concrete Augmentation class
69      *
70      * This method uses first generic argument of implemented
71      * {@link Augmentation} interface.
72      *
73      * @param augmentation
74      *            {@link Augmentation} subclass for which we want to determine
75      *            augmentation target.
76      * @return Augmentation target - class which augmentation provides
77      *         additional extensions.
78      */
79     public static Class<? extends Augmentable<?>> findAugmentationTarget(
80             final Class<? extends Augmentation<?>> augmentation) {
81         return ClassLoaderUtils.findFirstGenericArgument(augmentation, Augmentation.class);
82     }
83
84     /**
85      * Find data hierarchy parent from concrete Data class
86      *
87      * This method uses first generic argument of implemented {@link ChildOf}
88      * interface.
89      *
90      * @param childClass
91      *            child class for which we want to find the parent class.
92      * @return Parent class, e.g. class of which the childClass is ChildOf.
93      */
94     public static Class<?> findHierarchicalParent(final Class<? extends ChildOf<?>> childClass) {
95         return ClassLoaderUtils.findFirstGenericArgument(childClass, ChildOf.class);
96     }
97
98     /**
99      * Find data hierarchy parent from concrete Data class
100      *
101      * This method is shorthand which gets DataObject class by invoking
102      * {@link DataObject#getImplementedInterface()} and uses
103      * {@link #findHierarchicalParent(Class)}.
104      *
105      * @param child
106      *            Child object for which the parent needs to be located.
107      * @return Parent class, or null if a parent is not found.
108      */
109     public static Class<?> findHierarchicalParent(final DataObject child) {
110         if (child instanceof ChildOf) {
111             return ClassLoaderUtils.findFirstGenericArgument(child.getImplementedInterface(), ChildOf.class);
112         }
113         return null;
114     }
115
116     /**
117      * Returns a QName associated to supplied type
118      *
119      * @param dataType
120      * @return QName associated to supplied dataType. If dataType is
121      *         Augmentation method does not return canonical QName, but QName
122      *         with correct namespace revision, but virtual local name, since
123      *         augmentations do not have name.
124      *
125      *         May return null if QName is not present.
126      */
127     public static final QName findQName(final Class<?> dataType) {
128         return CLASS_TO_QNAME.getUnchecked(dataType).orNull();
129     }
130
131     /**
132      * Checks if method is RPC invocation
133      *
134      *
135      *
136      * @param possibleMethod
137      *            Method to check
138      * @return true if method is RPC invocation, false otherwise.
139      */
140     public static boolean isRpcMethod(final Method possibleMethod) {
141         return possibleMethod != null && RpcService.class.isAssignableFrom(possibleMethod.getDeclaringClass())
142                 && Future.class.isAssignableFrom(possibleMethod.getReturnType())
143                 && possibleMethod.getParameterTypes().length <= 1;
144     }
145
146     /**
147      *
148      * Extracts Output class for RPC method
149      *
150      * @param targetMethod
151      *            method to scan
152      * @return Optional.absent() if result type could not be get, or return type
153      *         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
172      *            method to scan
173      * @return Optional.absent() if rpc has no input, Rpc input type otherwise.
174      */
175     @SuppressWarnings("unchecked")
176     public static Optional<Class<? extends DataContainer>> resolveRpcInputClass(final Method targetMethod) {
177         @SuppressWarnings("rawtypes")
178         Class[] types = targetMethod.getParameterTypes();
179         if (types.length == 0) {
180             return Optional.absent();
181         }
182         if (types.length == 1) {
183             return Optional.<Class<? extends DataContainer>> of(types[0]);
184         }
185         throw new IllegalArgumentException("Method has 2 or more arguments.");
186     }
187
188     public static QName getQName(final Class<? extends BaseIdentity> context) {
189         return findQName(context);
190     }
191
192     /**
193      *
194      * Checks if class is child of augmentation.
195      *
196      *
197      * @param clazz
198      * @return
199      */
200     public static boolean isAugmentationChild(final Class<?> clazz) {
201         // FIXME: Current resolver could be still confused when
202         // child node was added by grouping
203         checkArgument(clazz != null);
204
205         @SuppressWarnings({ "rawtypes", "unchecked" })
206         Class<?> parent = findHierarchicalParent((Class) clazz);
207         if (parent == null) {
208             LOG.debug("Did not find a parent for class {}", clazz);
209             return false;
210         }
211
212         String clazzModelPackage = getModelRootPackageName(clazz.getPackage());
213         String parentModelPackage = getModelRootPackageName(parent.getPackage());
214
215         return !clazzModelPackage.equals(parentModelPackage);
216     }
217
218     /**
219      * Returns root package name for suplied package.
220      *
221      * @param pkg
222      *            Package for which find model root package.
223      * @return Package of model root.
224      */
225     public static String getModelRootPackageName(final Package pkg) {
226         return getModelRootPackageName(pkg.getName());
227     }
228
229     /**
230      * Returns root package name for supplied package name.
231      *
232      * @param name
233      *            Package for which find model root package.
234      * @return Package of model root.
235      */
236     public static String getModelRootPackageName(final String name) {
237         checkArgument(name != null, "Package name should not be null.");
238         checkArgument(name.startsWith(BindingMapping.PACKAGE_PREFIX), "Package name not starting with %s, is: %s",
239                 BindingMapping.PACKAGE_PREFIX, name);
240         Matcher match = ROOT_PACKAGE_PATTERN.matcher(name);
241         checkArgument(match.find(), "Package name '%s' does not match required pattern '%s'", name,
242                 ROOT_PACKAGE_PATTERN_STRING);
243         return match.group(0);
244     }
245
246     public static final QNameModule getQNameModule(final Class<?> clz) {
247         if(DataContainer.class.isAssignableFrom(clz) || BaseIdentity.class.isAssignableFrom(clz)) {
248             return findQName(clz).getModule();
249         }
250         try {
251             YangModuleInfo modInfo = BindingReflections.getModuleInfo(clz);
252             return getQNameModule(modInfo);
253         } catch (Exception e) {
254             throw new IllegalStateException("Unable to get QName of defining model.", e);
255         }
256     }
257
258     public static final QNameModule getQNameModule(final YangModuleInfo modInfo) {
259         return QNameModule.create(URI.create(modInfo.getNamespace()), QName.parseRevision(modInfo.getRevision()));
260     }
261
262     /**
263      *
264      * Returns instance of {@link YangModuleInfo} of declaring model for
265      * specific class.
266      *
267      * @param cls
268      * @return Instance of {@link YangModuleInfo} associated with model, from
269      *         which this class was derived.
270      * @throws Exception
271      */
272     public static YangModuleInfo getModuleInfo(final Class<?> cls) throws Exception {
273         checkArgument(cls != null);
274         String packageName = getModelRootPackageName(cls.getPackage());
275         final String potentialClassName = getModuleInfoClassName(packageName);
276         return ClassLoaderUtils.withClassLoader(cls.getClassLoader(), (Callable<YangModuleInfo>) () -> {
277             Class<?> moduleInfoClass = Thread.currentThread().getContextClassLoader().loadClass(potentialClassName);
278             return (YangModuleInfo) moduleInfoClass.getMethod("getInstance").invoke(null);
279          });
280     }
281
282     public static String getModuleInfoClassName(final String packageName) {
283         return packageName + "." + BindingMapping.MODULE_INFO_CLASS_NAME;
284     }
285
286     /**
287      *
288      * Check if supplied class is derived from YANG model.
289      *
290      * @param cls
291      *            Class to check
292      * @return true if class is derived from YANG model.
293      */
294     public static boolean isBindingClass(final Class<?> cls) {
295         if (DataContainer.class.isAssignableFrom(cls) || Augmentation.class.isAssignableFrom(cls)) {
296             return true;
297         }
298         return cls.getName().startsWith(BindingMapping.PACKAGE_PREFIX);
299     }
300
301     /**
302      *
303      * Checks if supplied method is callback for notifications.
304      *
305      * @param method
306      * @return true if method is notification callback.
307      */
308     public static boolean isNotificationCallback(final Method method) {
309         checkArgument(method != null);
310         if (method.getName().startsWith("on") && method.getParameterTypes().length == 1) {
311             Class<?> potentialNotification = method.getParameterTypes()[0];
312             if (isNotification(potentialNotification)
313                     && method.getName().equals("on" + potentialNotification.getSimpleName())) {
314                 return true;
315             }
316         }
317         return false;
318     }
319
320     /**
321      *
322      * Checks is supplied class is Notification.
323      *
324      * @param potentialNotification
325      * @return
326      */
327     public static boolean isNotification(final Class<?> potentialNotification) {
328         checkArgument(potentialNotification != null, "potentialNotification must not be null.");
329         return Notification.class.isAssignableFrom(potentialNotification);
330     }
331
332     /**
333      *
334      * Loads {@link YangModuleInfo} infos available on current classloader.
335      *
336      * This method is shorthand for {@link #loadModuleInfos(ClassLoader)} with
337      * {@link Thread#getContextClassLoader()} for current thread.
338      *
339      * @return Set of {@link YangModuleInfo} available for current classloader.
340      */
341     public static ImmutableSet<YangModuleInfo> loadModuleInfos() {
342         return loadModuleInfos(Thread.currentThread().getContextClassLoader());
343     }
344
345     /**
346      *
347      * Loads {@link YangModuleInfo} infos available on supplied classloader.
348      *
349      * {@link YangModuleInfo} are discovered using {@link ServiceLoader} for
350      * {@link YangModelBindingProvider}. {@link YangModelBindingProvider} are
351      * simple classes which holds only pointers to actual instance
352      * {@link YangModuleInfo}.
353      *
354      * When {@link YangModuleInfo} is available, all dependencies are
355      * recursivelly collected into returning set by collecting results of
356      * {@link YangModuleInfo#getImportedModules()}.
357      *
358      *
359      * @param loader
360      *            Classloader for which {@link YangModuleInfo} should be
361      *            retrieved.
362      * @return Set of {@link YangModuleInfo} available for supplied classloader.
363      */
364     public static ImmutableSet<YangModuleInfo> loadModuleInfos(final ClassLoader loader) {
365         Builder<YangModuleInfo> moduleInfoSet = ImmutableSet.<YangModuleInfo> builder();
366         ServiceLoader<YangModelBindingProvider> serviceLoader = ServiceLoader.load(YangModelBindingProvider.class,
367                 loader);
368         for (YangModelBindingProvider bindingProvider : serviceLoader) {
369             YangModuleInfo moduleInfo = bindingProvider.getModuleInfo();
370             checkState(moduleInfo != null, "Module Info for %s is not available.", bindingProvider.getClass());
371             collectYangModuleInfo(bindingProvider.getModuleInfo(), moduleInfoSet);
372         }
373         return moduleInfoSet.build();
374     }
375
376     private static void collectYangModuleInfo(final YangModuleInfo moduleInfo,
377             final Builder<YangModuleInfo> moduleInfoSet) {
378         moduleInfoSet.add(moduleInfo);
379         for (YangModuleInfo dependency : moduleInfo.getImportedModules()) {
380             collectYangModuleInfo(dependency, moduleInfoSet);
381         }
382     }
383
384     /**
385      *
386      * Checks if supplied class represents RPC Input / RPC Output.
387      *
388      * @param targetType
389      *            Class to be checked
390      * @return true if class represents RPC Input or RPC Output class.
391      */
392     public static boolean isRpcType(final Class<? extends DataObject> targetType) {
393         return DataContainer.class.isAssignableFrom(targetType) //
394                 && !ChildOf.class.isAssignableFrom(targetType) //
395                 && !Notification.class.isAssignableFrom(targetType) //
396                 && (targetType.getName().endsWith("Input") || targetType.getName().endsWith("Output"));
397     }
398
399     /**
400      *
401      * Scans supplied class and returns an iterable of all data children
402      * classes.
403      *
404      * @param type
405      *            YANG Modeled Entity derived from DataContainer
406      * @return Iterable of all data children, which have YANG modeled entity
407      */
408     @SuppressWarnings("unchecked")
409     public static Iterable<Class<? extends DataObject>> getChildrenClasses(final Class<? extends DataContainer> type) {
410         checkArgument(type != null, "Target type must not be null");
411         checkArgument(DataContainer.class.isAssignableFrom(type), "Supplied type must be derived from DataContainer");
412         List<Class<? extends DataObject>> ret = new LinkedList<>();
413         for (Method method : type.getMethods()) {
414             Optional<Class<? extends DataContainer>> entity = getYangModeledReturnType(method);
415             if (entity.isPresent()) {
416                 ret.add((Class<? extends DataObject>) entity.get());
417             }
418         }
419         return ret;
420     }
421
422     /**
423      *
424      * Scans supplied class and returns an iterable of all data children
425      * classes.
426      *
427      * @param type
428      *            YANG Modeled Entity derived from DataContainer
429      * @return Iterable of all data children, which have YANG modeled entity
430      */
431     public static Map<Class<?>, Method> getChildrenClassToMethod(final Class<?> type) {
432         checkArgument(type != null, "Target type must not be null");
433         checkArgument(DataContainer.class.isAssignableFrom(type), "Supplied type must be derived from DataContainer");
434         Map<Class<?>, Method> ret = new HashMap<>();
435         for (Method method : type.getMethods()) {
436             Optional<Class<? extends DataContainer>> entity = getYangModeledReturnType(method);
437             if (entity.isPresent()) {
438                 ret.put(entity.get(), method);
439             }
440         }
441         return ret;
442     }
443
444     @SuppressWarnings({ "unchecked", "rawtypes" })
445     private static Optional<Class<? extends DataContainer>> getYangModeledReturnType(final Method method) {
446         if ("getClass".equals(method.getName()) || !method.getName().startsWith("get")
447                 || method.getParameterTypes().length > 0) {
448             return Optional.absent();
449         }
450
451         @SuppressWarnings("rawtypes")
452         Class returnType = method.getReturnType();
453         if (DataContainer.class.isAssignableFrom(returnType)) {
454             return Optional.<Class<? extends DataContainer>> of(returnType);
455         } else if (List.class.isAssignableFrom(returnType)) {
456             try {
457                 return ClassLoaderUtils.withClassLoader(method.getDeclaringClass().getClassLoader(),
458                         (Callable<Optional<Class<? extends DataContainer>>>) () -> {
459                             Type listResult = ClassLoaderUtils.getFirstGenericParameter(method.getGenericReturnType());
460                             if (listResult instanceof Class
461                                     && DataContainer.class.isAssignableFrom((Class) listResult)) {
462                                 return Optional.<Class<? extends DataContainer>> of((Class) listResult);
463                             }
464                             return Optional.absent();
465                         });
466             } catch (Exception e) {
467                 /*
468                  *
469                  * It is safe to log this this exception on debug, since this
470                  * method should not fail. Only failures are possible if the
471                  * runtime / backing.
472                  */
473                 LOG.debug("Unable to find YANG modeled return type for {}", method, e);
474             }
475         }
476         return Optional.absent();
477     }
478
479     private static class ClassToQNameLoader extends CacheLoader<Class<?>, Optional<QName>> {
480
481         @Override
482         public Optional<QName> load(final Class<?> key) throws Exception {
483             return resolveQNameNoCache(key);
484         }
485
486         /**
487          *
488          * Tries to resolve QName for supplied class.
489          *
490          * Looks up for static field with name from constant {@link BindingMapping#QNAME_STATIC_FIELD_NAME} and returns
491          * value if present.
492          *
493          * If field is not present uses {@link #computeQName(Class)} to compute QName for missing types.
494          *
495          * @param key
496          * @return
497          */
498         private static Optional<QName> resolveQNameNoCache(final Class<?> key) {
499             try {
500                 Field field = key.getField(BindingMapping.QNAME_STATIC_FIELD_NAME);
501                 Object obj = field.get(null);
502                 if (obj instanceof QName) {
503                     return Optional.of((QName) obj);
504                 }
505
506             } catch (NoSuchFieldException e) {
507                 return Optional.of(computeQName(key));
508
509             } catch (SecurityException | IllegalArgumentException | IllegalAccessException e) {
510                 /*
511                  *
512                  * It is safe to log this this exception on debug, since this method
513                  * should not fail. Only failures are possible if the runtime /
514                  * backing.
515                  */
516                 LOG.debug("Unexpected exception during extracting QName for {}", key, e);
517             }
518             return Optional.absent();
519         }
520
521         /**
522          * Computes QName for supplied class
523          *
524          * Namespace and revision are same as {@link YangModuleInfo} associated with supplied class.
525          * <p>
526          * If class is
527          * <ul>
528          * <li>rpc input: local name is "input".
529          * <li>rpc output: local name is "output".
530          * <li>augmentation: local name is "module name".
531          * </ul>
532          *
533          * There is also fallback, if it is not possible to compute QName using following algorithm returns module
534          * QName.
535          *
536          * FIXME: Extend this algorithm to also provide QName for YANG modeled simple types.
537          *
538          * @throws IllegalStateException If YangModuleInfo could not be resolved
539          * @throws IllegalArgumentException If supplied class was not derived from YANG model.
540          *
541          */
542         @SuppressWarnings({ "rawtypes", "unchecked" })
543         private static QName computeQName(final Class key) {
544             if (isBindingClass(key)) {
545                 YangModuleInfo moduleInfo;
546                 try {
547                     moduleInfo = getModuleInfo(key);
548                 } catch (Exception e) {
549                     throw new IllegalStateException("Unable to get QName for " + key
550                             + ". YangModuleInfo was not found.", e);
551                 }
552                 final QName module = getModuleQName(moduleInfo).intern();
553                 if (Augmentation.class.isAssignableFrom(key)) {
554                     return module;
555                 } else if (isRpcType(key)) {
556                     final String className = key.getSimpleName();
557                     if (className.endsWith(BindingMapping.RPC_OUTPUT_SUFFIX)) {
558                         return QName.create(module, "output").intern();
559                     } else {
560                         return QName.create(module, "input").intern();
561                     }
562                 }
563                 /*
564                  * Fallback for Binding types which do not have QNAME field
565                  */
566                 return module;
567             } else {
568                 throw new IllegalArgumentException("Supplied class " + key + "is not derived from YANG.");
569             }
570         }
571
572     }
573
574     /**
575      * Given a {@link YangModuleInfo}, create a QName representing it. The QName
576      * is formed by reusing the module's namespace and revision using the
577      * module's name as the QName's local name.
578      *
579      * @param moduleInfo
580      *            module information
581      * @return QName representing the module
582      */
583     public static QName getModuleQName(final YangModuleInfo moduleInfo) {
584         checkArgument(moduleInfo != null, "moduleInfo must not be null.");
585         return QName.create(moduleInfo.getNamespace(), moduleInfo.getRevision(), moduleInfo.getName());
586     }
587
588     /**
589      * Extracts augmentation from Binding DTO field using reflection
590      *
591      * @param input
592      *            Instance of DataObject which is augmentable and may contain
593      *            augmentation
594      * @return Map of augmentations if read was successful, otherwise empty map.
595      */
596     public static Map<Class<? extends Augmentation<?>>, Augmentation<?>> getAugmentations(final Augmentable<?> input) {
597         return AugmentationFieldGetter.getGetter(input.getClass()).getAugmentations(input);
598     }
599
600     /**
601      *
602      * Determines if two augmentation classes or case classes represents same
603      * data.
604      * <p>
605      * Two augmentations or cases could be substituted only if and if:
606      * <ul>
607      * <li>Both implements same interfaces</li>
608      * <li>Both have same children</li>
609      * <li>If augmentations: Both have same augmentation target class. Target
610      * class was generated for data node in grouping.</li>
611      * <li>If cases: Both are from same choice. Choice class was generated for
612      * data node in grouping.</li>
613      * </ul>
614      * <p>
615      * <b>Explanation:</b> Binding Specification reuses classes generated for
616      * groupings as part of normal data tree, this classes from grouping could
617      * be used at various locations and user may not be aware of it and may use
618      * incorrect case or augmentation in particular subtree (via copy
619      * constructors, etc).
620      *
621      * @param potential
622      *            Class which is potential substition
623      * @param target
624      *            Class which should be used at particular subtree
625      * @return true if and only if classes represents same data.
626      */
627     @SuppressWarnings({ "rawtypes", "unchecked" })
628     public static boolean isSubstitutionFor(final Class potential, final Class target) {
629         HashSet<Class> subImplemented = Sets.newHashSet(potential.getInterfaces());
630         HashSet<Class> targetImplemented = Sets.newHashSet(target.getInterfaces());
631         if (!subImplemented.equals(targetImplemented)) {
632             return false;
633         }
634         if (Augmentation.class.isAssignableFrom(potential)
635                 && !BindingReflections.findAugmentationTarget(potential).equals(
636                         BindingReflections.findAugmentationTarget(target))) {
637             return false;
638         }
639         for (Method potentialMethod : potential.getMethods()) {
640             try {
641                 Method targetMethod = target.getMethod(potentialMethod.getName(), potentialMethod.getParameterTypes());
642                 if (!potentialMethod.getReturnType().equals(targetMethod.getReturnType())) {
643                     return false;
644                 }
645             } catch (NoSuchMethodException e) {
646                 // Counterpart method is missing, so classes could not be
647                 // substituted.
648                 return false;
649             } catch (SecurityException e) {
650                 throw new IllegalStateException("Could not compare methods", e);
651             }
652         }
653         return true;
654     }
655 }