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