X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=blobdiff_plain;f=yang%2Fyang-binding%2Fsrc%2Fmain%2Fjava%2Forg%2Fopendaylight%2Fyangtools%2Fyang%2Fbinding%2Futil%2FBindingReflections.java;h=bc7474a9f2cb765796063f07e8c9cd65dc2b5e96;hb=4302f95307c27902956e838f52d06bf97e879639;hp=0b057a194bc86b1d79a8218b67e820eb88dee819;hpb=28a1f6957cdf8eb0bbe2e205d0f138996e951e15;p=yangtools.git diff --git a/yang/yang-binding/src/main/java/org/opendaylight/yangtools/yang/binding/util/BindingReflections.java b/yang/yang-binding/src/main/java/org/opendaylight/yangtools/yang/binding/util/BindingReflections.java index 0b057a194b..bc7474a9f2 100644 --- a/yang/yang-binding/src/main/java/org/opendaylight/yangtools/yang/binding/util/BindingReflections.java +++ b/yang/yang-binding/src/main/java/org/opendaylight/yangtools/yang/binding/util/BindingReflections.java @@ -10,12 +10,24 @@ package org.opendaylight.yangtools.yang.binding.util; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; +import com.google.common.base.Optional; +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 java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Type; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; import java.util.LinkedList; import java.util.List; +import java.util.Map; import java.util.ServiceLoader; import java.util.concurrent.Callable; import java.util.concurrent.Future; @@ -38,13 +50,6 @@ import org.opendaylight.yangtools.yang.common.QName; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.google.common.base.Optional; -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; - public class BindingReflections { private static final long EXPIRATION_TIME = 60; @@ -58,12 +63,12 @@ public class BindingReflections { .build(new ClassToQNameLoader()); + private BindingReflections() { throw new UnsupportedOperationException("Utility class."); } /** - * * Find augmentation target class from concrete Augmentation class * * This method uses first generic argument of @@ -395,6 +400,27 @@ public class BindingReflections { return ret; } + /** + * + * Scans supplied class and returns an iterable of all data children classes. + * + * @param type YANG Modeled Entity derived from DataContainer + * @return Iterable of all data children, which have YANG modeled entity + */ + @SuppressWarnings("unchecked") + public static Map,Method> getChildrenClassToMethod(final Class type) { + checkArgument(type != null, "Target type must not be null"); + checkArgument(DataContainer.class.isAssignableFrom(type), "Supplied type must be derived from DataContainer"); + Map,Method> ret = new HashMap<>(); + for (Method method : type.getMethods()) { + Optional> entity = getYangModeledReturnType(method); + if (entity.isPresent()) { + ret.put(entity.get(),method); + } + } + return ret; + } + @SuppressWarnings("unchecked") private static Optional> getYangModeledReturnType(final Method method) { if (method.getName().equals("getClass") || !method.getName().startsWith("get") @@ -441,29 +467,168 @@ public class BindingReflections { @Override public Optional load(final Class key) throws Exception { + return resolveQNameNoCache(key); + } + } + + /** + * + * Tries to resolve QName for supplied class. + * + * Looks up for static field with name from constant {@link BindingMapping#QNAME_STATIC_FIELD_NAME} + * and returns value if present. + * + * If field is not present uses {@link #computeQName(Class)} to compute QName for missing types. + * + * @param key + * @return + */ + private static Optional resolveQNameNoCache(final Class key) { + try { + Field field = key.getField(BindingMapping.QNAME_STATIC_FIELD_NAME); + Object obj = field.get(null); + if (obj instanceof QName) { + return Optional.of((QName) obj); + } + + } catch (NoSuchFieldException e) { + return Optional.of(computeQName(key)); + + } catch (SecurityException | IllegalArgumentException | IllegalAccessException 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("Unexpected exception during extracting QName for {}",key,e); + } + return Optional.absent(); + } + + /** + * Computes QName for supplied class + * + * Namespace and revision are same as {@link YangModuleInfo} + * associated with supplied class. + *

+ * If class is + *

    + *
  • rpc input: local name is "input". + *
  • rpc output: local name is "output". + *
  • augmentation: local name is "module name". + *
+ * + * There is also fallback, if it is not possible to compute QName + * using following algorithm returns module QName. + * + * FIXME: Extend this algorithm to also provide QName for YANG modeled simple types. + * + * @throws IllegalStateException If YangModuleInfo could not be resolved + * @throws IllegalArgumentException If supplied class was not derived from YANG model. + * + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + private static QName computeQName(final Class key) { + if(isBindingClass(key)) { + YangModuleInfo moduleInfo; try { - Field field = key.getField(BindingMapping.QNAME_STATIC_FIELD_NAME); - Object obj = field.get(null); - if (obj instanceof QName) { - return Optional.of((QName) obj); + moduleInfo = getModuleInfo(key); + } catch (Exception e) { + throw new IllegalStateException("Unable to get QName for " + key + ". YangModuleInfo was not found.",e); + } + final QName module = getModuleQName(moduleInfo); + if (Augmentation.class.isAssignableFrom(key)) { + return module; + } else if(isRpcType(key)) { + final String className = key.getSimpleName(); + if(className.endsWith(BindingMapping.RPC_OUTPUT_SUFFIX)) { + return QName.create(module,"output"); + } else { + return QName.create(module,"input"); } - } catch (NoSuchFieldException e) { - if (Augmentation.class.isAssignableFrom(key)) { - YangModuleInfo moduleInfo = getModuleInfo(key); - return Optional.of(QName.create(moduleInfo.getNamespace(), moduleInfo.getRevision(), - moduleInfo.getName())); + } + /* + * Fallback for Binding types which fo not have QNAME + * field + */ + return module; + } else { + throw new IllegalArgumentException("Supplied class "+key+"is not derived from YANG."); + } + } + + /** + * 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 + */ + public static QName getModuleQName(final YangModuleInfo moduleInfo) { + checkArgument(moduleInfo != null, "moduleInfo must not be null."); + return QName.create(moduleInfo.getNamespace(), moduleInfo.getRevision(), + 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. + *

+ * Two augmentations or cases could be substituted only if and if: + *

    + *
  • Both implements same interfaces
  • + *
  • Both have same children
  • + *
  • If augmentations: Both have same augmentation target class. Target class was generated for data node in grouping.
  • + *
  • If cases: Both are from same choice. Choice class was generated for data node in grouping.
  • + *
+ *

+ * Explanation: Binding Specification reuses classes generated for groupings as part of normal data tree, + * this classes from grouping could be used at various locations and user may not be aware of it + * and may use incorrect case or augmentation in particular subtree (via copy constructors, etc). + * + * @param potential Class which is potential substition + * @param target Class which should be used at particular subtree + * @return true if and only if classes represents same data. + */ + @SuppressWarnings({"rawtypes","unchecked"}) + public static boolean isSubstitutionFor(Class potential, Class target) { + HashSet subImplemented = Sets.newHashSet(potential.getInterfaces()); + HashSet targetImplemented = Sets.newHashSet(target.getInterfaces()); + if(!subImplemented.equals(targetImplemented)) { + return false; + } + if(Augmentation.class.isAssignableFrom(potential) + && !BindingReflections.findAugmentationTarget(potential).equals(BindingReflections.findAugmentationTarget(target))) { + return false; + } + for(Method potentialMethod : potential.getMethods()) { + try { + Method targetMethod = target.getMethod(potentialMethod.getName(), potentialMethod.getParameterTypes()); + if(!potentialMethod.getReturnType().equals(targetMethod.getReturnType())) { + return false; } - } catch (SecurityException | IllegalArgumentException | IllegalAccessException 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("Unexpected exception during extracting QName for {}",key,e); + } catch (NoSuchMethodException e) { + // Counterpart method is missing, so classes could not be substituted. + return false; + } catch (SecurityException e) { + throw new IllegalStateException("Could not compare methods",e); } - return Optional.absent(); } + return true; } }