Add CodecDataObjectAnalysis 28/106128/6
authorRobert Varga <robert.varga@pantheon.tech>
Mon, 22 May 2023 22:14:55 +0000 (00:14 +0200)
committerRobert Varga <robert.varga@pantheon.tech>
Wed, 24 May 2023 14:20:36 +0000 (16:20 +0200)
We will be separating out DataObjectCodecContext into two classes, and
the new one will need to be able to create proxies.

Separate out all the analytics and indexing into a separate DTO, which
can be constructed externally, so the new classes can construct them
separately.

This also necessitates splitting out bindingChildArg() into a separate
class -- but we still want to migrate that to RuntimTypes at some point.

JIRA: MDSAL-820
Change-Id: I970b8f92443a9faf57c1786239e2f552f7ff938d
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/AugmentationNodeContext.java
binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/CaseNodeCodecContext.java
binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/CodecDataObjectAnalysis.java [new file with mode: 0644]
binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/CodecItemFactory.java [new file with mode: 0644]
binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/DataContainerCodecContext.java
binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/DataObjectCodecContext.java

index 55ceb9b20f4ec598c6b098c2b6cde31b9e350af1..59f2e226e816189f3ea7bdc2a420f1e857de0fb2 100644 (file)
@@ -15,7 +15,6 @@ import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
 
 final class AugmentationNodeContext<D extends DataObject & Augmentation<?>>
         extends DataObjectCodecContext<D, AugmentRuntimeType> {
-
     AugmentationNodeContext(final DataContainerCodecPrototype<AugmentRuntimeType> prototype) {
         super(prototype);
     }
index 804640f03bbb6f0736f33077fbade2a9b45b2b6d..0352a82676e869c7ca1eef8f4ac75e7f9348fbeb 100644 (file)
@@ -12,26 +12,14 @@ import static com.google.common.base.Preconditions.checkArgument;
 import java.util.List;
 import org.opendaylight.mdsal.binding.runtime.api.CaseRuntimeType;
 import org.opendaylight.yangtools.yang.binding.DataObject;
-import org.opendaylight.yangtools.yang.binding.InstanceIdentifier.Item;
 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier.PathArgument;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
 import org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode;
 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
-import org.opendaylight.yangtools.yang.model.api.AddedByUsesAware;
-import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
 
 final class CaseNodeCodecContext<D extends DataObject> extends DataObjectCodecContext<D, CaseRuntimeType> {
     CaseNodeCodecContext(final DataContainerCodecPrototype<CaseRuntimeType> prototype) {
-        super(prototype);
-    }
-
-    @Override
-    @SuppressWarnings({ "unchecked", "rawtypes" })
-    Item<?> createBindingArg(final Class<?> childClass, final EffectiveStatement<?, ?> childSchema) {
-        // FIXME: MDSAL-697: see overridden method for further guidance
-        return childSchema instanceof AddedByUsesAware aware && aware.isAddedByUses()
-            ? Item.of((Class)getBindingClass(), (Class)childClass)
-                : super.createBindingArg(childClass, childSchema);
+        super(prototype, CodecItemFactory.of(prototype.getBindingClass()));
     }
 
     @Override
diff --git a/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/CodecDataObjectAnalysis.java b/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/CodecDataObjectAnalysis.java
new file mode 100644 (file)
index 0000000..71dd5a3
--- /dev/null
@@ -0,0 +1,213 @@
+/*
+ * Copyright (c) 2023 PANTHEON.tech, 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.dom.codec.impl;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Verify.verify;
+import static com.google.common.base.Verify.verifyNotNull;
+
+import com.google.common.base.VerifyException;
+import com.google.common.collect.ImmutableMap;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.mdsal.binding.dom.codec.impl.NodeCodecContext.CodecContextFactory;
+import org.opendaylight.mdsal.binding.model.api.JavaTypeName;
+import org.opendaylight.mdsal.binding.runtime.api.AugmentRuntimeType;
+import org.opendaylight.mdsal.binding.runtime.api.AugmentableRuntimeType;
+import org.opendaylight.mdsal.binding.runtime.api.ChoiceRuntimeType;
+import org.opendaylight.mdsal.binding.runtime.api.CompositeRuntimeType;
+import org.opendaylight.yangtools.yang.binding.Augmentable;
+import org.opendaylight.yangtools.yang.binding.DataContainer;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.OpaqueObject;
+import org.opendaylight.yangtools.yang.binding.contract.Naming;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.DistinctNodeContainer;
+
+/**
+ * Analysis of a {@link DataObject} specialization class. The primary point of this class is to separate out creation
+ * indices needed for {@link #proxyConstructor}. Since we want to perform as much indexing as possible in a single pass,
+ * we also end up indexing things that are not strictly required to arrive at that constructor.
+ */
+final class CodecDataObjectAnalysis<R extends CompositeRuntimeType> {
+    private static final MethodType CONSTRUCTOR_TYPE = MethodType.methodType(void.class,
+        DataObjectCodecContext.class, DistinctNodeContainer.class);
+    private static final MethodType DATAOBJECT_TYPE = MethodType.methodType(DataObject.class,
+        DataObjectCodecContext.class, DistinctNodeContainer.class);
+
+    final @NonNull ImmutableMap<Class<?>, DataContainerCodecPrototype<?>> byStreamClass;
+    final @NonNull ImmutableMap<Class<?>, DataContainerCodecPrototype<?>> byBindingArgClass;
+    final @NonNull ImmutableMap<PathArgument, NodeContextSupplier> byYang;
+    final @NonNull ImmutableMap<String, ValueNodeCodecContext> leafNodes;
+    final @NonNull Class<? extends CodecDataObject<?>> generatedClass;
+    final @NonNull List<AugmentRuntimeType> possibleAugmentations;
+    final @NonNull MethodHandle proxyConstructor;
+
+    CodecDataObjectAnalysis(final DataContainerCodecPrototype<R> prototype, final CodecItemFactory itemFactory,
+            final Method keyMethod) {
+        // Preliminaries from prototype
+        @SuppressWarnings("unchecked")
+        final Class<DataObject> bindingClass = Class.class.cast(prototype.getBindingClass());
+        final var runtimeType = prototype.getType();
+        final var factory = prototype.getFactory();
+        final var leafContexts = factory.getLeafNodes(bindingClass, runtimeType.statement());
+
+        // Reflection-based on the passed class
+        final var clsToMethod = getChildrenClassToMethod(bindingClass);
+
+        // Indexing part: be very careful around what gets done when
+        final var byYangBuilder = new HashMap<PathArgument, NodeContextSupplier>();
+
+        // Step 1: add leaf children
+        var leafBuilder = ImmutableMap.<String, ValueNodeCodecContext>builderWithExpectedSize(leafContexts.size());
+        for (var leaf : leafContexts.values()) {
+            leafBuilder.put(leaf.getSchema().getQName().getLocalName(), leaf);
+            byYangBuilder.put(leaf.getDomPathArgument(), leaf);
+        }
+        leafNodes = leafBuilder.build();
+
+        final var byBindingArgClassBuilder = new HashMap<Class<?>, DataContainerCodecPrototype<?>>();
+        final var byStreamClassBuilder = new HashMap<Class<?>, DataContainerCodecPrototype<?>>();
+        final var daoProperties = new HashMap<Class<?>, PropertyInfo>();
+        for (var childDataObj : clsToMethod.entrySet()) {
+            final var method = childDataObj.getValue();
+            verify(!method.isDefault(), "Unexpected default method %s in %s", method, bindingClass);
+
+            final var retClass = childDataObj.getKey();
+            if (OpaqueObject.class.isAssignableFrom(retClass)) {
+                // Filter OpaqueObjects, they are not containers
+                continue;
+            }
+
+            // Record getter method
+            daoProperties.put(retClass, new PropertyInfo.Getter(method));
+
+            final var childProto = getChildPrototype(runtimeType, factory, itemFactory, retClass);
+            byStreamClassBuilder.put(childProto.getBindingClass(), childProto);
+            byYangBuilder.put(childProto.getYangArg(), childProto);
+
+            // FIXME: It really feels like we should be specializing DataContainerCodecPrototype so as to ditch
+            //        createInstance() and then we could do an instanceof check instead.
+            if (childProto.getType() instanceof ChoiceRuntimeType) {
+                final var choice = (ChoiceNodeCodecContext<?>) childProto.get();
+                for (var cazeChild : choice.getCaseChildrenClasses()) {
+                    byBindingArgClassBuilder.put(cazeChild, childProto);
+                }
+            }
+        }
+
+        // Snapshot before below processing
+        byStreamClass = ImmutableMap.copyOf(byStreamClassBuilder);
+
+        // Slight footprint optimization: we do not want to copy byStreamClass, as that would force its entrySet view
+        // to be instantiated. Furthermore the two maps can easily end up being equal -- hence we can reuse
+        // byStreamClass for the purposes of both.
+        byBindingArgClassBuilder.putAll(byStreamClassBuilder);
+        byBindingArgClass = byStreamClassBuilder.equals(byBindingArgClassBuilder) ? byStreamClass
+            : ImmutableMap.copyOf(byBindingArgClassBuilder);
+
+        // Find all non-default nonnullFoo() methods and update the corresponding property info
+        for (var entry : getChildrenClassToNonnullMethod(bindingClass).entrySet()) {
+            final var method = entry.getValue();
+            if (!method.isDefault()) {
+                daoProperties.compute(entry.getKey(), (key, value) -> new PropertyInfo.GetterAndNonnull(
+                    verifyNotNull(value, "No getter for %s", key).getterMethod(), method));
+            }
+        }
+        // At this point all indexing is done: byYangBuilder should not be referenced
+        byYang = ImmutableMap.copyOf(byYangBuilder);
+
+        // Final bits: generate the appropriate class, As a side effect we identify what Augmentations are possible
+        if (Augmentable.class.isAssignableFrom(bindingClass)) {
+            // Verify we have the appropriate backing runtimeType
+            if (!(runtimeType instanceof AugmentableRuntimeType augmentableRuntimeType)) {
+                throw new VerifyException(
+                    "Unexpected type %s backing augmenable %s".formatted(runtimeType, bindingClass));
+            }
+
+            possibleAugmentations = augmentableRuntimeType.augments();
+            generatedClass = CodecDataObjectGenerator.generateAugmentable(prototype.getFactory().getLoader(),
+                bindingClass, leafContexts, daoProperties, keyMethod);
+        } else {
+            possibleAugmentations = List.of();
+            generatedClass = CodecDataObjectGenerator.generate(prototype.getFactory().getLoader(), bindingClass,
+                leafContexts, daoProperties, keyMethod);
+        }
+
+        // All done: acquire the constructor: it is supposed to be public
+        final MethodHandle ctor;
+        try {
+            ctor = MethodHandles.publicLookup().findConstructor(generatedClass, CONSTRUCTOR_TYPE);
+        } catch (NoSuchMethodException | IllegalAccessException e) {
+            throw new LinkageError("Failed to find contructor for class " + generatedClass, e);
+        }
+
+        proxyConstructor = ctor.asType(DATAOBJECT_TYPE);
+    }
+
+    private static @NonNull DataContainerCodecPrototype<?> getChildPrototype(final CompositeRuntimeType type,
+            final CodecContextFactory factory, final CodecItemFactory itemFactory,
+            final Class<? extends DataContainer> childClass) {
+        final var child = type.bindingChild(JavaTypeName.create(childClass));
+        if (child == null) {
+            throw DataContainerCodecContext.childNullException(factory.getRuntimeContext(), childClass,
+                "Node %s does not have child named %s", type, childClass);
+        }
+
+        return DataContainerCodecPrototype.from(itemFactory.createItem(childClass, child.statement()),
+            (CompositeRuntimeType) child, factory);
+    }
+
+
+    // FIXME: MDSAL-780: these methods perform analytics using java.lang.reflect to acquire the basic shape of the
+    //                   class. This is not exactly AOT friendly, as most of the information should be provided by
+    //                   CompositeRuntimeType.
+    //
+    //                   As as first cut, CompositeRuntimeType should provide the mapping between YANG children and the
+    //                   corresponding GETTER_PREFIX/NONNULL_PREFIX method names, If we have that, the use in this
+    //                   class should be fine.
+    //
+    //                   The second cut is binding the actual Method invocations, which is fine here, as this class is
+    //                   all about having a run-time generated class. AOT would be providing an alternative, where the
+    //                   equivalent class would be generated at compile-time and hence would bind to the methods
+    //                   directly -- and AOT equivalent of this class would really be a compile-time generated registry
+    //                   to those classes' entry points.
+
+    /**
+     * 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
+     */
+    private static Map<Class<? extends DataContainer>, Method> getChildrenClassToMethod(final Class<?> type) {
+        return getChildClassToMethod(type, Naming.GETTER_PREFIX);
+    }
+
+    private static Map<Class<? extends DataContainer>, Method> getChildrenClassToNonnullMethod(final Class<?> type) {
+        return getChildClassToMethod(type, Naming.NONNULL_PREFIX);
+    }
+
+    private static Map<Class<? extends DataContainer>, Method> getChildClassToMethod(final Class<?> type,
+            final String prefix) {
+        checkArgument(type != null, "Target type must not be null");
+        checkArgument(DataContainer.class.isAssignableFrom(type), "Supplied type %s must be derived from DataContainer",
+            type);
+        final var ret = new HashMap<Class<? extends DataContainer>, Method>();
+        for (Method method : type.getMethods()) {
+            DataContainerCodecContext.getYangModeledReturnType(method, prefix)
+                .ifPresent(entity -> ret.put(entity, method));
+        }
+        return ret;
+    }
+}
diff --git a/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/CodecItemFactory.java b/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/CodecItemFactory.java
new file mode 100644 (file)
index 0000000..10a3248
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2023 PANTHEON.tech, 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.dom.codec.impl;
+
+import static java.util.Objects.requireNonNull;
+
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier.Item;
+import org.opendaylight.yangtools.yang.model.api.AddedByUsesAware;
+import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
+
+sealed class CodecItemFactory {
+    private static final class Case extends CodecItemFactory {
+        private final Class<?> bindingClass;
+
+        Case(final Class<?> bindingClass) {
+            this.bindingClass = requireNonNull(bindingClass);
+        }
+
+        @Override
+        @SuppressWarnings({ "rawtypes", "unchecked" })
+        Item<?> createItem(final Class<?> childClass, final EffectiveStatement<?, ?> childSchema) {
+            // FIXME: MDSAL-697: see overridden method for further guidance
+            return childSchema instanceof AddedByUsesAware aware && aware.isAddedByUses()
+                ? Item.of((Class) bindingClass, (Class) childClass) : super.createItem(childClass, childSchema);
+        }
+    }
+
+    private static final @NonNull CodecItemFactory DEFAULT = new CodecItemFactory();
+
+    private CodecItemFactory() {
+        // Hidden on purpose
+    }
+
+    // FIXME: MDSAL-697: move this method into BindingRuntimeContext
+    //        This method is only called from loadChildPrototype() and exists only to be overridden by
+    //        CaseNodeCodecContext. Since we are providing childClass and our schema to BindingRuntimeContext and
+    //        receiving childSchema from it via findChildSchemaDefinition, we should be able to receive the equivalent
+    //        of Map.Entry<Item, DataSchemaNode>, along with the override we create here. One more input we may need to
+    //        provide is our bindingClass().
+    @SuppressWarnings("unchecked")
+    Item<?> createItem(final Class<?> childClass, final EffectiveStatement<?, ?> childSchema) {
+        return Item.of((Class<? extends DataObject>) childClass);
+    }
+
+    static @NonNull CodecItemFactory of() {
+        return DEFAULT;
+    }
+
+    static @NonNull CodecItemFactory of(final Class<?> bindingClass) {
+        return new Case(bindingClass);
+    }
+}
index 7a92d50219821ac690f003bf753b5d387df855e9..27a565652faa37a92cf39f137777c89a9fc98f21 100644 (file)
@@ -227,7 +227,7 @@ abstract class DataContainerCodecContext<D extends DataObject, T extends Runtime
     }
 
     @CheckReturnValue
-    private static @NonNull IllegalArgumentException childNullException(final BindingRuntimeContext runtimeContext,
+    static @NonNull IllegalArgumentException childNullException(final BindingRuntimeContext runtimeContext,
             final Class<?> childClass, final String message, final Object... args) {
         final CompositeRuntimeType schema;
         if (Augmentation.class.isAssignableFrom(childClass)) {
index d859ec8f2730437248dc9729e2ecb5f7d9839d66..9c941089fd7a434d35072f7005a708d5ff8842fc 100644 (file)
@@ -8,44 +8,32 @@
 package org.opendaylight.mdsal.binding.dom.codec.impl;
 
 import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Verify.verify;
-import static com.google.common.base.Verify.verifyNotNull;
 
 import com.google.common.annotations.Beta;
 import com.google.common.base.Throwables;
 import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableMap.Builder;
 import com.google.common.collect.ImmutableSet;
 import java.lang.invoke.MethodHandle;
 import java.lang.invoke.MethodHandles;
-import java.lang.invoke.MethodType;
 import java.lang.invoke.VarHandle;
 import java.lang.reflect.Method;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Map.Entry;
 import java.util.Optional;
 import org.eclipse.jdt.annotation.NonNull;
 import org.eclipse.jdt.annotation.Nullable;
 import org.opendaylight.mdsal.binding.dom.codec.api.IncorrectNestingException;
 import org.opendaylight.mdsal.binding.model.api.GeneratedType;
-import org.opendaylight.mdsal.binding.model.api.JavaTypeName;
 import org.opendaylight.mdsal.binding.model.api.Type;
 import org.opendaylight.mdsal.binding.runtime.api.AugmentRuntimeType;
 import org.opendaylight.mdsal.binding.runtime.api.AugmentableRuntimeType;
 import org.opendaylight.mdsal.binding.runtime.api.BindingRuntimeContext;
-import org.opendaylight.mdsal.binding.runtime.api.ChoiceRuntimeType;
 import org.opendaylight.mdsal.binding.runtime.api.CompositeRuntimeType;
 import org.opendaylight.mdsal.binding.spec.reflect.BindingReflections;
-import org.opendaylight.yangtools.yang.binding.Augmentable;
 import org.opendaylight.yangtools.yang.binding.Augmentation;
-import org.opendaylight.yangtools.yang.binding.DataContainer;
 import org.opendaylight.yangtools.yang.binding.DataObject;
 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
-import org.opendaylight.yangtools.yang.binding.InstanceIdentifier.Item;
-import org.opendaylight.yangtools.yang.binding.OpaqueObject;
-import org.opendaylight.yangtools.yang.binding.contract.Naming;
 import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
@@ -56,7 +44,6 @@ import org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode;
 import org.opendaylight.yangtools.yang.data.api.schema.DistinctNodeContainer;
 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
 import org.opendaylight.yangtools.yang.model.api.DocumentedNode.WithStatus;
-import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaTreeEffectiveStatement;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -68,10 +55,7 @@ import org.slf4j.LoggerFactory;
 public abstract class DataObjectCodecContext<D extends DataObject, T extends CompositeRuntimeType>
         extends DataContainerCodecContext<D, T> {
     private static final Logger LOG = LoggerFactory.getLogger(DataObjectCodecContext.class);
-    private static final MethodType CONSTRUCTOR_TYPE = MethodType.methodType(void.class,
-        DataObjectCodecContext.class, DistinctNodeContainer.class);
-    private static final MethodType DATAOBJECT_TYPE = MethodType.methodType(DataObject.class,
-        DataObjectCodecContext.class, DistinctNodeContainer.class);
+
     private static final VarHandle MISMATCHED_AUGMENTED;
 
     static {
@@ -98,104 +82,40 @@ public abstract class DataObjectCodecContext<D extends DataObject, T extends Com
     private volatile ImmutableMap<Class<?>, DataContainerCodecPrototype<?>> mismatchedAugmented = ImmutableMap.of();
 
     DataObjectCodecContext(final DataContainerCodecPrototype<T> prototype) {
-        this(prototype, null);
+        this(prototype, CodecItemFactory.of());
     }
 
-    DataObjectCodecContext(final DataContainerCodecPrototype<T> prototype, final Method keyMethod) {
-        super(prototype);
-
-        final Class<D> bindingClass = getBindingClass();
-
-        final ImmutableMap<Method, ValueNodeCodecContext> tmpLeaves = factory().getLeafNodes(bindingClass,
-            getType().statement());
-        final Map<Class<? extends DataContainer>, Method> clsToMethod = getChildrenClassToMethod(bindingClass);
-
-        final Map<YangInstanceIdentifier.PathArgument, NodeContextSupplier> byYangBuilder = new HashMap<>();
-        final Map<Class<?>, DataContainerCodecPrototype<?>> byStreamClassBuilder = new HashMap<>();
-        final Map<Class<?>, DataContainerCodecPrototype<?>> byBindingArgClassBuilder = new HashMap<>();
-
-        // Adds leaves to mapping
-        final Builder<String, ValueNodeCodecContext> leafChildBuilder =
-                ImmutableMap.builderWithExpectedSize(tmpLeaves.size());
-        for (final ValueNodeCodecContext leaf : tmpLeaves.values()) {
-            leafChildBuilder.put(leaf.getSchema().getQName().getLocalName(), leaf);
-            byYangBuilder.put(leaf.getDomPathArgument(), leaf);
-        }
-        this.leafChild = leafChildBuilder.build();
-
-        final Map<Class<?>, PropertyInfo> daoProperties = new HashMap<>();
-        for (final Entry<Class<? extends DataContainer>, Method> childDataObj : clsToMethod.entrySet()) {
-            final Method method = childDataObj.getValue();
-            verify(!method.isDefault(), "Unexpected default method %s in %s", method, bindingClass);
-
-            final Class<? extends DataContainer> retClass = childDataObj.getKey();
-            if (OpaqueObject.class.isAssignableFrom(retClass)) {
-                // Filter OpaqueObjects, they are not containers
-                continue;
-            }
-
-            // Record getter method
-            daoProperties.put(retClass, new PropertyInfo.Getter(method));
-
-            final DataContainerCodecPrototype<?> childProto = loadChildPrototype(retClass);
-            byStreamClassBuilder.put(childProto.getBindingClass(), childProto);
-            byYangBuilder.put(childProto.getYangArg(), childProto);
-
-            // FIXME: It really feels like we should be specializing DataContainerCodecPrototype so as to ditch
-            //        createInstance() and then we could do an instanceof check instead.
-            if (childProto.getType() instanceof ChoiceRuntimeType) {
-                final ChoiceNodeCodecContext<?> choice = (ChoiceNodeCodecContext<?>) childProto.get();
-                for (final Class<?> cazeChild : choice.getCaseChildrenClasses()) {
-                    byBindingArgClassBuilder.put(cazeChild, childProto);
-                }
-            }
-        }
+    DataObjectCodecContext(final DataContainerCodecPrototype<T> prototype, final CodecItemFactory itemFactory) {
+        this(prototype, new CodecDataObjectAnalysis<>(prototype, itemFactory, null));
+    }
 
-        // Find all non-default nonnullFoo() methods and update the corresponding property info
-        for (var entry : getChildrenClassToNonnullMethod(bindingClass).entrySet()) {
-            final var method = entry.getValue();
-            if (!method.isDefault()) {
-                daoProperties.compute(entry.getKey(), (key, value) -> new PropertyInfo.GetterAndNonnull(
-                    verifyNotNull(value, "No getter for %s", key).getterMethod(), method));
-            }
-        }
+    DataObjectCodecContext(final DataContainerCodecPrototype<T> prototype, final Method keyMethod) {
+        this(prototype, new CodecDataObjectAnalysis<>(prototype, CodecItemFactory.of(), keyMethod));
+    }
 
-        this.byYang = ImmutableMap.copyOf(byYangBuilder);
-        this.byStreamClass = ImmutableMap.copyOf(byStreamClassBuilder);
-
-        // Slight footprint optimization: we do not want to copy byStreamClass, as that would force its entrySet view
-        // to be instantiated. Furthermore the two maps can easily end up being equal -- hence we can reuse
-        // byStreamClass for the purposes of both.
-        byBindingArgClassBuilder.putAll(byStreamClassBuilder);
-        this.byBindingArgClass = byStreamClassBuilder.equals(byBindingArgClassBuilder) ? this.byStreamClass
-                : ImmutableMap.copyOf(byBindingArgClassBuilder);
-
-        final List<AugmentRuntimeType> possibleAugmentations;
-        if (Augmentable.class.isAssignableFrom(bindingClass)) {
-            // Verify we have the appropriate backing runtimeType
-            final var type = getType();
-            verify(type instanceof AugmentableRuntimeType, "Unexpected type %s backing augmenable %s", type,
-                bindingClass);
-            possibleAugmentations = ((AugmentableRuntimeType) type).augments();
-            generatedClass = CodecDataObjectGenerator.generateAugmentable(prototype.getFactory().getLoader(),
-                bindingClass, tmpLeaves, daoProperties, keyMethod);
-        } else {
-            possibleAugmentations = List.of();
-            generatedClass = CodecDataObjectGenerator.generate(prototype.getFactory().getLoader(), bindingClass,
-                tmpLeaves, daoProperties, keyMethod);
-        }
+    private DataObjectCodecContext(final DataContainerCodecPrototype<T> prototype,
+            final CodecDataObjectAnalysis<T> analysis) {
+        super(prototype);
 
-        // Iterate over all possible augmentations, indexing them as needed
-        final Map<PathArgument, DataContainerCodecPrototype<?>> augByYang = new HashMap<>();
-        final Map<Class<?>, DataContainerCodecPrototype<?>> augByStream = new HashMap<>();
-        for (final AugmentRuntimeType augment : possibleAugmentations) {
-            final DataContainerCodecPrototype<?> augProto = loadAugmentPrototype(augment);
+        // Inherit analysis stuff
+        leafChild = analysis.leafNodes;
+        proxyConstructor = analysis.proxyConstructor;
+        generatedClass = analysis.generatedClass;
+        byBindingArgClass = analysis.byBindingArgClass;
+        byStreamClass = analysis.byStreamClass;
+        byYang = analysis.byYang;
+
+        // Deal with augmentations, which are not something we analysis provides
+        final var augByYang = new HashMap<PathArgument, DataContainerCodecPrototype<?>>();
+        final var augByStream = new HashMap<Class<?>, DataContainerCodecPrototype<?>>();
+        for (var augment : analysis.possibleAugmentations) {
+            final var augProto = loadAugmentPrototype(augment);
             if (augProto != null) {
-                final PathArgument augYangArg = augProto.getYangArg();
+                final var augYangArg = augProto.getYangArg();
                 if (augByYang.putIfAbsent(augYangArg, augProto) == null) {
                     LOG.trace("Discovered new YANG mapping {} -> {} in {}", augYangArg, augProto, this);
                 }
-                final Class<?> augBindingClass = augProto.getBindingClass();
+                final var augBindingClass = augProto.getBindingClass();
                 if (augByStream.putIfAbsent(augBindingClass, augProto) == null) {
                     LOG.trace("Discovered new class mapping {} -> {} in {}", augBindingClass, augProto, this);
                 }
@@ -203,15 +123,6 @@ public abstract class DataObjectCodecContext<D extends DataObject, T extends Com
         }
         augmentationByYang = ImmutableMap.copyOf(augByYang);
         augmentationByStream = ImmutableMap.copyOf(augByStream);
-
-        final MethodHandle ctor;
-        try {
-            ctor = MethodHandles.publicLookup().findConstructor(generatedClass, CONSTRUCTOR_TYPE);
-        } catch (NoSuchMethodException | IllegalAccessException e) {
-            throw new LinkageError("Failed to find contructor for class " + generatedClass, e);
-        }
-
-        proxyConstructor = ctor.asType(DATAOBJECT_TYPE);
     }
 
     @Override
@@ -297,31 +208,11 @@ public abstract class DataObjectCodecContext<D extends DataObject, T extends Com
     protected final ValueNodeCodecContext getLeafChild(final String name) {
         final ValueNodeCodecContext value = leafChild.get(name);
         if (value == null) {
-            throw IncorrectNestingException.create("Leaf %s is not valid for %s", name, getBindingClass());
+            throw new IncorrectNestingException("Leaf %s is not valid for %s", name, getBindingClass());
         }
         return value;
     }
 
-    private DataContainerCodecPrototype<?> loadChildPrototype(final Class<? extends DataContainer> childClass) {
-        final var type = getType();
-        final var child = childNonNull(type.bindingChild(JavaTypeName.create(childClass)), childClass,
-            "Node %s does not have child named %s", type, childClass);
-
-        return DataContainerCodecPrototype.from(createBindingArg(childClass, child.statement()),
-            (CompositeRuntimeType) child, factory());
-    }
-
-    // FIXME: MDSAL-697: move this method into BindingRuntimeContext
-    //                   This method is only called from loadChildPrototype() and exists only to be overridden by
-    //                   CaseNodeCodecContext. Since we are providing childClass and our schema to BindingRuntimeContext
-    //                   and receiving childSchema from it via findChildSchemaDefinition, we should be able to receive
-    //                   the equivalent of Map.Entry<Item, DataSchemaNode>, along with the override we create here. One
-    //                   more input we may need to provide is our bindingClass().
-    @SuppressWarnings("unchecked")
-    Item<?> createBindingArg(final Class<?> childClass, final EffectiveStatement<?, ?> childSchema) {
-        return Item.of((Class<? extends DataObject>) childClass);
-    }
-
     private @Nullable DataContainerCodecPrototype<?> augmentationByClass(final @NonNull Class<?> childClass) {
         final DataContainerCodecPrototype<?> childProto = augmentationByStream.get(childClass);
         return childProto != null ? childProto : mismatchedAugmentationByClass(childClass);
@@ -482,32 +373,4 @@ public abstract class DataObjectCodecContext<D extends DataObject, T extends Com
         return getDomPathArgument();
     }
 
-    /**
-     * 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
-     */
-    // FIXME: MDSAL-780: replace use of this method
-    private static Map<Class<? extends DataContainer>, Method> getChildrenClassToMethod(final Class<?> type) {
-        return getChildClassToMethod(type, Naming.GETTER_PREFIX);
-    }
-
-    // FIXME: MDSAL-780: replace use of this method
-    private static Map<Class<? extends DataContainer>, Method> getChildrenClassToNonnullMethod(final Class<?> type) {
-        return getChildClassToMethod(type, Naming.NONNULL_PREFIX);
-    }
-
-    // FIXME: MDSAL-780: replace use of this method
-    private static Map<Class<? extends DataContainer>, Method> getChildClassToMethod(final Class<?> type,
-            final String prefix) {
-        checkArgument(type != null, "Target type must not be null");
-        checkArgument(DataContainer.class.isAssignableFrom(type), "Supplied type %s must be derived from DataContainer",
-            type);
-        final var ret = new HashMap<Class<? extends DataContainer>, Method>();
-        for (Method method : type.getMethods()) {
-            getYangModeledReturnType(method, prefix).ifPresent(entity -> ret.put(entity, method));
-        }
-        return ret;
-    }
 }