X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=blobdiff_plain;f=binding%2Fmdsal-binding-spec-util%2Fsrc%2Fmain%2Fjava%2Forg%2Fopendaylight%2Fmdsal%2Fbinding%2Fspec%2Freflect%2FBindingReflections.java;h=ef55504e52957f0e39a3dcbe4d70b6e4ecd67dbe;hb=refs%2Fchanges%2F98%2F99998%2F2;hp=c4a0d34abf85c2ebd15ef9ab76ef3baa291fa229;hpb=d1081ff6798c7678eaaa5decb1a389a884389f51;p=mdsal.git diff --git a/binding/mdsal-binding-spec-util/src/main/java/org/opendaylight/mdsal/binding/spec/reflect/BindingReflections.java b/binding/mdsal-binding-spec-util/src/main/java/org/opendaylight/mdsal/binding/spec/reflect/BindingReflections.java index c4a0d34abf..ef55504e52 100644 --- a/binding/mdsal-binding-spec-util/src/main/java/org/opendaylight/mdsal/binding/spec/reflect/BindingReflections.java +++ b/binding/mdsal-binding-spec-util/src/main/java/org/opendaylight/mdsal/binding/spec/reflect/BindingReflections.java @@ -9,28 +9,35 @@ package org.opendaylight.mdsal.binding.spec.reflect; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; +import static java.util.Objects.requireNonNull; -import com.google.common.base.Optional; +import com.google.common.annotations.Beta; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet.Builder; -import com.google.common.collect.Sets; import com.google.common.util.concurrent.ListenableFuture; import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; +import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.ServiceLoader; +import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; -import javax.annotation.RegEx; +import org.checkerframework.checker.regex.qual.Regex; +import org.eclipse.jdt.annotation.NonNull; import org.opendaylight.mdsal.binding.spec.naming.BindingMapping; import org.opendaylight.yangtools.util.ClassLoaderUtils; import org.opendaylight.yangtools.yang.binding.Action; @@ -54,7 +61,7 @@ public final class BindingReflections { private static final long EXPIRATION_TIME = 60; - @RegEx + @Regex 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]|norev))"; private static final Pattern ROOT_PACKAGE_PATTERN = Pattern.compile(ROOT_PACKAGE_PATTERN_STRING); @@ -65,8 +72,17 @@ public final class BindingReflections { .expireAfterAccess(EXPIRATION_TIME, TimeUnit.SECONDS) .build(new ClassToQNameLoader()); + private static final LoadingCache> MODULE_INFO_CACHE = + CacheBuilder.newBuilder().weakKeys().weakValues().build( + new CacheLoader>() { + @Override + public ImmutableSet load(final ClassLoader key) { + return loadModuleInfos(key); + } + }); + private BindingReflections() { - throw new UnsupportedOperationException("Utility class."); + // Hidden on purpose } /** @@ -80,7 +96,9 @@ public final class BindingReflections { */ public static Class> findAugmentationTarget( final Class> augmentation) { - return ClassLoaderUtils.findFirstGenericArgument(augmentation, Augmentation.class); + final Optional>> opt = ClassLoaderUtils.findFirstGenericArgument(augmentation, + Augmentation.class); + return opt.orElse(null); } /** @@ -92,12 +110,12 @@ public final class BindingReflections { * @return Parent class, e.g. class of which the childClass is ChildOf. */ public static Class findHierarchicalParent(final Class> childClass) { - return ClassLoaderUtils.findFirstGenericArgument(childClass, ChildOf.class); + return ClassLoaderUtils.findFirstGenericArgument(childClass, ChildOf.class).orElse(null); } /** * Find data hierarchy parent from concrete Data class. This method is shorthand which gets DataObject class by - * invoking {@link DataObject#getImplementedInterface()} and uses {@link #findHierarchicalParent(Class)}. + * invoking {@link DataObject#implementedInterface()} and uses {@link #findHierarchicalParent(Class)}. * * @param child * Child object for which the parent needs to be located. @@ -105,7 +123,7 @@ public final class BindingReflections { */ public static Class findHierarchicalParent(final DataObject child) { if (child instanceof ChildOf) { - return ClassLoaderUtils.findFirstGenericArgument(child.getImplementedInterface(), ChildOf.class); + return ClassLoaderUtils.findFirstGenericArgument(child.implementedInterface(), ChildOf.class).orElse(null); } return null; } @@ -119,7 +137,7 @@ public final class BindingReflections { * have name. May return null if QName is not present. */ public static QName findQName(final Class dataType) { - return CLASS_TO_QNAME.getUnchecked(dataType).orNull(); + return CLASS_TO_QNAME.getUnchecked(dataType).orElse(null); } /** @@ -136,7 +154,7 @@ public final class BindingReflections { // resolveRpcInputClass() check.While RpcMethodInvoker counts with one argument for // non input type and two arguments for input type, resolveRpcInputClass() counting // with zero for non input and one for input type - && possibleMethod.getParameterTypes().length <= 2; + && possibleMethod.getParameterCount() <= 2; } /** @@ -144,18 +162,18 @@ public final class BindingReflections { * * @param targetMethod * method to scan - * @return Optional.absent() if result type could not be get, or return type is Void. + * @return Optional.empty() if result type could not be get, or return type is Void. */ @SuppressWarnings("rawtypes") public static Optional> resolveRpcOutputClass(final Method targetMethod) { checkState(isRpcMethod(targetMethod), "Supplied method is not a RPC invocation method"); Type futureType = targetMethod.getGenericReturnType(); - Type rpcResultType = ClassLoaderUtils.getFirstGenericParameter(futureType); - Type rpcResultArgument = ClassLoaderUtils.getFirstGenericParameter(rpcResultType); + Type rpcResultType = ClassLoaderUtils.getFirstGenericParameter(futureType).orElse(null); + Type rpcResultArgument = ClassLoaderUtils.getFirstGenericParameter(rpcResultType).orElse(null); if (rpcResultArgument instanceof Class && !Void.class.equals(rpcResultArgument)) { return Optional.of((Class) rpcResultArgument); } - return Optional.absent(); + return Optional.empty(); } /** @@ -163,7 +181,7 @@ public final class BindingReflections { * * @param targetMethod * method to scan - * @return Optional.absent() if RPC has no input, RPC input type otherwise. + * @return Optional.empty() if RPC has no input, RPC input type otherwise. */ @SuppressWarnings("rawtypes") public static Optional> resolveRpcInputClass(final Method targetMethod) { @@ -172,11 +190,13 @@ public final class BindingReflections { return Optional.of(clazz); } } - return Optional.absent(); + return Optional.empty(); } - public static QName getQName(final Class context) { - return findQName(context); + public static @NonNull QName getQName(final Class bindingClass) { + final Optional qname = CLASS_TO_QNAME.getUnchecked(requireNonNull(bindingClass)); + checkState(qname.isPresent(), "Failed to resolve QName of %s", bindingClass); + return qname.get(); } /** @@ -200,7 +220,7 @@ public final class BindingReflections { } /** - * Returns root package name for suplied package. + * Returns root package name for supplied package. * * @param pkg * Package for which find model root package. @@ -227,51 +247,43 @@ public final class BindingReflections { return match.group(0); } - @SuppressWarnings("checkstyle:illegalCatch") public static QNameModule getQNameModule(final Class clz) { if (DataContainer.class.isAssignableFrom(clz) || BaseIdentity.class.isAssignableFrom(clz) || Action.class.isAssignableFrom(clz)) { return findQName(clz).getModule(); } - try { - return BindingReflections.getModuleInfo(clz).getName().getModule(); - } catch (Exception e) { - throw new IllegalStateException("Unable to get QName of defining model.", e); - } - } - /** - * Extract a QNameModule from YangModuleInfo. - * - * @param modInfo Module info - * @return QNameModule for the module - * @throws NullPointerException in modInfo is null - * - * @deprecated Use {@code YangModuleInfo.getName().getModule()} instead. - */ - @Deprecated - public static QNameModule getQNameModule(final YangModuleInfo modInfo) { - return modInfo.getName().getModule(); + return getModuleInfo(clz).getName().getModule(); } /** * Returns instance of {@link YangModuleInfo} of declaring model for specific class. * * @param cls data object class - * @return Instance of {@link YangModuleInfo} associated with model, from - * which this class was derived. + * @return Instance of {@link YangModuleInfo} associated with model, from which this class was derived. */ - public static YangModuleInfo getModuleInfo(final Class cls) throws Exception { - checkArgument(cls != null); - String packageName = getModelRootPackageName(cls.getPackage()); + public static @NonNull YangModuleInfo getModuleInfo(final Class cls) { + final String packageName = getModelRootPackageName(cls.getPackage()); final String potentialClassName = getModuleInfoClassName(packageName); - return ClassLoaderUtils.callWithClassLoader(cls.getClassLoader(), () -> { - Class moduleInfoClass = Thread.currentThread().getContextClassLoader().loadClass(potentialClassName); - return (YangModuleInfo) moduleInfoClass.getMethod("getInstance").invoke(null); - }); + final Class moduleInfoClass; + try { + moduleInfoClass = cls.getClassLoader().loadClass(potentialClassName); + } catch (ClassNotFoundException e) { + throw new IllegalStateException("Failed to load " + potentialClassName, e); + } + + final Object infoInstance; + try { + infoInstance = moduleInfoClass.getMethod("getInstance").invoke(null); + } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { + throw new IllegalStateException("Failed to get instance of " + moduleInfoClass, e); + } + + checkState(infoInstance instanceof YangModuleInfo, "Unexpected instance %s", infoInstance); + return (YangModuleInfo) infoInstance; } - public static String getModuleInfoClassName(final String packageName) { + public static @NonNull String getModuleInfoClassName(final String packageName) { return packageName + "." + BindingMapping.MODULE_INFO_CLASS_NAME; } @@ -297,7 +309,7 @@ public final class BindingReflections { */ public static boolean isNotificationCallback(final Method method) { checkArgument(method != null); - if (method.getName().startsWith("on") && method.getParameterTypes().length == 1) { + if (method.getName().startsWith("on") && method.getParameterCount() == 1) { Class potentialNotification = method.getParameterTypes()[0]; if (isNotification(potentialNotification) && method.getName().equals("on" + potentialNotification.getSimpleName())) { @@ -324,7 +336,7 @@ public final class BindingReflections { * * @return Set of {@link YangModuleInfo} available for current classloader. */ - public static ImmutableSet loadModuleInfos() { + public static @NonNull ImmutableSet loadModuleInfos() { return loadModuleInfos(Thread.currentThread().getContextClassLoader()); } @@ -340,12 +352,13 @@ public final class BindingReflections { * When {@link YangModuleInfo} is available, all dependencies are recursively collected into returning set by * collecting results of {@link YangModuleInfo#getImportedModules()}. * - * @param loader - * Classloader for which {@link YangModuleInfo} should be - * retrieved. + *

