Binding v2 runtime context 30/58330/3
authorJakub Toth <jakub.toth@pantheon.tech>
Thu, 1 Jun 2017 06:10:08 +0000 (08:10 +0200)
committerMartin Ciglan <martin.ciglan@pantheon.tech>
Tue, 6 Jun 2017 20:47:42 +0000 (20:47 +0000)
  *preparing of binding runtime context via reflection
  *tests

Change-Id: I02a3e568942fe4f31621299c12bf978bcaa4940b
Signed-off-by: Jakub Toth <jakub.toth@pantheon.tech>
(cherry picked from commit 319b4d682c190284488cad35e6ff58746d5fde9a)

binding2/mdsal-binding2-generator-impl/src/main/java/org/opendaylight/mdsal/binding/javav2/generator/impl/BindingGeneratorImpl.java
binding2/mdsal-binding2-generator-impl/src/main/java/org/opendaylight/mdsal/binding/javav2/generator/impl/ModuleContext.java
binding2/mdsal-binding2-generator-impl/src/main/java/org/opendaylight/mdsal/binding/javav2/generator/impl/util/BindingRuntimeContext.java [new file with mode: 0644]
binding2/mdsal-binding2-generator-impl/src/main/java/org/opendaylight/mdsal/binding/javav2/generator/impl/util/BindingSchemaContextUtils.java [new file with mode: 0644]
binding2/mdsal-binding2-spec/src/main/java/org/opendaylight/mdsal/binding/javav2/spec/util/AugmentationFieldGetter.java [new file with mode: 0644]
binding2/mdsal-binding2-spec/src/main/java/org/opendaylight/mdsal/binding/javav2/spec/util/BindingReflections.java [new file with mode: 0644]
binding2/mdsal-binding2-spec/test/main/java/org/opendaylight/mdsal/binding/javav2/spec/util/AugmentationFieldGetterTest.java [new file with mode: 0644]
binding2/mdsal-binding2-spec/test/main/java/org/opendaylight/mdsal/binding/javav2/spec/util/BindingReflectionsTest.java [new file with mode: 0644]
binding2/mdsal-binding2-spec/test/main/java/org/opendaylight/mdsal/binding/javav2/spec/util/test/mock/FooChild.java [new file with mode: 0644]
binding2/mdsal-binding2-spec/test/main/java/org/opendaylight/mdsal/binding/javav2/spec/util/test/mock/GroupingFoo.java [new file with mode: 0644]
binding2/mdsal-binding2-util/src/main/java/org/opendaylight/mdsal/binding/javav2/util/BindingMapping.java