+ * Consider using {@link #cacheModuleInfos(ClassLoader)} if the classloader is known to be immutable. + * + * @param loader Classloader for which {@link YangModuleInfo} should be retrieved. * @return Set of {@link YangModuleInfo} available for supplied classloader. */ - public static ImmutableSet loadModuleInfos(final ClassLoader loader) { + public static @NonNull ImmutableSet loadModuleInfos(final ClassLoader loader) { Builder moduleInfoSet = ImmutableSet.builder(); ServiceLoader serviceLoader = ServiceLoader.load(YangModelBindingProvider.class, loader); @@ -357,6 +370,27 @@ public final class BindingReflections { return moduleInfoSet.build(); } + /** + * Loads {@link YangModuleInfo} instances available on supplied {@link ClassLoader}, assuming the set of available + * information does not change. Subsequent accesses may return cached values. + * + *

+ * {@link YangModuleInfo} are discovered using {@link ServiceLoader} for {@link YangModelBindingProvider}. + * {@link YangModelBindingProvider} are simple classes which holds only pointers to actual instance + * {@link YangModuleInfo}. + * + *

+ * When {@link YangModuleInfo} is available, all dependencies are recursively collected into returning set by + * collecting results of {@link YangModuleInfo#getImportedModules()}. + * + * @param loader Class loader for which {@link YangModuleInfo} should be retrieved. + * @return Set of {@link YangModuleInfo} available for supplied classloader. + */ + @Beta + public static @NonNull ImmutableSet cacheModuleInfos(final ClassLoader loader) { + return MODULE_INFO_CACHE.getUnchecked(loader); + } + private static void collectYangModuleInfo(final YangModuleInfo moduleInfo, final Builder moduleInfoSet) { moduleInfoSet.add(moduleInfo); @@ -392,7 +426,8 @@ public final class BindingReflections { checkArgument(DataContainer.class.isAssignableFrom(type), "Supplied type must be derived from DataContainer"); List> ret = new LinkedList<>(); for (Method method : type.getMethods()) { - Optional> entity = getYangModeledReturnType(method); + Optional> entity = getYangModeledReturnType(method, + BindingMapping.GETTER_PREFIX); if (entity.isPresent()) { ret.add((Class) entity.get()); } @@ -403,17 +438,26 @@ public final class BindingReflections { /** * Scans supplied class and returns an iterable of all data children classes. * - * @param type - * YANG Modeled Entity derived from DataContainer + * @param type YANG Modeled Entity derived from DataContainer * @return Iterable of all data children, which have YANG modeled entity */ - public static Map, Method> getChildrenClassToMethod(final Class type) { + public static Map, Method> getChildrenClassToMethod(final Class type) { + return getChildClassToMethod(type, BindingMapping.GETTER_PREFIX); + } + + @Beta + public static Map, Method> getChildrenClassToNonnullMethod(final Class type) { + return getChildClassToMethod(type, BindingMapping.NONNULL_PREFIX); + } + + private static Map, Method> getChildClassToMethod(final Class type, + final String prefix) { checkArgument(type != null, "Target type must not be null"); checkArgument(DataContainer.class.isAssignableFrom(type), "Supplied type %s must be derived from DataContainer", type); - Map, Method> ret = new HashMap<>(); + Map, Method> ret = new HashMap<>(); for (Method method : type.getMethods()) { - Optional> entity = getYangModeledReturnType(method); + Optional> entity = getYangModeledReturnType(method, prefix); if (entity.isPresent()) { ret.put(entity.get(), method); } @@ -421,36 +465,58 @@ public final class BindingReflections { return ret; } - @SuppressWarnings({ "unchecked", "rawtypes", "checkstyle:illegalCatch" }) - private static Optional> getYangModeledReturnType(final Method method) { - if ("getClass".equals(method.getName()) || !method.getName().startsWith("get") - || method.getParameterTypes().length > 0) { - return Optional.absent(); + private static Optional> getYangModeledReturnType(final Method method, + final String prefix) { + final String methodName = method.getName(); + if ("getClass".equals(methodName) || !methodName.startsWith(prefix) || method.getParameterCount() > 0) { + return Optional.empty(); } - Class returnType = method.getReturnType(); + final Class returnType = method.getReturnType(); if (DataContainer.class.isAssignableFrom(returnType)) { - return Optional.of(returnType); + return optionalDataContainer(returnType); } else if (List.class.isAssignableFrom(returnType)) { - try { - return ClassLoaderUtils.callWithClassLoader(method.getDeclaringClass().getClassLoader(), () -> { - Type listResult = ClassLoaderUtils.getFirstGenericParameter(method.getGenericReturnType()); - if (listResult instanceof Class - && DataContainer.class.isAssignableFrom((Class) listResult)) { - return Optional.of((Class) listResult); - } - return Optional.absent(); - }); - } catch (Exception e) { - /* - * It is safe to log this this exception on debug, since this - * method should not fail. Only failures are possible if the - * runtime / backing. - */ - LOG.debug("Unable to find YANG modeled return type for {}", method, e); + return getYangModeledReturnType(method, 0); + } else if (Map.class.isAssignableFrom(returnType)) { + return getYangModeledReturnType(method, 1); + } + return Optional.empty(); + } + + @SuppressWarnings("checkstyle:illegalCatch") + private static Optional> getYangModeledReturnType(final Method method, + final int parameterOffset) { + try { + return ClassLoaderUtils.callWithClassLoader(method.getDeclaringClass().getClassLoader(), + () -> genericParameter(method.getGenericReturnType(), parameterOffset) + .flatMap(result -> result instanceof Class ? optionalCast((Class) result) : Optional.empty())); + } catch (Exception e) { + /* + * It is safe to log this this exception on debug, since this + * method should not fail. Only failures are possible if the + * runtime / backing. + */ + LOG.debug("Unable to find YANG modeled return type for {}", method, e); + } + return Optional.empty(); + } + + private static Optional> optionalCast(final Class type) { + return DataContainer.class.isAssignableFrom(type) ? optionalDataContainer(type) : Optional.empty(); + } + + private static Optional> optionalDataContainer(final Class type) { + return Optional.of(type.asSubclass(DataContainer.class)); + } + + private static Optional genericParameter(final Type type, final int offset) { + if (type instanceof ParameterizedType) { + final Type[] parameters = ((ParameterizedType) type).getActualTypeArguments(); + if (parameters.length > offset) { + return Optional.of(parameters[offset]); } } - return Optional.absent(); + return Optional.empty(); } private static class ClassToQNameLoader extends CacheLoader, Optional> { @@ -487,7 +553,7 @@ public final class BindingReflections { */ LOG.debug("Unexpected exception during extracting QName for {}", key, e); } - return Optional.absent(); + return Optional.empty(); } /** @@ -510,18 +576,11 @@ public final class BindingReflections { * @throws IllegalArgumentException If supplied class was not derived from YANG model. */ // FIXME: Extend this algorithm to also provide QName for YANG modeled simple types. - @SuppressWarnings({ "rawtypes", "unchecked", "checkstyle:illegalCatch" }) + @SuppressWarnings({ "rawtypes", "unchecked" }) private static QName computeQName(final Class key) { checkArgument(isBindingClass(key), "Supplied class %s is not derived from YANG.", key); - YangModuleInfo moduleInfo; - try { - moduleInfo = getModuleInfo(key); - } catch (Exception e) { - throw new IllegalStateException("Unable to get QName for " + key + ". YangModuleInfo was not found.", - e); - } - final QName module = moduleInfo.getName(); + final QName module = getModuleInfo(key).getName(); if (Augmentation.class.isAssignableFrom(key)) { return module; } else if (isRpcType(key)) { @@ -540,34 +599,6 @@ public final class BindingReflections { } } - /** - * Given a {@link YangModuleInfo}, create a QName representing it. The QName is formed by reusing the module's - * namespace and revision using the module's name as the QName's local name. - * - * @param moduleInfo - * module information - * @return QName representing the module - * - * @deprecated Use {@link YangModuleInfo#getName()} instead. - */ - @Deprecated - public static QName getModuleQName(final YangModuleInfo moduleInfo) { - checkArgument(moduleInfo != null, "moduleInfo must not be null."); - return moduleInfo.getName(); - } - - /** - * Extracts augmentation from Binding DTO field using reflection. - * - * @param input - * Instance of DataObject which is augmentable and may contain - * augmentation - * @return Map of augmentations if read was successful, otherwise empty map. - */ - public static Map>, Augmentation> getAugmentations(final Augmentable input) { - return AugmentationFieldGetter.getGetter(input.getClass()).getAugmentations(input); - } - /** * Determines if two augmentation classes or case classes represents same * data. @@ -598,8 +629,8 @@ public final class BindingReflections { */ @SuppressWarnings({ "rawtypes", "unchecked" }) public static boolean isSubstitutionFor(final Class potential, final Class target) { - HashSet subImplemented = Sets.newHashSet(potential.getInterfaces()); - HashSet targetImplemented = Sets.newHashSet(target.getInterfaces()); + Set subImplemented = new HashSet<>(Arrays.asList(potential.getInterfaces())); + Set targetImplemented = new HashSet<>(Arrays.asList(target.getInterfaces())); if (!subImplemented.equals(targetImplemented)) { return false; } @@ -609,6 +640,11 @@ public final class BindingReflections { return false; } for (Method potentialMethod : potential.getMethods()) { + if (Modifier.isStatic(potentialMethod.getModifiers())) { + // Skip any static methods, as we are not interested in those + continue; + } + try { Method targetMethod = target.getMethod(potentialMethod.getName(), potentialMethod.getParameterTypes()); if (!potentialMethod.getReturnType().equals(targetMethod.getReturnType())) {