index fc5a2026889b6b8d5e9f27fffa7980a64e36bea8..ba89513fd8bde69ff5cda62cf0a5102dd4664868 100644 (file)
@@ -75,7 +75,7 @@ public class BindingGeneratorImpl implements BindingGenerator {
      * @throws IllegalStateException if <code>context</code> contain no modules
      */
     @Override
-    public List<Type> generateTypes(SchemaContext context) {
+    public List<Type> generateTypes(final SchemaContext context) {
         Preconditions.checkArgument(context != null, "Schema Context reference cannot be NULL.");
         Preconditions.checkState(context.getModules() != null, "Schema Context does not contain defined modules.");
         final Set<Module> modules = context.getModules();
@@ -111,32 +111,32 @@ public class BindingGeneratorImpl implements BindingGenerator {
      *             if <code>context</code> contain no modules
      */
     @Override
-    public List<Type> generateTypes(SchemaContext context, Set<Module> modules) {
+    public List<Type> generateTypes(final SchemaContext context, final Set<Module> modules) {
         Preconditions.checkArgument(context != null, "Schema Context reference cannot be NULL.");
         Preconditions.checkState(context.getModules() != null, "Schema Context does not contain defined modules.");
         Preconditions.checkArgument(modules != null, "Set of Modules cannot be NULL.");
 
-        typeProvider = new TypeProviderImpl(context);
+        this.typeProvider = new TypeProviderImpl(context);
         final Module[] modulesArray = new Module[context.getModules().size()];
         context.getModules().toArray(modulesArray);
         final List<Module> contextModules = ModuleDependencySort.sort(modulesArray);
-        genTypeBuilders = new HashMap<>();
+        this.genTypeBuilders = new HashMap<>();
 
         for (final Module contextModule : contextModules) {
-            genCtx = ModuleToGenType.generate(contextModule, genTypeBuilders, context, typeProvider,
-                    genCtx, verboseClassComments);
+            this.genCtx = ModuleToGenType.generate(contextModule, this.genTypeBuilders, context, this.typeProvider,
+                    this.genCtx, this.verboseClassComments);
         }
         for (final Module contextModule : contextModules) {
-            genCtx = AugmentToGenType.generate(contextModule, context, typeProvider, genCtx,
-                    genTypeBuilders, verboseClassComments);
+            this.genCtx = AugmentToGenType.generate(contextModule, context, this.typeProvider, this.genCtx,
+                    this.genTypeBuilders, this.verboseClassComments);
         }
 
         final List<Type> filteredGenTypes = new ArrayList<>();
         for (final Module m : modules) {
-            final ModuleContext ctx = Preconditions.checkNotNull(genCtx.get(m),
+            final ModuleContext ctx = Preconditions.checkNotNull(this.genCtx.get(m),
                     "Module context not found for module %s", m);
             filteredGenTypes.addAll(ctx.getGeneratedTypes());
-            final Set<Type> additionalTypes = ((TypeProviderImpl) typeProvider).getAdditionalTypes().get(m);
+            final Set<Type> additionalTypes = ((TypeProviderImpl) this.typeProvider).getAdditionalTypes().get(m);
             if (additionalTypes != null) {
                 filteredGenTypes.addAll(additionalTypes);
             }
@@ -144,4 +144,16 @@ public class BindingGeneratorImpl implements BindingGenerator {
 
         return filteredGenTypes;
     }
+
+    /**
+     * Return module contexts from generated types according to context.
+     *
+     * @param schemaContext
+     *            - for generating types
+     * @return module contexts
+     */
+    public Map<Module, ModuleContext> getModuleContexts(final SchemaContext schemaContext) {
+        generateTypes(schemaContext);
+        return this.genCtx;
+    }
 }
index 58cd2b08553c54c270e46e81d8e437650fbff792..4a80b28c80db90a6bcf410617319bad0aa9842f1 100644 (file)
@@ -39,7 +39,7 @@ import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
  * This class holds information about generated entities in context of YANG module
  */
 @Beta
-final class ModuleContext {
+public final class ModuleContext {
     private GeneratedTypeBuilder moduleNode;
     private final List<GeneratedTOBuilder> genTOs = new ArrayList<>();
     private final Map<SchemaPath, Type> typedefs = new HashMap<>();
@@ -56,42 +56,42 @@ final class ModuleContext {
     private final Map<SchemaPath, Type> innerTypes = new HashMap<>();
 
     List<Type> getGeneratedTypes() {
-        List<Type> result = new ArrayList<>();
+        final List<Type> result = new ArrayList<>();
 
-        if (moduleNode != null) {
-            result.add(moduleNode.toInstance());
+        if (this.moduleNode != null) {
+            result.add(this.moduleNode.toInstance());
         }
 
-        result.addAll(genTOs.stream().map(GeneratedTOBuilder::toInstance).collect(Collectors.toList()));
-        result.addAll(typedefs.values().stream().filter(b -> b != null).collect(Collectors.toList()));
-        result.addAll(childNodes.values().stream().map(GeneratedTypeBuilder::toInstance).collect(Collectors.toList()));
-        result.addAll(groupings.values().stream().map(GeneratedTypeBuilder::toInstance).collect(Collectors.toList()));
-        result.addAll(cases.values().stream().map(GeneratedTypeBuilder::toInstance).collect(Collectors.toList()));
-        result.addAll(identities.values().stream().map(GeneratedTOBuilder::toInstance).collect(Collectors.toList()));
-        result.addAll(topLevelNodes.stream().map(GeneratedTypeBuilder::toInstance).collect(Collectors.toList()));
-        result.addAll(augmentations.stream().map(GeneratedTypeBuilder::toInstance).collect(Collectors.toList()));
+        result.addAll(this.genTOs.stream().map(GeneratedTOBuilder::toInstance).collect(Collectors.toList()));
+        result.addAll(this.typedefs.values().stream().filter(b -> b != null).collect(Collectors.toList()));
+        result.addAll(this.childNodes.values().stream().map(GeneratedTypeBuilder::toInstance).collect(Collectors.toList()));
+        result.addAll(this.groupings.values().stream().map(GeneratedTypeBuilder::toInstance).collect(Collectors.toList()));
+        result.addAll(this.cases.values().stream().map(GeneratedTypeBuilder::toInstance).collect(Collectors.toList()));
+        result.addAll(this.identities.values().stream().map(GeneratedTOBuilder::toInstance).collect(Collectors.toList()));
+        result.addAll(this.topLevelNodes.stream().map(GeneratedTypeBuilder::toInstance).collect(Collectors.toList()));
+        result.addAll(this.augmentations.stream().map(GeneratedTypeBuilder::toInstance).collect(Collectors.toList()));
 
         return ImmutableList.copyOf(result);
     }
 
     public Multimap<Type, Type> getChoiceToCases() {
-        return Multimaps.unmodifiableMultimap(choiceToCases);
+        return Multimaps.unmodifiableMultimap(this.choiceToCases);
     }
 
     public GeneratedTypeBuilder getModuleNode() {
-        return moduleNode;
+        return this.moduleNode;
     }
 
     public GeneratedTypeBuilder getChildNode(final SchemaPath p) {
-        return childNodes.get(p);
+        return this.childNodes.get(p);
     }
 
     public GeneratedTypeBuilder getGrouping(final SchemaPath p) {
-        return groupings.get(p);
+        return this.groupings.get(p);
     }
 
     public GeneratedTypeBuilder getCase(final SchemaPath p) {
-        return cases.get(p);
+        return this.cases.get(p);
     }
 
     public void addModuleNode(final GeneratedTypeBuilder moduleNode) {
@@ -99,83 +99,83 @@ final class ModuleContext {
     }
 
     public void addGeneratedTOBuilder(final GeneratedTOBuilder b) {
-        genTOs.add(b);
+        this.genTOs.add(b);
     }
 
     public void addChildNodeType(final SchemaNode p, final GeneratedTypeBuilder b) {
-        childNodes.put(p.getPath(), b);
-        typeToSchema.put(b,p);
+        this.childNodes.put(p.getPath(), b);
+        this.typeToSchema.put(b,p);
     }
 
     public void addGroupingType(final SchemaPath p, final GeneratedTypeBuilder b) {
-        groupings.put(p, b);
+        this.groupings.put(p, b);
     }
 
     public void addTypedefType(final SchemaPath p, final Type t) {
-        typedefs.put(p, t);
+        this.typedefs.put(p, t);
     }
 
     public void addCaseType(final SchemaPath p, final GeneratedTypeBuilder b) {
-        cases.put(p, b);
+        this.cases.put(p, b);
     }
 
     public void addIdentityType(final QName name,final GeneratedTOBuilder b) {
-        identities.put(name,b);
+        this.identities.put(name,b);
     }
 
     public void addTopLevelNodeType(final GeneratedTypeBuilder b) {
-        topLevelNodes.add(b);
+        this.topLevelNodes.add(b);
     }
 
     public void addAugmentType(final GeneratedTypeBuilder b) {
-        augmentations.add(b);
+        this.augmentations.add(b);
     }
 
     public Map<SchemaPath, Type> getTypedefs() {
-        return typedefs;
+        return this.typedefs;
     }
 
     public Map<SchemaPath, GeneratedTypeBuilder> getChildNodes() {
-        return Collections.unmodifiableMap(childNodes);
+        return Collections.unmodifiableMap(this.childNodes);
     }
 
     public Map<SchemaPath, GeneratedTypeBuilder> getGroupings() {
-        return Collections.unmodifiableMap(groupings);
+        return Collections.unmodifiableMap(this.groupings);
     }
 
     public Map<SchemaPath, GeneratedTypeBuilder> getCases() {
-        return Collections.unmodifiableMap(cases);
+        return Collections.unmodifiableMap(this.cases);
     }
 
     public Map<QName,GeneratedTOBuilder> getIdentities() {
-        return Collections.unmodifiableMap(identities);
+        return Collections.unmodifiableMap(this.identities);
     }
 
     public Set<GeneratedTypeBuilder> getTopLevelNodes() {
-        return Collections.unmodifiableSet(topLevelNodes);
+        return Collections.unmodifiableSet(this.topLevelNodes);
     }
 
     public List<GeneratedTypeBuilder> getAugmentations() {
-        return Collections.unmodifiableList(augmentations);
+        return Collections.unmodifiableList(this.augmentations);
     }
 
     public BiMap<Type, AugmentationSchema> getTypeToAugmentation() {
-        return Maps.unmodifiableBiMap(typeToAugmentation);
+        return Maps.unmodifiableBiMap(this.typeToAugmentation);
     }
 
     public void addTypeToAugmentation(final GeneratedTypeBuilder builder, final AugmentationSchema schema) {
-        typeToAugmentation.put(builder, schema);
-        typeToSchema.put(builder, schema);
+        this.typeToAugmentation.put(builder, schema);
+        this.typeToSchema.put(builder, schema);
     }
 
     public void addChoiceToCaseMapping(final Type choiceType, final Type caseType, final ChoiceCaseNode schema) {
-        choiceToCases.put(choiceType, caseType);
-        caseTypeToSchema.put(caseType, schema);
-        typeToSchema.put(caseType, schema);
+        this.choiceToCases.put(choiceType, caseType);
+        this.caseTypeToSchema.put(caseType, schema);
+        this.typeToSchema.put(caseType, schema);
     }
 
     public BiMap<Type, ChoiceCaseNode> getCaseTypeToSchemas() {
-        return Maps.unmodifiableBiMap(caseTypeToSchema);
+        return Maps.unmodifiableBiMap(this.caseTypeToSchema);
     }
 
     /**
@@ -187,11 +187,11 @@ final class ModuleContext {
      * @return Mapping from type to corresponding schema
      */
     public Map<Type, Object> getTypeToSchema() {
-        return Collections.unmodifiableMap(typeToSchema);
+        return Collections.unmodifiableMap(this.typeToSchema);
     }
 
-    protected void addTypeToSchema(Type type, TypeDefinition<?> typedef) {
-        typeToSchema.put(type, typedef);
+    protected void addTypeToSchema(final Type type, final TypeDefinition<?> typedef) {
+        this.typeToSchema.put(type, typedef);
     }
 
     /**
@@ -200,12 +200,12 @@ final class ModuleContext {
      * @param path
      * @param enumBuilder
      */
-    void addInnerTypedefType(SchemaPath path, EnumBuilder enumBuilder) {
-        innerTypes.put(path, enumBuilder);
+    void addInnerTypedefType(final SchemaPath path, final EnumBuilder enumBuilder) {
+        this.innerTypes.put(path, enumBuilder);
     }
 
-    public Type getInnerType(SchemaPath path) {
-        return innerTypes.get(path);
+    public Type getInnerType(final SchemaPath path) {
+        return this.innerTypes.get(path);
     }
 
 }
diff --git a/binding2/mdsal-binding2-generator-impl/src/main/java/org/opendaylight/mdsal/binding/javav2/generator/impl/util/BindingRuntimeContext.java b/binding2/mdsal-binding2-generator-impl/src/main/java/org/opendaylight/mdsal/binding/javav2/generator/impl/util/BindingRuntimeContext.java
new file mode 100644 (file)
index 0000000..7174b94
--- /dev/null
@@ -0,0 +1,475 @@
+/*
+ * Copyright (c) 2017 Pantheon Technologies s.r.o. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.mdsal.binding.javav2.generator.impl.util;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import com.google.common.collect.BiMap;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.HashBiMap;
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Multimap;
+import java.util.AbstractMap.SimpleEntry;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import javax.annotation.Nullable;
+import org.opendaylight.mdsal.binding.javav2.generator.api.ClassLoadingStrategy;
+import org.opendaylight.mdsal.binding.javav2.generator.impl.BindingGeneratorImpl;
+import org.opendaylight.mdsal.binding.javav2.generator.impl.ModuleContext;
+import org.opendaylight.mdsal.binding.javav2.generator.util.JavaIdentifier;
+import org.opendaylight.mdsal.binding.javav2.generator.util.JavaIdentifierNormalizer;
+import org.opendaylight.mdsal.binding.javav2.generator.util.ReferencedTypeImpl;
+import org.opendaylight.mdsal.binding.javav2.model.api.GeneratedType;
+import org.opendaylight.mdsal.binding.javav2.model.api.MethodSignature;
+import org.opendaylight.mdsal.binding.javav2.model.api.ParameterizedType;
+import org.opendaylight.mdsal.binding.javav2.model.api.Type;
+import org.opendaylight.mdsal.binding.javav2.model.api.type.builder.GeneratedTypeBuilder;
+import org.opendaylight.mdsal.binding.javav2.spec.structural.Augmentation;
+import org.opendaylight.mdsal.binding.javav2.spec.util.BindingReflections;
+import org.opendaylight.yangtools.concepts.Immutable;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
+import org.opendaylight.yangtools.yang.model.api.AugmentationSchema;
+import org.opendaylight.yangtools.yang.model.api.AugmentationTarget;
+import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode;
+import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.Module;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.model.api.SchemaNode;
+import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
+import org.opendaylight.yangtools.yang.model.api.type.EnumTypeDefinition;
+import org.opendaylight.yangtools.yang.model.util.EffectiveAugmentationSchema;
+import org.opendaylight.yangtools.yang.model.util.SchemaNodeUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Runtime Context for Java YANG Binding classes
+ *
+ * <p>
+ * Runtime Context provides additional insight in Java YANG Binding, binding
+ * classes and underlying YANG schema, it contains runtime information, which
+ * could not be derived from generated classes alone using
+ * {@link BindingReflections}.
+ * <p>
+ * Some of this information are for example list of all available children for
+ * cases {@link #getChoiceCaseChildren(DataNodeContainer)}, since choices are
+ * augmentable and new choices may be introduced by additional models.
+ * <p>
+ * Same goes for all possible augmentations.
+ *
+ */
+@Beta
+public class BindingRuntimeContext implements Immutable {
+    private static final Logger LOG = LoggerFactory.getLogger(BindingRuntimeContext.class);
+    private static final char DOT = '.';
+    private final ClassLoadingStrategy strategy;
+    private final SchemaContext schemaContext;
+
+    private final Map<Type, AugmentationSchema> augmentationToSchema = new HashMap<>();
+    private final BiMap<Type, Object> typeToDefiningSchema = HashBiMap.create();
+    private final Multimap<Type, Type> choiceToCases = HashMultimap.create();
+    private final Map<QName, Type> identities = new HashMap<>();
+
+    private final LoadingCache<QName, Class<?>> identityClasses = CacheBuilder.newBuilder().weakValues().build(
+        new CacheLoader<QName, Class<?>>() {
+            @Override
+            public Class<?> load(final QName key) {
+                final Type identityType = BindingRuntimeContext.this.identities.get(key);
+                Preconditions.checkArgument(identityType != null, "Supplied QName %s is not a valid identity", key);
+                try {
+                    return BindingRuntimeContext.this.strategy.loadClass(identityType);
+                } catch (final ClassNotFoundException e) {
+                    throw new IllegalArgumentException("Required class " + identityType + "was not found.", e);
+                }
+            }
+        });
+
+    private BindingRuntimeContext(final ClassLoadingStrategy strategy, final SchemaContext schema) {
+        this.strategy = strategy;
+        this.schemaContext = schema;
+
+        final BindingGeneratorImpl generator = new BindingGeneratorImpl(false);
+        final Map<Module, ModuleContext> modules = generator.getModuleContexts(this.schemaContext);
+
+        for (final ModuleContext ctx : modules.values()) {
+            this.augmentationToSchema.putAll(ctx.getTypeToAugmentation());
+            this.typeToDefiningSchema.putAll(ctx.getTypeToSchema());
+
+            ctx.getTypedefs();
+            this.choiceToCases.putAll(ctx.getChoiceToCases());
+            this.identities.putAll(ctx.getIdentities());
+        }
+    }
+
+    /**
+     * Creates Binding Runtime Context from supplied class loading strategy and
+     * schema context.
+     *
+     * @param strategy
+     *            - class loading strategy to retrieve generated Binding classes
+     * @param ctx
+     *            - schema context which describes YANG model and to which
+     *            Binding classes should be mapped
+     * @return Instance of BindingRuntimeContext for supplied schema context.
+     */
+    public static final BindingRuntimeContext create(final ClassLoadingStrategy strategy, final SchemaContext ctx) {
+        return new BindingRuntimeContext(strategy, ctx);
+    }
+
+    /**
+     * Returns a class loading strategy associated with this binding runtime context
+     * which is used to load classes.
+     *
+     * @return Class loading strategy.
+     */
+    public ClassLoadingStrategy getStrategy() {
+        return this.strategy;
+    }
+
+    /**
+     * Returns an stable immutable view of schema context associated with this Binding runtime context.
+     *
+     * @return stable view of schema context
+     */
+    public SchemaContext getSchemaContext() {
+        return this.schemaContext;
+    }
+
+    /**
+     * Returns schema of augmentation
+     * <p>
+     * Returned schema is schema definition from which augmentation class was
+     * generated. This schema is isolated from other augmentations. This means
+     * it contains augmentation definition as was present in original YANG
+     * module.
+     * <p>
+     * Children of returned schema does not contain any additional
+     * augmentations, which may be present in runtime for them, thus returned
+     * schema is unsuitable for use for validation of data.
+     * <p>
+     * For retrieving {@link AugmentationSchema}, which will contains full model
+     * for child nodes, you should use method
+     * {@link #getResolvedAugmentationSchema(DataNodeContainer, Class)} which
+     * will return augmentation schema derived from supplied augmentation target
+     * schema.
+     *
+     * @param augClass
+     *            - ugmentation class
+     * @return Schema of augmentation or null if augmentaiton is not known in
+     *         this context
+     * @throws IllegalArgumentException
+     *             - if supplied class is not an augmentation
+     */
+    public @Nullable AugmentationSchema getAugmentationDefinition(final Class<?> augClass) throws IllegalArgumentException {
+        Preconditions.checkArgument(Augmentation.class.isAssignableFrom(augClass), "Class %s does not represent augmentation", augClass);
+        return this.augmentationToSchema.get(referencedType(augClass));
+    }
+
+    /**
+     * Returns defining {@link DataSchemaNode} for supplied class.
+     *
+     * <p>
+     * Returned schema is schema definition from which class was generated. This
+     * schema may be isolated from augmentations, if supplied class represent
+     * node, which was child of grouping or augmentation.
+     * <p>
+     * For getting augmentation schema from augmentation class use
+     * {@link #getAugmentationDefinition(Class)} instead.
+     *
+     * @param cls
+     *            - class which represents list, container, choice or case.
+     * @return Schema node, from which class was generated.
+     */
+    public DataSchemaNode getSchemaDefinition(final Class<?> cls) {
+        Preconditions.checkArgument(!Augmentation.class.isAssignableFrom(cls),"Supplied class must not be augmentation (%s is)", cls);
+        return (DataSchemaNode) this.typeToDefiningSchema.get(referencedType(cls));
+    }
+
+    /**
+     * Returns defining {@link AugmentationSchema} of target for supplied class.
+     *
+     * @param target
+     *            - {@link DataNodeContainer}
+     * @param aug
+     *            - supplied class
+     * @return entry of {@link AugmentationSchema} according to its identifier
+     *         {@link AugmentationIdentifier}
+     */
+    public Entry<AugmentationIdentifier, AugmentationSchema> getResolvedAugmentationSchema(final DataNodeContainer target,
+            final Class<? extends Augmentation<?>> aug) {
+        final AugmentationSchema origSchema = getAugmentationDefinition(aug);
+        Preconditions.checkArgument(origSchema != null, "Augmentation %s is not known in current schema context",aug);
+        /*
+         * FIXME: Validate augmentation schema lookup
+         *
+         * Currently this algorithm, does not verify if instantiated child nodes
+         * are real one derived from augmentation schema. The problem with full
+         * validation is, if user used copy builders, he may use augmentation
+         * which was generated for different place.
+         *
+         * If this augmentations have same definition, we emit same identifier
+         * with data and it is up to underlying user to validate data.
+         *
+         */
+        final Set<QName> childNames = new HashSet<>();
+        final Set<DataSchemaNode> realChilds = new HashSet<>();
+        for (final DataSchemaNode child : origSchema.getChildNodes()) {
+            final DataSchemaNode dataChildQNname = target.getDataChildByName(child.getQName());
+            final String childLocalName = child.getQName().getLocalName();
+            if (dataChildQNname == null) {
+                for (final DataSchemaNode dataSchemaNode : target.getChildNodes()) {
+                    if (childLocalName.equals(dataSchemaNode.getQName().getLocalName())) {
+                        realChilds.add(dataSchemaNode);
+                        childNames.add(dataSchemaNode.getQName());
+                    }
+                }
+            } else {
+                realChilds.add(dataChildQNname);
+                childNames.add(child.getQName());
+            }
+        }
+
+        final AugmentationIdentifier identifier = new AugmentationIdentifier(childNames);
+        final AugmentationSchema proxy = new EffectiveAugmentationSchema(origSchema, realChilds);
+        return new SimpleEntry<>(identifier, proxy);
+    }
+
+    /**
+     * Returns resolved case schema for supplied class
+     *
+     * @param schema
+     *            - resolved parent choice schema
+     * @param childClass
+     *            - class representing case.
+     * @return Optionally a resolved case schema, absent if the choice is not
+     *         legal in the given context.
+     * @throws IllegalArgumentException
+     *             - if supplied class does not represent case
+     */
+    public Optional<ChoiceCaseNode> getCaseSchemaDefinition(final ChoiceSchemaNode schema, final Class<?> childClass) throws IllegalArgumentException {
+        final DataSchemaNode origSchema = getSchemaDefinition(childClass);
+        Preconditions.checkArgument(origSchema instanceof ChoiceCaseNode, "Supplied schema %s is not case.", origSchema);
+
+        /*
+         * FIXME: Make sure that if there are multiple augmentations of same
+         * named case, with same structure we treat it as equals this is due
+         * property of Binding specification and copy builders that user may be
+         * unaware that he is using incorrect case which was generated for
+         * choice inside grouping.
+         */
+        final Optional<ChoiceCaseNode> found = BindingSchemaContextUtils.findInstantiatedCase(schema,
+                (ChoiceCaseNode) origSchema);
+        return found;
+    }
+
+    private static Type referencedType(final Class<?> type) {
+        return new ReferencedTypeImpl(type.getPackage().getName(), type.getSimpleName());
+    }
+
+    static Type referencedType(final String type) {
+        final int packageClassSeparator = type.lastIndexOf(DOT);
+        return new ReferencedTypeImpl(type.substring(0, packageClassSeparator), type.substring(packageClassSeparator + 1));
+    }
+
+    /**
+     * Returns schema ({@link DataSchemaNode}, {@link AugmentationSchema} or {@link TypeDefinition})
+     * from which supplied class was generated. Returned schema may be augmented with
+     * additional information, which was not available at compile type
+     * (e.g. third party augmentations).
+     *
+     * @param type Binding Class for which schema should be retrieved.
+     * @return Instance of generated type (definition of Java API), along with
+     *     {@link DataSchemaNode}, {@link AugmentationSchema} or {@link TypeDefinition}
+     *     which was used to generate supplied class.
+     */
+    public Entry<GeneratedType, Object> getTypeWithSchema(final Class<?> type) {
+        return getTypeWithSchema(referencedType(type));
+    }
+
+    public Entry<GeneratedType, Object> getTypeWithSchema(final String type) {
+        return getTypeWithSchema(referencedType(type));
+    }
+
+    private Entry<GeneratedType, Object> getTypeWithSchema(final Type referencedType) {
+        final Object schema = this.typeToDefiningSchema.get(referencedType);
+        Preconditions.checkNotNull(schema, "Failed to find schema for type %s", referencedType);
+
+        final Type definedType = this.typeToDefiningSchema.inverse().get(schema);
+        Preconditions.checkNotNull(definedType, "Failed to find defined type for %s schema %s", referencedType, schema);
+
+        if (definedType instanceof GeneratedTypeBuilder) {
+            return new SimpleEntry<>(((GeneratedTypeBuilder) definedType).toInstance(), schema);
+        }
+        Preconditions.checkArgument(definedType instanceof GeneratedType,"Type {} is not GeneratedType", referencedType);
+        return new SimpleEntry<>((GeneratedType) definedType,schema);
+    }
+
+    public ImmutableMap<Type, Entry<Type, Type>> getChoiceCaseChildren(final DataNodeContainer schema) {
+        final Map<Type,Entry<Type,Type>> childToCase = new HashMap<>();
+        for (final ChoiceSchemaNode choice :  FluentIterable.from(schema.getChildNodes()).filter(ChoiceSchemaNode.class)) {
+            final ChoiceSchemaNode originalChoice = getOriginalSchema(choice);
+            final Type choiceType = referencedType(this.typeToDefiningSchema.inverse().get(originalChoice));
+            final Collection<Type> cases = this.choiceToCases.get(choiceType);
+
+            for (Type caze : cases) {
+                final Entry<Type,Type> caseIdentifier = new SimpleEntry<>(choiceType,caze);
+                final HashSet<Type> caseChildren = new HashSet<>();
+                if (caze instanceof GeneratedTypeBuilder) {
+                    caze = ((GeneratedTypeBuilder) caze).toInstance();
+                }
+                collectAllContainerTypes((GeneratedType) caze, caseChildren);
+                for (final Type caseChild : caseChildren) {
+                    childToCase.put(caseChild, caseIdentifier);
+                }
+            }
+        }
+        return ImmutableMap.copyOf(childToCase);
+    }
+
+    /**
+     * Map enum constants: yang - java
+     *
+     * @param enumClass enum generated class
+     * @return mapped enum constants from yang with their corresponding values in generated binding classes
+     */
+    public BiMap<String, String> getEnumMapping(final Class<?> enumClass) {
+        final Entry<GeneratedType, Object> typeWithSchema = getTypeWithSchema(enumClass);
+        return getEnumMapping(typeWithSchema);
+    }
+
+    /**
+     * See {@link #getEnumMapping(Class)}}
+     */
+    public BiMap<String, String> getEnumMapping(final String enumClass) {
+        final Entry<GeneratedType, Object> typeWithSchema = getTypeWithSchema(enumClass);
+        return getEnumMapping(typeWithSchema);
+    }
+
+    private static BiMap<String, String> getEnumMapping(final Entry<GeneratedType, Object> typeWithSchema) {
+        final TypeDefinition<?> typeDef = (TypeDefinition<?>) typeWithSchema.getValue();
+
+        Preconditions.checkArgument(typeDef instanceof EnumTypeDefinition);
+        final EnumTypeDefinition enumType = (EnumTypeDefinition) typeDef;
+
+        final HashBiMap<String, String> mappedEnums = HashBiMap.create();
+
+        for (final EnumTypeDefinition.EnumPair enumPair : enumType.getValues()) {
+            mappedEnums.put(enumPair.getName(),
+                    JavaIdentifierNormalizer.normalizeSpecificIdentifier(enumPair.getName(), JavaIdentifier.CLASS));
+        }
+
+        // TODO cache these maps for future use
+
+        return mappedEnums;
+    }
+
+    public Set<Class<?>> getCases(final Class<?> choice) {
+        final Collection<Type> cazes = this.choiceToCases.get(referencedType(choice));
+        final Set<Class<?>> ret = new HashSet<>(cazes.size());
+        for(final Type caze : cazes) {
+            try {
+                final Class<?> c = this.strategy.loadClass(caze);
+                ret.add(c);
+            } catch (final ClassNotFoundException e) {
+                LOG.warn("Failed to load class for case {}, ignoring it", caze, e);
+            }
+        }
+        return ret;
+    }
+
+    public Class<?> getClassForSchema(final SchemaNode childSchema) {
+        final SchemaNode origSchema = getOriginalSchema(childSchema);
+        final Type clazzType = this.typeToDefiningSchema.inverse().get(origSchema);
+        try {
+            return this.strategy.loadClass(clazzType);
+        } catch (final ClassNotFoundException e) {
+            throw new IllegalStateException(e);
+        }
+    }
+
+    public ImmutableMap<AugmentationIdentifier,Type> getAvailableAugmentationTypes(final DataNodeContainer container) {
+        final Map<AugmentationIdentifier,Type> identifierToType = new HashMap<>();
+        if (container instanceof AugmentationTarget) {
+            final Set<AugmentationSchema> augments = ((AugmentationTarget) container).getAvailableAugmentations();
+            for (final AugmentationSchema augment : augments) {
+                // Augmentation must have child nodes if is to be used with Binding classes
+                AugmentationSchema augOrig = augment;
+                while (augOrig.getOriginalDefinition().isPresent()) {
+                    augOrig = augOrig.getOriginalDefinition().get();
+                }
+
+                if (!augment.getChildNodes().isEmpty()) {
+                    final Type augType = this.typeToDefiningSchema.inverse().get(augOrig);
+                    if (augType != null) {
+                        identifierToType.put(getAugmentationIdentifier(augment),augType);
+                    }
+                }
+            }
+        }
+
+        return ImmutableMap.copyOf(identifierToType);
+    }
+
+    private static AugmentationIdentifier getAugmentationIdentifier(final AugmentationSchema augment) {
+        final Set<QName> childNames = new HashSet<>();
+        for (final DataSchemaNode child : augment.getChildNodes()) {
+            childNames.add(child.getQName());
+        }
+        return new AugmentationIdentifier(childNames);
+    }
+
+    private static Type referencedType(final Type type) {
+        if (type instanceof ReferencedTypeImpl) {
+            return type;
+        }
+        return new ReferencedTypeImpl(type.getPackageName(), type.getName());
+    }
+
+    private static Set<Type> collectAllContainerTypes(final GeneratedType type, final Set<Type> collection) {
+        for (final MethodSignature definition : type.getMethodDefinitions()) {
+            Type childType = definition.getReturnType();
+            if (childType instanceof ParameterizedType) {
+                childType = ((ParameterizedType) childType).getActualTypeArguments()[0];
+            }
+            if (childType instanceof GeneratedType || childType instanceof GeneratedTypeBuilder) {
+                collection.add(referencedType(childType));
+            }
+        }
+        for (final Type parent : type.getImplements()) {
+            if (parent instanceof GeneratedType) {
+                collectAllContainerTypes((GeneratedType) parent, collection);
+            }
+        }
+        return collection;
+    }
+
+    private static <T extends SchemaNode> T getOriginalSchema(final T choice) {
+        @SuppressWarnings("unchecked")
+        final T original = (T) SchemaNodeUtils.getRootOriginalIfPossible(choice);
+        if (original != null) {
+            return original;
+        }
+        return choice;
+    }
+
+    public Class<?> getIdentityClass(final QName input) {
+        return this.identityClasses.getUnchecked(input);
+    }
+}
diff --git a/binding2/mdsal-binding2-generator-impl/src/main/java/org/opendaylight/mdsal/binding/javav2/generator/impl/util/BindingSchemaContextUtils.java b/binding2/mdsal-binding2-generator-impl/src/main/java/org/opendaylight/mdsal/binding/javav2/generator/impl/util/BindingSchemaContextUtils.java
new file mode 100644 (file)
index 0000000..5be37fd
--- /dev/null
@@ -0,0 +1,258 @@
+/*
+ * Copyright (c) 2017 Pantheon Technologies s.r.o. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.mdsal.binding.javav2.generator.impl.util;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import java.util.Iterator;
+import java.util.Set;
+import org.opendaylight.mdsal.binding.javav2.generator.util.JavaIdentifier;
+import org.opendaylight.mdsal.binding.javav2.generator.util.JavaIdentifierNormalizer;
+import org.opendaylight.mdsal.binding.javav2.spec.base.InstanceIdentifier;
+import org.opendaylight.mdsal.binding.javav2.spec.base.TreeArgument;
+import org.opendaylight.mdsal.binding.javav2.spec.base.TreeNode;
+import org.opendaylight.mdsal.binding.javav2.spec.runtime.YangModuleInfo;
+import org.opendaylight.mdsal.binding.javav2.spec.structural.Augmentation;
+import org.opendaylight.mdsal.binding.javav2.spec.structural.TreeChildNode;
+import org.opendaylight.mdsal.binding.javav2.spec.util.BindingReflections;
+import org.opendaylight.mdsal.binding.javav2.util.BindingMapping;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode;
+import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.NotificationDefinition;
+import org.opendaylight.yangtools.yang.model.api.OperationDefinition;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.model.api.SchemaNode;
+import org.opendaylight.yangtools.yang.model.util.SchemaNodeUtils;
+
+@Beta
+public final class BindingSchemaContextUtils {
+
+    private BindingSchemaContextUtils() {
+        throw new UnsupportedOperationException("Utility class should not be instantiated");
+    }
+
+    /**
+     * Find data node container by binding path in schema context.
+     *
+     * FIXME: This method does not search in case augmentations.
+     *
+     * @param ctx
+     *            - schema context
+     * @param path
+     *            - binding path
+     * @return node container by binding path if exists, absent otherwise
+     */
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    public static Optional<DataNodeContainer> findDataNodeContainer(final SchemaContext ctx,
+            final InstanceIdentifier<?> path) {
+        final Iterator<TreeArgument> pathArguments = path.getPathArguments().iterator();
+        TreeArgument currentArg = pathArguments.next();
+        Preconditions.checkArgument(currentArg != null);
+        QName currentQName = BindingReflections.findQName(currentArg.getType());
+
+        Optional<DataNodeContainer> currentContainer = Optional.absent();
+        if (BindingReflections.isNotification(currentArg.getType())) {
+            currentContainer = findNotification(ctx, currentQName);
+        } else if (BindingReflections.isRpcOrActionType(currentArg.getType())) {
+            currentContainer = findFirstDataNodeContainerInRpcOrAction(ctx, currentArg.getType());
+            if(currentQName == null && currentContainer.isPresent()) {
+                currentQName = ((DataSchemaNode) currentContainer.get()).getQName();
+            }
+        } else {
+            currentContainer = findDataNodeContainer(ctx, currentQName);
+        }
+
+        while (currentContainer.isPresent() && pathArguments.hasNext()) {
+            currentArg = pathArguments.next();
+            if (Augmentation.class.isAssignableFrom(currentArg.getType())) {
+                currentQName = BindingReflections.findQName(currentArg.getType());
+                if(pathArguments.hasNext()) {
+                    currentArg = pathArguments.next();
+                } else {
+                    return currentContainer;
+                }
+            }
+            if (TreeChildNode.class.isAssignableFrom(currentArg.getType())
+                    && BindingReflections.isAugmentationChild(currentArg.getType())) {
+                currentQName = BindingReflections.findQName(currentArg.getType());
+            } else {
+                currentQName = QName.create(currentQName, BindingReflections.findQName(currentArg.getType()).getLocalName());
+            }
+            final Optional<DataNodeContainer> potential = findDataNodeContainer(currentContainer.get(), currentQName);
+            if (potential.isPresent()) {
+                currentContainer = potential;
+            } else {
+                return Optional.absent();
+            }
+        }
+        return currentContainer;
+    }
+
+    private static Optional<DataNodeContainer> findNotification(final SchemaContext ctx, final QName notificationQName) {
+        for (final NotificationDefinition notification : ctx.getNotifications()) {
+            if (notification.getQName().equals(notificationQName)) {
+                return Optional.<DataNodeContainer> of(notification);
+            }
+        }
+        return Optional.absent();
+    }
+
+    private static Optional<DataNodeContainer> findDataNodeContainer(final DataNodeContainer ctx,
+            final QName targetQName) {
+
+        for (final DataSchemaNode child : ctx.getChildNodes()) {
+            if (child instanceof ChoiceSchemaNode) {
+                final DataNodeContainer potential = findInCases(((ChoiceSchemaNode) child), targetQName);
+                if (potential != null) {
+                    return Optional.of(potential);
+                }
+            } else if (child instanceof DataNodeContainer && child.getQName().equals(targetQName)) {
+                return Optional.of((DataNodeContainer) child);
+            } else if (child instanceof DataNodeContainer //
+                    && child.isAddedByUses() //
+                    && child.getQName().getLocalName().equals(targetQName.getLocalName())) {
+                return Optional.of((DataNodeContainer) child);
+            }
+
+        }
+        return Optional.absent();
+    }
+
+    private static DataNodeContainer findInCases(final ChoiceSchemaNode choiceNode, final QName targetQName) {
+        for (final ChoiceCaseNode caze : choiceNode.getCases()) {
+            final Optional<DataNodeContainer> potential = findDataNodeContainer(caze, targetQName);
+            if (potential.isPresent()) {
+                return potential.get();
+            }
+        }
+        return null;
+    }
+
+    private static Optional<DataNodeContainer> findFirstDataNodeContainerInRpcOrAction(final SchemaContext ctx,
+            final Class<? extends TreeNode> targetType) {
+        final YangModuleInfo moduleInfo;
+        try {
+            moduleInfo = BindingReflections.getModuleInfo(targetType);
+        } catch (final Exception e) {
+            throw new IllegalArgumentException(
+                    String.format("Failed to load module information for class %s", targetType), e);
+        }
+        Optional<DataNodeContainer> optional = null;
+        optional = findFirst(ctx.getOperations(), moduleInfo, targetType);
+        if (optional.isPresent()) {
+            return optional;
+        } else {
+            return findFirst(ctx.getActions(), moduleInfo, targetType);
+        }
+    }
+
+    private static Optional<DataNodeContainer> findFirst(final Set<? extends OperationDefinition> operations,
+            final YangModuleInfo moduleInfo, final Class<? extends TreeNode> targetType) {
+        for (final OperationDefinition operation : operations) {
+            final String operationNamespace = operation.getQName().getNamespace().toString();
+            final String operationRevision = operation.getQName().getFormattedRevision();
+            if (moduleInfo.getNamespace().equals(operationNamespace)
+                    && moduleInfo.getRevision().equals(operationRevision)) {
+                final Optional<DataNodeContainer> potential = findInputOutput(operation, targetType.getSimpleName());
+                if(potential.isPresent()) {
+                    return potential;
+                }
+            }
+        }
+        return Optional.absent();
+    }
+
+    private static Optional<DataNodeContainer> findInputOutput(final OperationDefinition operation,
+            final String targetType) {
+        final String operationName =
+                JavaIdentifierNormalizer.normalizeSpecificIdentifier(operation.getQName().getLocalName(),
+                JavaIdentifier.CLASS);
+        final String actionInputName =
+                new StringBuilder(operationName).append(BindingMapping.RPC_INPUT_SUFFIX).toString();
+        final String actionOutputName =
+                new StringBuilder(operationName).append(BindingMapping.RPC_OUTPUT_SUFFIX).toString();
+        if (targetType.equals(actionInputName)) {
+            return Optional.<DataNodeContainer> of(operation.getInput());
+        } else if (targetType.equals(actionOutputName)) {
+            return Optional.<DataNodeContainer> of(operation.getOutput());
+        }
+       return Optional.absent();
+    }
+
+    /**
+     * Find choice schema node in parent by binding class.
+     *
+     * @param parent
+     *            - choice parent
+     * @param choiceClass
+     *            - choice binding class
+     * @return choice schema node if exists, absent() otherwise
+     */
+    public static Optional<ChoiceSchemaNode> findInstantiatedChoice(final DataNodeContainer parent, final Class<?> choiceClass) {
+        return findInstantiatedChoice(parent, BindingReflections.findQName(choiceClass));
+    }
+
+    /**
+     * Find choice schema node in parent node by qname of choice.
+     *
+     * @param ctxNode
+     *            - parent node
+     * @param choiceName
+     *            - qname of choice
+     * @return choice schema node if exists, absent() otherwise
+     */
+    public static Optional<ChoiceSchemaNode> findInstantiatedChoice(final DataNodeContainer ctxNode, final QName choiceName) {
+        final DataSchemaNode potential = ctxNode.getDataChildByName(choiceName);
+        if (potential instanceof ChoiceSchemaNode) {
+            return Optional.of((ChoiceSchemaNode) potential);
+        }
+
+        return Optional.absent();
+    }
+
+    /**
+     * Find choice case node in choice schema node.
+     *
+     * @param instantiatedChoice
+     *            - choice
+     * @param originalDefinition
+     *            - choice case
+     * @return choice case node if exists, absent() otherwise
+     */
+    public static Optional<ChoiceCaseNode> findInstantiatedCase(final ChoiceSchemaNode instantiatedChoice, final ChoiceCaseNode originalDefinition) {
+        ChoiceCaseNode potential = instantiatedChoice.getCaseNodeByName(originalDefinition.getQName());
+        if(originalDefinition.equals(potential)) {
+            return Optional.of(potential);
+        }
+        if (potential != null) {
+            final SchemaNode potentialRoot = SchemaNodeUtils.getRootOriginalIfPossible(potential);
+            if (originalDefinition.equals(potentialRoot)) {
+                return Optional.of(potential);
+            }
+        }
+        // We try to find case by name, then lookup its root definition
+        // and compare it with original definition
+        // This solves case, if choice was inside grouping
+        // which was used in different module and thus namespaces are
+        // different, but local names are still same.
+        //
+        // Still we need to check equality of definition, because local name is not
+        // sufficient to uniquelly determine equality of cases
+        //
+        potential = instantiatedChoice.getCaseNodeByName(originalDefinition.getQName().getLocalName());
+        if(potential != null && (originalDefinition.equals(SchemaNodeUtils.getRootOriginalIfPossible(potential)))) {
+            return Optional.of(potential);
+        }
+        return Optional.absent();
+    }
+}
diff --git a/binding2/mdsal-binding2-spec/src/main/java/org/opendaylight/mdsal/binding/javav2/spec/util/AugmentationFieldGetter.java b/binding2/mdsal-binding2-spec/src/main/java/org/opendaylight/mdsal/binding/javav2/spec/util/AugmentationFieldGetter.java
new file mode 100644 (file)
index 0000000..d15a3be
--- /dev/null
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2017 Pantheon Technologies s.r.o. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.mdsal.binding.javav2.spec.util;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.Preconditions;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodHandles.Lookup;
+import java.lang.invoke.MethodType;
+import java.lang.reflect.Field;
+import java.util.Collections;
+import java.util.Map;
+import org.opendaylight.mdsal.binding.javav2.spec.structural.Augmentation;
+import org.opendaylight.mdsal.binding.javav2.spec.structural.AugmentationHolder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Beta
+abstract class AugmentationFieldGetter {
+
+    private static final Logger LOG = LoggerFactory.getLogger(AugmentationFieldGetter.class);
+
+    private static final String AUGMENTATION_FIELD = "augmentation";
+
+    private static final AugmentationFieldGetter DUMMY = new AugmentationFieldGetter() {
+        @Override
+        protected Map<Class<? extends Augmentation<?>>, Augmentation<?>> getAugmentations(final Object input) {
+            return Collections.emptyMap();
+        }
+    };
+
+    private static final AugmentationFieldGetter AUGMENTATION_HOLDER_GETTER = new AugmentationFieldGetter() {
+
+        @Override
+        @SuppressWarnings({ "unchecked", "rawtypes" })
+        protected Map<Class<? extends Augmentation<?>>, Augmentation<?>> getAugmentations(final Object input) {
+            return (Map) ((AugmentationHolder<?>) input).augmentations();
+        }
+    };
+
+    private static final LoadingCache<Class<?>, AugmentationFieldGetter> AUGMENTATION_GETTERS =
+            CacheBuilder.newBuilder().weakKeys().build(new AugmentationGetterLoader());
+
+    /**
+     * Retrieves augmentations from supplied object.
+     *
+     * @param input
+     *            - input Data object, from which augmentations should be
+     *            extracted
+     * @return Map of Augmentation class to augmentation
+     */
+    protected abstract Map<Class<? extends Augmentation<?>>, Augmentation<?>> getAugmentations(final Object input);
+
+    public static AugmentationFieldGetter getGetter(final Class<? extends Object> clz) {
+        if (AugmentationHolder.class.isAssignableFrom(clz)) {
+            return AUGMENTATION_HOLDER_GETTER;
+        }
+        return AUGMENTATION_GETTERS.getUnchecked(clz);
+    }
+
+    private static final class AugmentationGetterLoader extends CacheLoader<Class<?>, AugmentationFieldGetter> {
+        private static final MethodType GETTER_TYPE = MethodType.methodType(Map.class, Object.class);
+        private static final Lookup LOOKUP = MethodHandles.lookup();
+
+        @Override
+        public AugmentationFieldGetter load(final Class<?> key) throws IllegalAccessException {
+            final Field field;
+            try {
+                field = key.getDeclaredField(AUGMENTATION_FIELD);
+                field.setAccessible(true);
+            } catch (NoSuchFieldException | SecurityException e) {
+                LOG.warn("Failed to acquire augmentation field {}, ignoring augmentations in class {}",
+                        AUGMENTATION_FIELD, key, e);
+                return DUMMY;
+            }
+            if (!Map.class.isAssignableFrom(field.getType())) {
+                LOG.warn("Class {} field {} is not a Map, ignoring augmentations", key,
+                        AUGMENTATION_FIELD);
+                return DUMMY;
+            }
+
+            return new ReflectionAugmentationFieldGetter(LOOKUP.unreflectGetter(field).asType(GETTER_TYPE));
+        }
+    }
+
+    private static final class ReflectionAugmentationFieldGetter extends AugmentationFieldGetter {
+        private final MethodHandle fieldGetter;
+
+        ReflectionAugmentationFieldGetter(final MethodHandle mh) {
+            this.fieldGetter = Preconditions.checkNotNull(mh);
+        }
+
+        @Override
+        protected Map<Class<? extends Augmentation<?>>, Augmentation<?>> getAugmentations(final Object input) {
+            try {
+                return (Map<Class<? extends Augmentation<?>>, Augmentation<?>>) this.fieldGetter.invokeExact(input);
+            } catch (final Throwable e) {
+                throw new IllegalStateException("Failed to access augmentation field on " + input, e);
+            }
+        }
+    }
+}
diff --git a/binding2/mdsal-binding2-spec/src/main/java/org/opendaylight/mdsal/binding/javav2/spec/util/BindingReflections.java b/binding2/mdsal-binding2-spec/src/main/java/org/opendaylight/mdsal/binding/javav2/spec/util/BindingReflections.java
new file mode 100644 (file)
index 0000000..5474552
--- /dev/null
@@ -0,0 +1,665 @@
+/*
+ * Copyright (c) 2017 Pantheon Technologies s.r.o. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.mdsal.binding.javav2.spec.util;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkState;
+import com.google.common.annotations.Beta;
+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.Method;
+import java.lang.reflect.Type;
+import java.net.URI;
+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;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import javax.annotation.Nonnull;
+import org.opendaylight.mdsal.binding.javav2.spec.base.Action;
+import org.opendaylight.mdsal.binding.javav2.spec.base.BaseIdentity;
+import org.opendaylight.mdsal.binding.javav2.spec.base.Instantiable;
+import org.opendaylight.mdsal.binding.javav2.spec.base.Notification;
+import org.opendaylight.mdsal.binding.javav2.spec.base.Rpc;
+import org.opendaylight.mdsal.binding.javav2.spec.base.TreeNode;
+import org.opendaylight.mdsal.binding.javav2.spec.runtime.YangModelBindingProvider;
+import org.opendaylight.mdsal.binding.javav2.spec.runtime.YangModuleInfo;
+import org.opendaylight.mdsal.binding.javav2.spec.structural.Augmentable;
+import org.opendaylight.mdsal.binding.javav2.spec.structural.Augmentation;
+import org.opendaylight.mdsal.binding.javav2.spec.structural.TreeChildNode;
+import org.opendaylight.yangtools.util.ClassLoaderUtils;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.common.QNameModule;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Beta
+public final class BindingReflections {
+
+    private static final Logger LOG = LoggerFactory.getLogger(BindingReflections.class);
+
+    private static final long EXPIRATION_TIME = 60;
+    private static final String QNAME_STATIC_FIELD_NAME = "QNAME";
+    private static final String RPC_ACTION_OUTPUT_SUFFIX = "Output";
+    private static final String MODULE_INFO_CLASS_NAME = "$YangModuleInfoImpl";
+    private static final String PACKAGE_PREFIX = "org.opendaylight.mdsal.gen.javav2";
+    private static final String ROOT_PACKAGE_PATTERN_STRING =
+            "(org.opendaylight.mdsal.gen.javav2.[a-z0-9_\\.]*\\.rev[0-9][0-9][0-1][0-9][0-3][0-9])";
+
+    private static final Pattern ROOT_PACKAGE_PATTERN = Pattern.compile(ROOT_PACKAGE_PATTERN_STRING);
+
+    private static final LoadingCache<Class<?>, Optional<QName>> CLASS_TO_QNAME = CacheBuilder.newBuilder().weakKeys()
+            .expireAfterAccess(EXPIRATION_TIME, TimeUnit.SECONDS).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 implemented
+     * {@link Augmentation} interface.
+     *
+     * @param augmentation
+     *            {@link Augmentation} subclass for which we want to determine
+     *            augmentation target.
+     * @return Augmentation target - class which augmentation provides
+     *         additional extensions.
+     */
+    public static Class<? extends Augmentable<?>> findAugmentationTarget(
+            final Class<? extends Augmentation<?>> augmentation) {
+        return ClassLoaderUtils.findFirstGenericArgument(augmentation, Augmentation.class);
+    }
+
+    /**
+     * Find data hierarchy parent from concrete Tree Node class
+     *
+     * This method uses first generic argument of implemented
+     * {@link TreeChildNode} interface.
+     *
+     * @param childClass
+     *            - child class for which we want to find the parent class
+     * @return Parent class, e.g. class of which the childClass is ChildOf
+     */
+    static Class<?> findHierarchicalParent(final Class<? extends TreeChildNode<?, ?>> childClass) {
+        return ClassLoaderUtils.findFirstGenericArgument(childClass, TreeChildNode.class);
+    }
+
+    /**
+     * Returns a QName associated to supplied type
+     *
+     * @param dataType
+     *            - type of data
+     * @return QName associated to supplied dataType. If dataType is
+     *         Augmentation method does not return canonical QName, but QName
+     *         with correct namespace revision, but virtual local name, since
+     *         augmentations do not have name.
+     *
+     *         May return null if QName is not present.
+     */
+    public static QName findQName(final Class<?> dataType) {
+        return CLASS_TO_QNAME.getUnchecked(dataType).orNull();
+    }
+
+    /**
+     * Checks if method is RPC or Action invocation
+     *
+     * @param possibleMethod
+     *            - method to check
+     * @return true if method is RPC or Action invocation, false otherwise.
+     */
+    public static boolean isRpcOrActionMethod(final Method possibleMethod) {
+        return possibleMethod != null && (Rpc.class.isAssignableFrom(possibleMethod.getDeclaringClass())
+                || Action.class.isAssignableFrom(possibleMethod.getDeclaringClass()))
+                && Future.class.isAssignableFrom(possibleMethod.getReturnType())
+                // length <= 2: it seemed to be impossible to get correct RpcMethodInvoker because of
+                // 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;
+    }
+
+    /**
+     * Extracts Output class for RPC method
+     *
+     * @param targetMethod
+     *            method to scan
+     * @return Optional.absent() if result type could not be get, or return type
+     *         is Void.
+     */
+    @SuppressWarnings("rawtypes")
+    public static Optional<Class<?>> resolveRpcOutputClass(final Method targetMethod) {
+        checkState(isRpcOrActionMethod(targetMethod), "Supplied method is not a RPC or Action invocation method");
+        final Type futureType = targetMethod.getGenericReturnType();
+        final Type rpcResultType = ClassLoaderUtils.getFirstGenericParameter(futureType);
+        final Type rpcResultArgument = ClassLoaderUtils.getFirstGenericParameter(rpcResultType);
+        if (rpcResultArgument instanceof Class && !Void.class.equals(rpcResultArgument)) {
+            return Optional.of((Class) rpcResultArgument);
+        }
+        return Optional.absent();
+    }
+
+    /**
+     * Extracts input class for RPC or Action
+     *
+     * @param targetMethod
+     *            - method to scan
+     * @return Optional.absent() if RPC or Action has no input, RPC input type
+     *         otherwise.
+     */
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    public static Optional<Class<? extends Instantiable<?>>> resolveRpcInputClass(final Method targetMethod) {
+        for (final Class clazz : targetMethod.getParameterTypes()) {
+            if (Instantiable.class.isAssignableFrom(clazz)) {
+                return Optional.of(clazz);
+            }
+        }
+        return Optional.absent();
+    }
+
+    /**
+     * Find qname of base identity context.
+     *
+     * @param context
+     *            - base identity type context
+     * @return QName of base identity context
+     */
+    public static QName getQName(final Class<? extends BaseIdentity> context) {
+        return findQName(context);
+    }
+
+    /**
+     * Checks if class is child of augmentation.
+     *
+     * @param clazz
+     *            - class to check
+     * @return true if is augmentation, false otherwise
+     */
+    public static boolean isAugmentationChild(final Class<?> clazz) {
+        // FIXME: Current resolver could be still confused when child node was
+        // added by grouping
+        checkArgument(clazz != null);
+
+        @SuppressWarnings({ "rawtypes", "unchecked" })
+        final Class<?> parent = findHierarchicalParent((Class) clazz);
+        if (parent == null) {
+            LOG.debug("Did not find a parent for class {}", clazz);
+            return false;
+        }
+
+        final String clazzModelPackage = getModelRootPackageName(clazz.getPackage());
+        final String parentModelPackage = getModelRootPackageName(parent.getPackage());
+
+        return !clazzModelPackage.equals(parentModelPackage);
+    }
+
+    /**
+     * Returns root package name for supplied package.
+     *
+     * @param pkg
+     *            Package for which find model root package.
+     * @return Package of model root.
+     */
+    public static String getModelRootPackageName(final Package pkg) {
+        return getModelRootPackageName(pkg.getName());
+    }
+
+    /**
+     * Returns root package name for supplied package name.
+     *
+     * @param name
+     *            - package for which find model root package
+     * @return Package of model root
+     */
+    public static String getModelRootPackageName(final String name) {
+        checkArgument(name != null, "Package name should not be null.");
+        checkArgument(name.startsWith(PACKAGE_PREFIX), "Package name not starting with %s, is: %s", PACKAGE_PREFIX,
+                name);
+        final Matcher match = ROOT_PACKAGE_PATTERN.matcher(name);
+        checkArgument(match.find(), "Package name '%s' does not match required pattern '%s'", name,
+                ROOT_PACKAGE_PATTERN_STRING);
+        return match.group(0);
+    }
+
+    /**
+     * Get QName module of specific Binding object class.
+     *
+     * @param clz
+     *            - class of binding object
+     * @return QName module of binding object
+     */
+    public static final QNameModule getQNameModule(final Class<?> clz) {
+        if (Instantiable.class.isAssignableFrom(clz) || BaseIdentity.class.isAssignableFrom(clz)) {
+            return findQName(clz).getModule();
+        }
+        try {
+            final YangModuleInfo modInfo = BindingReflections.getModuleInfo(clz);
+            return getQNameModule(modInfo);
+        } catch (final Exception e) {
+            throw new IllegalStateException("Unable to get QName of defining model.", e);
+        }
+    }
+
+    /**
+     * Returns module QName.
+     *
+     * @param modInfo
+     *            - module info
+     * @return {@link QNameModule} from module info
+     */
+    public static final QNameModule getQNameModule(final YangModuleInfo modInfo) {
+        return QNameModule.create(URI.create(modInfo.getNamespace()), QName.parseRevision(modInfo.getRevision()));
+    }
+
+    /**
+     * Returns instance of {@link YangModuleInfo} of declaring model for
+     * specific class.
+     *
+     * @param cls
+     *            - class for getting info
+     * @return Instance of {@link YangModuleInfo} associated with model, from
+     *         which this class was derived.
+     * @throws Exception
+     */
+    public static YangModuleInfo getModuleInfo(final Class<?> cls) throws Exception {
+        checkArgument(cls != null);
+        final String packageName = getModelRootPackageName(cls.getPackage());
+        final String potentialClassName = getModuleInfoClassName(packageName);
+        return ClassLoaderUtils.withClassLoader(cls.getClassLoader(), (Callable<YangModuleInfo>) () -> {
+            final Class<?> moduleInfoClass = Thread.currentThread().getContextClassLoader().loadClass(potentialClassName);
+            return (YangModuleInfo) moduleInfoClass.getMethod("getInstance").invoke(null);
+         });
+    }
+
+    /**
+     * Returns name of module info class.
+     *
+     * @param packageName
+     *            - package name
+     * @return name of module info class
+     */
+    public static String getModuleInfoClassName(final String packageName) {
+        return packageName + "." + MODULE_INFO_CLASS_NAME;
+    }
+
+    /**
+     * Check if supplied class is derived from YANG model.
+     *
+     * @param cls
+     *            - class to check
+     * @return true if class is derived from YANG model.
+     */
+    public static boolean isBindingClass(final Class<?> cls) {
+        if (Instantiable.class.isAssignableFrom(cls) || Augmentation.class.isAssignableFrom(cls)
+                || TreeNode.class.isAssignableFrom(cls)) {
+            return true;
+        }
+        return cls.getName().startsWith(PACKAGE_PREFIX);
+    }
+
+    /**
+     * Checks if supplied method is callback for notifications.
+     *
+     * @param method
+     *            - method for check
+     * @return true if method is notification callback
+     */
+    public static boolean isNotificationCallback(final Method method) {
+        checkArgument(method != null);
+        if (method.getName().startsWith("on") && method.getParameterTypes().length == 1) {
+            final Class<?> potentialNotification = method.getParameterTypes()[0];
+            if (isNotification(potentialNotification)
+                    && method.getName().equals("on" + potentialNotification.getSimpleName())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Check if supplied class is Notification.
+     *
+     * @param potentialNotification
+     *            - class to check
+     * @return true if class is notification, false otherwise
+     */
+    public static boolean isNotification(final Class<?> potentialNotification) {
+        checkArgument(potentialNotification != null, "potentialNotification must not be null.");
+        return Notification.class.isAssignableFrom(potentialNotification);
+    }
+
+    /**
+     * Loads {@link YangModuleInfo} info available on current classloader.
+     *
+     * This method is shorthand for {@link #loadModuleInfos(ClassLoader)} with
+     * {@link Thread#getContextClassLoader()} for current thread.
+     *
+     * @return Set of {@link YangModuleInfo} available for current classloader.
+     */
+    public static ImmutableSet<YangModuleInfo> loadModuleInfos() {
+        return loadModuleInfos(Thread.currentThread().getContextClassLoader());
+    }
+
+    /**
+     * Loads {@link YangModuleInfo} info available on supplied classloader.
+     *
+     * {@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
+     *            - classloader for which {@link YangModuleInfo} should be
+     *            retrieved
+     * @return Set of {@link YangModuleInfo} available for supplied classloader.
+     */
+    public static ImmutableSet<YangModuleInfo> loadModuleInfos(final ClassLoader loader) {
+        final Builder<YangModuleInfo> moduleInfoSet = ImmutableSet.builder();
+        final ServiceLoader<YangModelBindingProvider> serviceLoader = ServiceLoader.load(YangModelBindingProvider.class,
+                loader);
+        for (final YangModelBindingProvider bindingProvider : serviceLoader) {
+            final YangModuleInfo moduleInfo = bindingProvider.getModuleInfo();
+            checkState(moduleInfo != null, "Module Info for %s is not available.", bindingProvider.getClass());
+            collectYangModuleInfo(bindingProvider.getModuleInfo(), moduleInfoSet);
+        }
+        return moduleInfoSet.build();
+    }
+
+    private static void collectYangModuleInfo(final YangModuleInfo moduleInfo,
+            final Builder<YangModuleInfo> moduleInfoSet) {
+        moduleInfoSet.add(moduleInfo);
+        for (final YangModuleInfo dependency : moduleInfo.getImportedModules()) {
+            collectYangModuleInfo(dependency, moduleInfoSet);
+        }
+    }
+
+    /**
+     * Checks if supplied class represents RPC or Action input/output.
+     *
+     * @param targetType
+     *            - class to be checked
+     * @return true if class represents RPC or Action input/output class
+     */
+    public static boolean isRpcOrActionType(final Class<? extends TreeNode> targetType) {
+        return Instantiable.class.isAssignableFrom(targetType) && !TreeChildNode.class.isAssignableFrom(targetType)
+                && !Notification.class.isAssignableFrom(targetType)
+                && (targetType.getName().endsWith("Input") || targetType.getName().endsWith("Output"));
+    }
+
+    /**
+     * 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 Iterable<Class<? extends TreeNode>> getChildrenClasses(final Class<? extends Instantiable<?>> type) {
+        checkArgument(type != null, "Target type must not be null");
+        checkArgument(Instantiable.class.isAssignableFrom(type), "Supplied type must be derived from Instantiable");
+        final List<Class<? extends TreeNode>> ret = new LinkedList<>();
+        for (final Method method : type.getMethods()) {
+            final Optional<Class<? extends Instantiable<?>>> entity = getYangModeledReturnType(method);
+            if (entity.isPresent()) {
+                ret.add((Class<? extends TreeNode>) entity.get());
+            }
+        }
+        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
+     */
+    public static Map<Class<?>, Method> getChildrenClassToMethod(final Class<?> type) {
+        checkArgument(type != null, "Target type must not be null");
+        checkArgument(Instantiable.class.isAssignableFrom(type), "Supplied type must be derived from Instantiable");
+        final Map<Class<?>, Method> ret = new HashMap<>();
+        for (final Method method : type.getMethods()) {
+            final Optional<Class<? extends Instantiable<?>>> entity = getYangModeledReturnType(method);
+            if (entity.isPresent()) {
+                ret.put(entity.get(), method);
+            }
+        }
+        return ret;
+    }
+
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    private static Optional<Class<? extends Instantiable<?>>> getYangModeledReturnType(final Method method) {
+        if ("getClass".equals(method.getName()) || !method.getName().startsWith("get")
+                || method.getParameterTypes().length > 0) {
+            return Optional.absent();
+        }
+
+        final Class returnType = method.getReturnType();
+        if (Instantiable.class.isAssignableFrom(returnType)) {
+            return Optional.of(returnType);
+        } else if (List.class.isAssignableFrom(returnType)) {
+            try {
+                return ClassLoaderUtils.withClassLoader(method.getDeclaringClass().getClassLoader(),
+                        (Callable<Optional<Class<? extends Instantiable<?>>>>) () -> {
+                            final Type listResult = ClassLoaderUtils.getFirstGenericParameter(method.getGenericReturnType());
+                            if (listResult instanceof Class
+                                    && Instantiable.class.isAssignableFrom((Class) listResult)) {
+                                return Optional.of((Class) listResult);
+                            }
+                            return Optional.absent();
+                        });
+            } catch (final 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.absent();
+    }
+
+    private static class ClassToQNameLoader extends CacheLoader<Class<?>, Optional<QName>> {
+
+        @Override
+        public Optional<QName> load(@Nonnull 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 #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
+         *            - class for resolving QName
+         * @return resolved QName
+         */
+        private static Optional<QName> resolveQNameNoCache(final Class<?> key) {
+            try {
+                final Field field = key.getField(QNAME_STATIC_FIELD_NAME);
+                final Object obj = field.get(null);
+                if (obj instanceof QName) {
+                    return Optional.of((QName) obj);
+                }
+
+            } catch (final 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.
+         * <p>
+         * If class is
+         * <ul>
+         * <li>rpc/action input: local name is "input".
+         * <li>rpc/action output: local name is "output".
+         * <li>augmentation: local name is "module name".
+         * </ul>
+         *
+         * 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 {
+                    moduleInfo = getModuleInfo(key);
+                } catch (final Exception e) {
+                    throw new IllegalStateException("Unable to get QName for " + key
+                            + ". YangModuleInfo was not found.", e);
+                }
+                final QName module = getModuleQName(moduleInfo).intern();
+                if (Augmentation.class.isAssignableFrom(key)) {
+                    return module;
+                } else if (isRpcOrActionType(key)) {
+                    final String className = key.getSimpleName();
+                    if (className.endsWith(RPC_ACTION_OUTPUT_SUFFIX)) {
+                        return QName.create(module, "output").intern();
+                    } else {
+                        return QName.create(module, "input").intern();
+                    }
+                }
+                /*
+                 * Fallback for Binding types which do 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<Class<? extends Augmentation<?>>, Augmentation<?>> getAugmentations(final Augmentable<?> input) {
+        return AugmentationFieldGetter.getGetter(input.getClass()).getAugmentations(input);
+    }
+
+    /**
+     * Determines if two augmentation classes or case classes represents same
+     * data.
+     * <p>
+     * Two augmentations or cases could be substituted only if and if:
+     * <ul>
+     * <li>Both implements same interfaces</li>
+     * <li>Both have same children</li>
+     * <li>If augmentations: Both have same augmentation target class. Target
+     * class was generated for data node in grouping.</li>
+     * <li>If cases: Both are from same choice. Choice class was generated for
+     * data node in grouping.</li>
+     * </ul>
+     * <p>
+     * <b>Explanation:</b> 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 substitution
+     * @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(final Class potential, final Class target) {
+        final HashSet<Class> subImplemented = Sets.newHashSet(potential.getInterfaces());
+        final HashSet<Class> 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 (final Method potentialMethod : potential.getMethods()) {
+            try {
+                final Method targetMethod = target.getMethod(potentialMethod.getName(), potentialMethod.getParameterTypes());
+                if (!potentialMethod.getReturnType().equals(targetMethod.getReturnType())) {
+                    return false;
+                }
+            } catch (final NoSuchMethodException e) {
+                // Counterpart method is missing, so classes could not be
+                // substituted.
+                return false;
+            } catch (final SecurityException e) {
+                throw new IllegalStateException("Could not compare methods", e);
+            }
+        }
+        return true;
+    }
+}
diff --git a/binding2/mdsal-binding2-spec/test/main/java/org/opendaylight/mdsal/binding/javav2/spec/util/AugmentationFieldGetterTest.java b/binding2/mdsal-binding2-spec/test/main/java/org/opendaylight/mdsal/binding/javav2/spec/util/AugmentationFieldGetterTest.java
new file mode 100644 (file)
index 0000000..1c9c3dd
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2017 Pantheon Technologies s.r.o. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.mdsal.binding.javav2.spec.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+import static org.opendaylight.mdsal.binding.javav2.spec.util.AugmentationFieldGetter.getGetter;
+
+import java.util.HashMap;
+import java.util.Map;
+import org.junit.Test;
+import org.opendaylight.mdsal.binding.javav2.spec.structural.Augmentation;
+import org.opendaylight.mdsal.binding.javav2.spec.structural.AugmentationHolder;
+
+public class AugmentationFieldGetterTest {
+
+    @SuppressWarnings("rawtypes")
+    @Test
+    public void getGetterTest() throws Exception {
+        assertNotNull(getGetter(AugmentationHolder.class));
+        assertTrue(getGetter(AugmentationHolder.class).getAugmentations(mock(AugmentationHolder.class)).isEmpty());
+        assertTrue(getGetter(Object.class).getAugmentations(null).isEmpty());
+        assertTrue(getGetter(TestAugmentationWrongTypeClass.class).getAugmentations(null).isEmpty());
+
+        final AugmentationFieldGetter augmentationFieldGetter = getGetter(TestAugmentationClass.class);
+        final Augmentation augmentation = mock(Augmentation.class);
+        final TestAugmentationClass testAugmentationClass = new TestAugmentationClass();
+
+        testAugmentationClass.addAugmentation(augmentation, augmentation);
+        assertNotNull(augmentationFieldGetter.getAugmentations(testAugmentationClass));
+        assertEquals(1, augmentationFieldGetter.getAugmentations(testAugmentationClass).size());
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void getWrongGetterTest() throws Exception {
+        final AugmentationFieldGetter augmentationFieldGetter = getGetter(TestAugmentationClass.class);
+        augmentationFieldGetter.getAugmentations(new String());
+        fail("Expected IllegalStateException");
+    }
+
+    @Test
+    public void getNoGetterTest() throws Exception {
+        assertTrue(getGetter(Object.class).getAugmentations(null).isEmpty());
+    }
+
+    private final class TestAugmentationClass {
+        @SuppressWarnings("rawtypes")
+        private final Map augmentation = new HashMap();
+
+        @SuppressWarnings({ "rawtypes", "unchecked" })
+        void addAugmentation(final Augmentation key, final Augmentation value) {
+            this.augmentation.put(key, value);
+        }
+    }
+
+    private final class TestAugmentationWrongTypeClass {
+        @SuppressWarnings("unused")
+        private String augmentation;
+    }
+}
diff --git a/binding2/mdsal-binding2-spec/test/main/java/org/opendaylight/mdsal/binding/javav2/spec/util/BindingReflectionsTest.java b/binding2/mdsal-binding2-spec/test/main/java/org/opendaylight/mdsal/binding/javav2/spec/util/BindingReflectionsTest.java
new file mode 100644 (file)
index 0000000..83789fc
--- /dev/null
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2017 Pantheon Technologies s.r.o. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.mdsal.binding.javav2.spec.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+
+import java.lang.reflect.Constructor;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.Future;
+import org.junit.Test;
+import org.opendaylight.mdsal.binding.javav2.spec.base.BaseIdentity;
+import org.opendaylight.mdsal.binding.javav2.spec.base.Input;
+import org.opendaylight.mdsal.binding.javav2.spec.base.Rpc;
+import org.opendaylight.mdsal.binding.javav2.spec.base.RpcCallback;
+import org.opendaylight.mdsal.binding.javav2.spec.base.TreeNode;
+import org.opendaylight.mdsal.binding.javav2.spec.structural.Augmentation;
+import org.opendaylight.mdsal.binding.javav2.spec.structural.TreeChildNode;
+import org.opendaylight.mdsal.binding.javav2.spec.util.test.mock.FooChild;
+import org.opendaylight.mdsal.binding.javav2.spec.util.test.mock.GroupingFoo;
+import org.opendaylight.yangtools.yang.common.QName;
+
+public class BindingReflectionsTest {
+
+    @Test
+    public void testBindingWithDummyObject() throws Exception {
+        assertEquals("Package name should be equal to string", "org.opendaylight.mdsal.gen.javav2.test.rev990939",
+                BindingReflections.getModelRootPackageName("org.opendaylight.mdsal.gen.javav2.test.rev990939"));
+        assertEquals("ModuleInfoClassName should be equal to string", "test.$YangModuleInfoImpl",
+                BindingReflections.getModuleInfoClassName("test"));
+        assertEquals("Module info should be empty Set", Collections.EMPTY_SET, BindingReflections.loadModuleInfos());
+        assertFalse("Should not be RpcType", BindingReflections.isRpcOrActionType(TreeNode.class));
+        assertFalse("Should not be AugmentationChild", BindingReflections.isAugmentationChild(TreeNode.class));
+        assertTrue("Should be BindingClass", BindingReflections.isBindingClass(TreeNode.class));
+        assertFalse("Should not be Notification", BindingReflections.isNotification(TreeNode.class));
+
+        assertNull(mock(TreeChildNode.class).treeParent());
+
+        assertEquals(GroupingFoo.class, BindingReflections.findHierarchicalParent(FooChild.class));
+
+        assertTrue(BindingReflections.isRpcOrActionMethod(TestImplementation.class.getDeclaredMethod("rpcMethodTest")));
+        assertEquals(TestImplementation.class, BindingReflections.findAugmentationTarget(TestImplementation.class));
+
+        assertEquals(Object.class, BindingReflections
+                .resolveRpcOutputClass(TestImplementation.class.getDeclaredMethod("rpcMethodTest")).get());
+        assertFalse(BindingReflections
+                .resolveRpcOutputClass(TestImplementation.class.getDeclaredMethod("rpcMethodTest2")).isPresent());
+
+        assertTrue(BindingReflections.getQName(TestImplementation.class).toString().equals("test"));
+    }
+
+    @SuppressWarnings("rawtypes")
+    @Test(expected = UnsupportedOperationException.class)
+    public void testPrivateConstructor() throws Throwable {
+        assertFalse(BindingReflections.class.getDeclaredConstructor().isAccessible());
+        final Constructor constructor = BindingReflections.class.getDeclaredConstructor();
+        constructor.setAccessible(true);
+        try {
+            constructor.newInstance();
+        } catch (final Exception e) {
+            throw e.getCause();
+        }
+    }
+
+    @SuppressWarnings({ "rawtypes", "unused" })
+    private static final class TestImplementation extends BaseIdentity
+            implements Augmentation<TestImplementation>, Rpc {
+
+        public static final QName QNAME = QName.create("test");
+
+        Future<List<Object>> rpcMethodTest() {
+            return null;
+        }
+
+        Future rpcMethodTest2() {
+            return null;
+        }
+
+        @Override
+        public void invoke(final Input input, final RpcCallback callback) {
+        }
+    }
+}
diff --git a/binding2/mdsal-binding2-spec/test/main/java/org/opendaylight/mdsal/binding/javav2/spec/util/test/mock/FooChild.java b/binding2/mdsal-binding2-spec/test/main/java/org/opendaylight/mdsal/binding/javav2/spec/util/test/mock/FooChild.java
new file mode 100644 (file)
index 0000000..c6743c9
--- /dev/null
@@ -0,0 +1,15 @@
+/*
+ * Copyright (c) 2017 Pantheon Technologies s.r.o. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.mdsal.binding.javav2.spec.util.test.mock;
+
+import org.opendaylight.mdsal.binding.javav2.spec.base.TreeArgument;
+import org.opendaylight.mdsal.binding.javav2.spec.structural.TreeChildNode;
+
+public interface FooChild extends TreeChildNode<GroupingFoo, TreeArgument<GroupingFoo>> {
+
+}
diff --git a/binding2/mdsal-binding2-spec/test/main/java/org/opendaylight/mdsal/binding/javav2/spec/util/test/mock/GroupingFoo.java b/binding2/mdsal-binding2-spec/test/main/java/org/opendaylight/mdsal/binding/javav2/spec/util/test/mock/GroupingFoo.java
new file mode 100644 (file)
index 0000000..795e234
--- /dev/null
@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) 2017 Pantheon Technologies s.r.o. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.mdsal.binding.javav2.spec.util.test.mock;
+
+import org.opendaylight.mdsal.binding.javav2.spec.base.TreeNode;
+
+public interface GroupingFoo extends TreeNode {
+
+}
index 6b3860e8c6225256729feb67d31d0218c2a31914..ef0067fc79118c5d7368b40eaca2cb6272c2815c 100644 (file)
@@ -47,6 +47,8 @@ public final class BindingMapping {
     public static final String MODEL_BINDING_PROVIDER_CLASS_NAME = "$YangModelBindingProvider";
     public static final String PATTERN_CONSTANT_NAME = "PATTERN_CONSTANTS";
     public static final String MEMBER_PATTERN_LIST = "patterns";
+    public static final String RPC_INPUT_SUFFIX = "Input";
+    public static final String RPC_OUTPUT_SUFFIX = "Output";
 
     private static final ThreadLocal<SimpleDateFormat> PACKAGE_DATE_FORMAT = new ThreadLocal<SimpleDateFormat>() {