Generate DataObject codec implementation 54/81654/30
authorRobert Varga <robert.varga@pantheon.tech>
Mon, 15 Apr 2019 11:50:58 +0000 (13:50 +0200)
committerRobert Varga <robert.varga@pantheon.tech>
Wed, 24 Apr 2019 08:59:27 +0000 (10:59 +0200)
With the advent of CodecClassLoader we can safely instantiate
codec-specific implementations of binding interfaces instead
of relying on dynamic proxies.

This has several advantages:
- we do not generate a proxy class in public space, hence our mess
  will undergo normal GC rules
- generated code is a normal final class, hence we can inherit
  method implementations from interfaces and subclasses
- for each instance, we do not require a proxy and an invocation
  handler, so we instantiate only one object
- there is no reflection in the invocation path, hence JIT has
  full (and direct) visibility and can perform all of its magic

This patch DataObject support to use this approach, with
(Augmentable)CodecDataObject forming the base class.

The result is a bit more complex than the OpaqueObject case, but
also offers several advantages over the LazyDataObject approach
it replaces:
- nonnullFoo() methods are inherited from the binding interface,
  hence the presence of binding-dom-codec does not prevent CHA
  analysis from concluding there is only a single implementation
  -- thus allowing rapid devirtualization. This also means that
  DataContainerCodecContext no longer needs to track those methods
  or do anything.
- Augmentable interfaces are supported by a separate superclass,
  which implements AugmentationHolder, resulting in augmentations
  field being held only if the binding interface is augmentable
- getters and Identifiable.key() values are held in dedicated
  fields of the generated object. This leads to them being properly
  cached in all cases, hence invoking a getter after an
  equals/hashCode() operation will reuse the cached value if it
  was populated
- since we are using plain fields, data access does not involve
  walking a ConcurrentHashMap, further improving performance

The amount of generated code is kept at a minimum -- methods
just invoke CodecDataObject's method, passing it the ARFU
corresponding to the field as a constant -- and rely on the
compiler to inline the call, so that constants are propagated.

JIRA: MDSAL-442
Change-Id: I6acc34ab6b2cc62fd26dc34275259494ac4541fb
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/AugmentableCodecDataObject.java [new file with mode: 0644]
binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/BindingCodecContext.java
binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/CodecDataObject.java [new file with mode: 0644]
binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/CodecDataObjectCustomizer.java [new file with mode: 0644]
binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/DataObjectCodecContext.java
binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/KeyedListNodeCodecContext.java
binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/LazyDataObject.java [deleted file]
binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/ListNodeCodecContext.java
binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/NodeCodecContext.java

diff --git a/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/AugmentableCodecDataObject.java b/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/AugmentableCodecDataObject.java
new file mode 100644 (file)
index 0000000..dcc5e5b
--- /dev/null
@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) 2019 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 com.google.common.base.MoreObjects.ToStringHelper;
+import com.google.common.collect.ImmutableClassToInstanceMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
+import org.eclipse.jdt.annotation.Nullable;
+import org.opendaylight.mdsal.binding.dom.codec.util.AugmentationReader;
+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.AugmentationHolder;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer;
+
+/**
+ * A base class for {@link DataObject}s which are also {@link Augmentable}, backed by {@link DataObjectCodecContext}.
+ * While this class is public, it not part of API surface and is an implementation detail. The only reason for it being
+ * public is that it needs to be accessible by code generated at runtime.
+ *
+ * @param <T> DataObject type
+ */
+public abstract class AugmentableCodecDataObject<T extends DataObject & Augmentable<T>>
+        extends CodecDataObject<T> implements Augmentable<T>, AugmentationHolder<T> {
+    @SuppressWarnings("rawtypes")
+    private static final AtomicReferenceFieldUpdater<AugmentableCodecDataObject, ImmutableClassToInstanceMap>
+            CACHED_AUGMENTATIONS_UPDATER = AtomicReferenceFieldUpdater.newUpdater(AugmentableCodecDataObject.class,
+                ImmutableClassToInstanceMap.class, "cachedAugmentations");
+    private volatile ImmutableClassToInstanceMap<Augmentation<T>> cachedAugmentations;
+
+    public AugmentableCodecDataObject(final DataObjectCodecContext<T, ?> ctx,
+            final NormalizedNodeContainer<?, ?, ?> data) {
+        super(ctx, data);
+    }
+
+    @Override
+    public final <A extends Augmentation<T>> @Nullable A augmentation(final Class<A> augmentationType) {
+        requireNonNull(augmentationType, "Supplied augmentation must not be null.");
+
+        final ImmutableClassToInstanceMap<Augmentation<T>> aug = cachedAugmentations;
+        if (aug != null) {
+            return aug.getInstance(augmentationType);
+        }
+
+        @SuppressWarnings({"unchecked","rawtypes"})
+        final Optional<DataContainerCodecContext<?, ?>> optAugCtx = context.possibleStreamChild(
+            (Class) augmentationType);
+        if (optAugCtx.isPresent()) {
+            final DataContainerCodecContext<?, ?> augCtx = optAugCtx.get();
+            // Due to binding specification not representing grouping instantiations we can end up having the same
+            // augmentation applied to a grouping multiple times. While these augmentations have the same shape, they
+            // are still represented by distinct binding classes and therefore we need to make sure the result matches
+            // the augmentation the user is requesting -- otherwise a strict receiver would end up with a cryptic
+            // ClassCastException.
+            if (augmentationType.isAssignableFrom(augCtx.getBindingClass())) {
+                final Optional<NormalizedNode<?, ?>> augData = codecData().getChild(augCtx.getDomPathArgument());
+                if (augData.isPresent()) {
+                    return (A) augCtx.deserialize(augData.get());
+                }
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public final Map<Class<? extends Augmentation<T>>, Augmentation<T>> augmentations() {
+        ImmutableClassToInstanceMap<Augmentation<T>> local = cachedAugmentations;
+        if (local != null) {
+            return local;
+        }
+
+        local = ImmutableClassToInstanceMap.copyOf(context.getAllAugmentationsFrom(codecData()));
+        return CACHED_AUGMENTATIONS_UPDATER.compareAndSet(this, null, local) ? local : cachedAugmentations;
+    }
+
+    @Override
+    final int codecAugmentedHashCode() {
+        return 31 * super.codecAugmentedHashCode() + augmentations().hashCode();
+    }
+
+    @Override
+    final boolean codecAugmentedEquals(final T other) {
+        return super.codecAugmentedEquals(other) && augmentations().equals(getAllAugmentations(other));
+    }
+
+    @Override
+    final ToStringHelper codecAugmentedFillToString(final ToStringHelper helper) {
+        return super.codecAugmentedFillToString(helper).add("augmentations", augmentations());
+    }
+
+    private static Map<Class<? extends Augmentation<?>>, Augmentation<?>> getAllAugmentations(
+            final Augmentable<?> dataObject) {
+        if (dataObject instanceof AugmentationReader) {
+            return ((AugmentationReader) dataObject).getAugmentations(dataObject);
+        }
+        return BindingReflections.getAugmentations(dataObject);
+    }
+}
index f8f06fbea76dea83f7b3f3d9c63b934d39398cbe..1078bda96496e8ac0d1f959c4d5bf2ebc76b0f1d 100644 (file)
@@ -73,7 +73,7 @@ import org.slf4j.LoggerFactory;
 final class BindingCodecContext implements CodecContextFactory, BindingCodecTree, Immutable {
     private static final Logger LOG = LoggerFactory.getLogger(BindingCodecContext.class);
 
-    private final CodecClassLoader loader = StaticClassPool.createLoader();
+    private final @NonNull CodecClassLoader loader = StaticClassPool.createLoader();
     private final InstanceIdentifierCodec instanceIdentifierCodec;
     private final IdentityCodec identityCodec;
     private final BindingNormalizedNodeCodecRegistry registry;
@@ -93,6 +93,11 @@ final class BindingCodecContext implements CodecContextFactory, BindingCodecTree
         return context;
     }
 
+    @Override
+    public CodecClassLoader getLoader() {
+        return loader;
+    }
+
     InstanceIdentifierCodec getInstanceIdentifierCodec() {
         return instanceIdentifierCodec;
     }
diff --git a/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/CodecDataObject.java b/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/CodecDataObject.java
new file mode 100644 (file)
index 0000000..a11293f
--- /dev/null
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2019 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 com.google.common.base.MoreObjects;
+import com.google.common.base.MoreObjects.ToStringHelper;
+import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer;
+
+/**
+ * A base class for {@link DataObject}s backed by {@link DataObjectCodecContext}. While this class is public, it not
+ * part of API surface and is an implementation detail. The only reason for it being public is that it needs to be
+ * accessible by code generated at runtime.
+ *
+ * @param <T> DataObject type
+ */
+public abstract class CodecDataObject<T extends DataObject> implements DataObject {
+    private static final @NonNull Object NULL_VALUE = new Object();
+
+    @SuppressWarnings("rawtypes")
+    private final @NonNull NormalizedNodeContainer data;
+    final @NonNull DataObjectCodecContext<T, ?> context;
+
+    private volatile Integer cachedHashcode = null;
+
+    public CodecDataObject(final DataObjectCodecContext<T, ?> ctx, final NormalizedNodeContainer<?, ?, ?> data) {
+        this.context = requireNonNull(ctx, "Context must not be null");
+        this.data = requireNonNull(data, "Data must not be null");
+    }
+
+    @Override
+    public final int hashCode() {
+        final Integer cached = cachedHashcode;
+        if (cached != null) {
+            return cached;
+        }
+
+        final int result = codecAugmentedHashCode();
+        cachedHashcode = result;
+        return result;
+    }
+
+    @Override
+    public final boolean equals(final Object obj) {
+        if (obj == this) {
+            return true;
+        }
+        final Class<? extends DataObject> iface = implementedInterface();
+        if (!iface.isInstance(obj)) {
+            return false;
+        }
+        @SuppressWarnings("unchecked")
+        final T other = (T) iface.cast(obj);
+        if (other instanceof CodecDataObject) {
+            return data.equals(((CodecDataObject<?>) obj).data);
+        }
+        return codecAugmentedEquals(other);
+    }
+
+    @Override
+    public final String toString() {
+        return codecAugmentedFillToString(MoreObjects.toStringHelper(implementedInterface()).omitNullValues())
+                .toString();
+    }
+
+    // TODO: consider switching to VarHandles for Java 9+
+    protected final Object codecMember(final AtomicReferenceFieldUpdater<CodecDataObject<?>, Object> updater,
+            final String methodName) {
+        final Object cached = updater.get(this);
+        return cached != null ? unmaskNull(cached) : loadMember(updater, methodName);
+    }
+
+    protected abstract int codecHashCode();
+
+    protected abstract boolean codecEquals(T other);
+
+    protected abstract ToStringHelper codecFillToString(ToStringHelper helper);
+
+    @SuppressWarnings("rawtypes")
+    final @NonNull NormalizedNodeContainer codecData() {
+        return data;
+    }
+
+    // Non-final to allow specialization in AugmentableCodecDataObject
+    int codecAugmentedHashCode() {
+        return codecHashCode();
+    }
+
+    // Non-final to allow specialization in AugmentableCodecDataObject
+    boolean codecAugmentedEquals(final T other) {
+        return codecEquals(other);
+    }
+
+    // Non-final to allow specialization in AugmentableCodecDataObject
+    ToStringHelper codecAugmentedFillToString(final ToStringHelper helper) {
+        return codecFillToString(helper);
+    }
+
+    // Helpers split out of codecMember to aid its inlining
+    private Object loadMember(final AtomicReferenceFieldUpdater<CodecDataObject<?>, Object> updater,
+            final String methodName) {
+        final Object decoded = context.getBindingChildValue(methodName, data);
+        return updater.compareAndSet(this, null, maskNull(decoded)) ? decoded : unmaskNull(updater.get(this));
+    }
+
+    private static @NonNull Object maskNull(final @Nullable Object unmasked) {
+        return unmasked == null ? NULL_VALUE : unmasked;
+    }
+
+    private static @Nullable Object unmaskNull(final Object masked) {
+        return masked == NULL_VALUE ? null : masked;
+    }
+}
diff --git a/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/CodecDataObjectCustomizer.java b/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/CodecDataObjectCustomizer.java
new file mode 100644 (file)
index 0000000..67568ae
--- /dev/null
@@ -0,0 +1,162 @@
+/*
+ * Copyright (c) 2019 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 com.google.common.base.MoreObjects.ToStringHelper;
+import com.google.common.collect.ImmutableList;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
+import javassist.CannotCompileException;
+import javassist.CtClass;
+import javassist.CtField;
+import javassist.CtMethod;
+import javassist.NotFoundException;
+import org.opendaylight.mdsal.binding.dom.codec.loader.CodecClassLoader;
+import org.opendaylight.mdsal.binding.dom.codec.loader.CodecClassLoader.Customizer;
+import org.opendaylight.mdsal.binding.dom.codec.loader.StaticClassPool;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Private support for generating AbstractDataObject specializations.
+ */
+final class CodecDataObjectCustomizer implements Customizer {
+    private static final Logger LOG = LoggerFactory.getLogger(CodecDataObjectCustomizer.class);
+    private static final CtClass CT_ARFU = StaticClassPool.findClass(AtomicReferenceFieldUpdater.class);
+    private static final CtClass CT_BOOLEAN = StaticClassPool.findClass(boolean.class);
+    private static final CtClass CT_INT = StaticClassPool.findClass(int.class);
+    private static final CtClass CT_OBJECT = StaticClassPool.findClass(Object.class);
+    private static final CtClass CT_HELPER = StaticClassPool.findClass(ToStringHelper.class);
+    private static final CtClass CT_DATAOBJECT = StaticClassPool.findClass(DataObject.class);
+    private static final CtClass[] EMPTY_ARGS = new CtClass[0];
+    private static final CtClass[] EQUALS_ARGS = new CtClass[] { CT_DATAOBJECT };
+    private static final CtClass[] TOSTRING_ARGS = new CtClass[] { CT_HELPER };
+
+    private final List<Method> properties;
+    private final List<Method> methods;
+
+    CodecDataObjectCustomizer(final List<Method> properties, final List<Method> methods) {
+        this.properties = requireNonNull(properties);
+        this.methods = requireNonNull(methods);
+    }
+
+    @Override
+    public List<Class<?>> customize(final CodecClassLoader loader, final CtClass bindingClass, final CtClass generated)
+            throws NotFoundException, CannotCompileException {
+        final String classFqn = generated.getName();
+        generated.addInterface(bindingClass);
+
+        // Generate members for all methods ...
+        LOG.trace("Generating class {}", classFqn);
+        for (Method method : methods) {
+            LOG.trace("Generating for method {}", method);
+            final String methodName = method.getName();
+            final String methodArfu = methodName + "$$$ARFU";
+
+            // AtomicReferenceFieldUpdater ...
+            final CtField arfuField = new CtField(CT_ARFU, methodArfu, generated);
+            arfuField.setModifiers(Modifier.PRIVATE | Modifier.STATIC | Modifier.FINAL);
+            generated.addField(arfuField, new StringBuilder().append(CT_ARFU.getName()).append(".newUpdater(")
+                .append(generated.getName()).append(".class, java.lang.Object.class, \"").append(methodName)
+                .append("\")").toString());
+
+            // ... corresponding volatile field ...
+            final CtField field = new CtField(CT_OBJECT, methodName, generated);
+            field.setModifiers(Modifier.PRIVATE | Modifier.VOLATILE);
+            generated.addField(field);
+
+            // ... and the getter
+            final Class<?> retType = method.getReturnType();
+            final CtMethod getter = new CtMethod(loader.findClass(retType), methodName, EMPTY_ARGS, generated);
+            final String retName = retType.isArray() ? retType.getSimpleName() : retType.getName();
+
+            getter.setBody(new StringBuilder()
+                .append("{\n")
+                .append("return (").append(retName).append(") codecMember(").append(methodArfu).append(", \"")
+                    .append(methodName).append("\");\n")
+                .append('}').toString());
+            getter.setModifiers(Modifier.PUBLIC | Modifier.FINAL);
+            generated.addMethod(getter);
+        }
+
+        // Final bits: codecHashCode() ...
+        final CtMethod codecHashCode = new CtMethod(CT_INT, "codecHashCode", EMPTY_ARGS, generated);
+        codecHashCode.setModifiers(Modifier.PROTECTED | Modifier.FINAL);
+        codecHashCode.setBody(codecHashCodeBody());
+        generated.addMethod(codecHashCode);
+
+        // ... equals
+        final CtMethod codecEquals = new CtMethod(CT_BOOLEAN, "codecEquals", EQUALS_ARGS, generated);
+        codecEquals.setModifiers(Modifier.PROTECTED | Modifier.FINAL);
+        codecEquals.setBody(codecEqualsBody(bindingClass.getName()));
+        generated.addMethod(codecEquals);
+
+        // ... and codecFillToString()
+        final CtMethod codecFillToString = new CtMethod(CT_HELPER, "codecFillToString", TOSTRING_ARGS, generated);
+        codecFillToString.setModifiers(Modifier.PROTECTED | Modifier.FINAL);
+        codecFillToString.setBody(codecFillToStringBody());
+        generated.addMethod(codecFillToString);
+
+        generated.setModifiers(Modifier.FINAL | Modifier.PUBLIC);
+        return ImmutableList.of();
+    }
+
+    private String codecHashCodeBody() {
+        final StringBuilder sb = new StringBuilder()
+                .append("{\n")
+                .append("final int prime = 31;\n")
+                .append("int result = 1;\n");
+
+        for (Method method : properties) {
+            sb.append("result = prime * result + java.util.").append(utilClass(method)).append(".hashCode(")
+            .append(method.getName()).append("());\n");
+        }
+
+        return sb.append("return result;\n")
+                .append('}').toString();
+    }
+
+    private String codecEqualsBody(final String ifaceName) {
+        final StringBuilder sb = new StringBuilder()
+                .append("{\n")
+                .append("final ").append(ifaceName).append(" other = $1;")
+                .append("return true");
+
+        for (Method method : properties) {
+            final String methodName = method.getName();
+            sb.append("\n&& java.util.").append(utilClass(method)).append(".equals(").append(methodName)
+            .append("(), other.").append(methodName).append("())");
+        }
+
+        return sb.append(";\n")
+                .append('}').toString();
+    }
+
+    private String codecFillToStringBody() {
+        final StringBuilder sb = new StringBuilder()
+                .append("{\n")
+                .append("return $1");
+        for (Method method : properties) {
+            final String methodName = method.getName();
+            sb.append("\n.add(\"").append(methodName).append("\", ").append(methodName).append("())");
+        }
+
+        return sb.append(";\n")
+                .append('}').toString();
+    }
+
+    private static String utilClass(final Method method) {
+        // We can either have objects or byte[], we cannot have Object[]
+        return method.getReturnType().isArray() ? "Arrays" : "Objects";
+    }
+}
\ No newline at end of file
index 6a5d26ce4b296665a857a402752a21f385c746cc..69b326d749ea35a740cbe6c6982e775b3f54f7e9 100644 (file)
@@ -15,12 +15,12 @@ import static java.util.Objects.requireNonNull;
 import com.google.common.base.Throwables;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableMap.Builder;
+import java.io.IOException;
 import java.lang.invoke.MethodHandle;
 import java.lang.invoke.MethodHandles;
 import java.lang.invoke.MethodType;
-import java.lang.reflect.InvocationHandler;
 import java.lang.reflect.Method;
-import java.lang.reflect.Proxy;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Comparator;
 import java.util.HashMap;
@@ -30,18 +30,20 @@ import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Optional;
 import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
+import javassist.CannotCompileException;
+import javassist.CtClass;
+import javassist.NotFoundException;
 import org.eclipse.jdt.annotation.NonNull;
 import org.eclipse.jdt.annotation.Nullable;
+import org.opendaylight.mdsal.binding.dom.codec.loader.StaticClassPool;
 import org.opendaylight.mdsal.binding.generator.api.ClassLoadingStrategy;
 import org.opendaylight.mdsal.binding.model.api.JavaTypeName;
 import org.opendaylight.mdsal.binding.model.api.Type;
-import org.opendaylight.mdsal.binding.spec.naming.BindingMapping;
 import org.opendaylight.mdsal.binding.spec.reflect.BindingReflections;
 import org.opendaylight.yangtools.concepts.Immutable;
 import org.opendaylight.yangtools.util.ClassLoaderUtils;
 import org.opendaylight.yangtools.yang.binding.Augmentable;
 import org.opendaylight.yangtools.yang.binding.Augmentation;
-import org.opendaylight.yangtools.yang.binding.AugmentationHolder;
 import org.opendaylight.yangtools.yang.binding.DataObject;
 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier.Item;
@@ -77,21 +79,23 @@ abstract class DataObjectCodecContext<D extends DataObject, T extends DataNodeCo
     }
 
     private static final Logger LOG = LoggerFactory.getLogger(DataObjectCodecContext.class);
-    private static final MethodType CONSTRUCTOR_TYPE = MethodType.methodType(void.class, InvocationHandler.class);
-    private static final MethodType DATAOBJECT_TYPE = MethodType.methodType(DataObject.class, InvocationHandler.class);
+    private static final MethodType CONSTRUCTOR_TYPE = MethodType.methodType(void.class, DataObjectCodecContext.class,
+        NormalizedNodeContainer.class);
+    private static final MethodType DATAOBJECT_TYPE = MethodType.methodType(DataObject.class,
+        DataObjectCodecContext.class, NormalizedNodeContainer.class);
     private static final Comparator<Method> METHOD_BY_ALPHABET = Comparator.comparing(Method::getName);
     private static final Augmentations EMPTY_AUGMENTATIONS = new Augmentations(ImmutableMap.of(), ImmutableMap.of());
-    private static final Method[] EMPTY_METHODS = new Method[0];
+    private static final CtClass SUPERCLASS = StaticClassPool.findClass(CodecDataObject.class);
+    private static final CtClass AUGMENTABLE_SUPERCLASS = StaticClassPool.findClass(
+        AugmentableCodecDataObject.class);
 
     private final ImmutableMap<String, ValueNodeCodecContext> leafChild;
     private final ImmutableMap<YangInstanceIdentifier.PathArgument, NodeContextSupplier> byYang;
     private final ImmutableMap<String, NodeContextSupplier> byMethod;
-    private final ImmutableMap<String, String> nonnullToGetter;
     private final ImmutableMap<Class<?>, DataContainerCodecPrototype<?>> byStreamClass;
     private final ImmutableMap<Class<?>, DataContainerCodecPrototype<?>> byBindingArgClass;
     private final ImmutableMap<AugmentationIdentifier, Type> possibleAugmentations;
     private final MethodHandle proxyConstructor;
-    private final Method[] propertyMethods;
 
     @SuppressWarnings("rawtypes")
     private static final AtomicReferenceFieldUpdater<DataObjectCodecContext, Augmentations>
@@ -101,7 +105,7 @@ abstract class DataObjectCodecContext<D extends DataObject, T extends DataNodeCo
 
     private volatile ImmutableMap<Class<?>, DataContainerCodecPrototype<?>> mismatchedAugmented = ImmutableMap.of();
 
-    DataObjectCodecContext(final DataContainerCodecPrototype<T> prototype) {
+    DataObjectCodecContext(final DataContainerCodecPrototype<T> prototype, final Method... additionalMethods) {
         super(prototype);
 
         final Class<D> bindingClass = getBindingClass();
@@ -144,17 +148,17 @@ abstract class DataObjectCodecContext<D extends DataObject, T extends DataNodeCo
 
         final int methodCount = tmpMethodToSupplier.size();
         final Builder<String, NodeContextSupplier> byMethodBuilder = ImmutableMap.builderWithExpectedSize(methodCount);
-        this.propertyMethods = methodCount == 0 ? EMPTY_METHODS : new Method[methodCount];
 
-        int offset = 0;
+
+        final List<Method> properties = new ArrayList<>(methodCount);
         for (Entry<Method, NodeContextSupplier> entry : tmpMethodToSupplier.entrySet()) {
             final Method method = entry.getKey();
-            propertyMethods[offset++] = method;
+            properties.add(method);
             byMethodBuilder.put(method.getName(), entry.getValue());
         }
 
         // Make sure properties are alpha-sorted
-        Arrays.sort(propertyMethods, METHOD_BY_ALPHABET);
+        properties.sort(METHOD_BY_ALPHABET);
 
         this.byMethod = byMethodBuilder.build();
         this.byYang = ImmutableMap.copyOf(byYangBuilder);
@@ -162,38 +166,38 @@ abstract class DataObjectCodecContext<D extends DataObject, T extends DataNodeCo
         byBindingArgClassBuilder.putAll(byStreamClass);
         this.byBindingArgClass = ImmutableMap.copyOf(byBindingArgClassBuilder);
 
-        final Map<Class<?>, Method> clsToNonnull = BindingReflections.getChildrenClassToNonnullMethod(bindingClass);
-        final Map<String, String> nonnullToGetterBuilder = new HashMap<>();
-        for (final Entry<Class<?>, Method> entry : clsToNonnull.entrySet()) {
-            final Method method = entry.getValue();
-            if (!method.isDefault()) {
-                LOG.warn("Ignoring non-default method {} in {}", method, bindingClass);
-                continue;
-            }
-
-            // Derive getter name from the nonnull method and verify we have the corresponding getter. Note that
-            // the intern() call is important, as it makes sure we use the same instance to bridge to byMethod map.
-            final String methodName = method.getName();
-            final String getterName = BindingMapping.getGetterMethodForNonnull(methodName).intern();
-            verify(byMethod.containsKey(getterName), "Cannot find getter %s for %s", getterName, methodName);
-            nonnullToGetterBuilder.put(methodName, getterName);
-        }
-        nonnullToGetter = ImmutableMap.copyOf(nonnullToGetterBuilder);
-
+        final CtClass superClass;
         if (Augmentable.class.isAssignableFrom(bindingClass)) {
             this.possibleAugmentations = factory().getRuntimeContext().getAvailableAugmentationTypes(getSchema());
+            superClass = AUGMENTABLE_SUPERCLASS;
         } else {
             this.possibleAugmentations = ImmutableMap.of();
+            superClass = SUPERCLASS;
         }
         reloadAllAugmentations();
 
-        final Class<?> proxyClass = Proxy.getProxyClass(bindingClass.getClassLoader(), bindingClass,
-            AugmentationHolder.class);
+        final List<Method> methods;
+        if (additionalMethods.length != 0) {
+            methods = new ArrayList<>(properties.size() + 1);
+            methods.addAll(properties);
+            methods.addAll(Arrays.asList(additionalMethods));
+        } else {
+            methods = properties;
+        }
+
+        final Class<?> generatedClass;
+        try {
+            generatedClass = prototype.getFactory().getLoader().generateSubclass(superClass, bindingClass, "codecImpl",
+                new CodecDataObjectCustomizer(properties, methods));
+        } catch (CannotCompileException | IOException | NotFoundException e) {
+            throw new LinkageError("Failed to generated class for " + bindingClass, e);
+        }
+
         try {
-            proxyConstructor = MethodHandles.publicLookup().findConstructor(proxyClass, CONSTRUCTOR_TYPE)
-                    .asType(DATAOBJECT_TYPE);
+            proxyConstructor = MethodHandles.publicLookup().findConstructor(generatedClass, CONSTRUCTOR_TYPE)
+                    .asType(DATAOBJECT_TYPE).bindTo(this);
         } catch (NoSuchMethodException | IllegalAccessException e) {
-            throw new IllegalStateException("Failed to find contructor for class " + proxyClass, e);
+            throw new LinkageError("Failed to find contructor for class " + generatedClass, e);
         }
     }
 
@@ -314,8 +318,8 @@ abstract class DataObjectCodecContext<D extends DataObject, T extends DataNodeCo
         if (ctxProto == null && Augmentation.class.isAssignableFrom(argType)) {
             ctxProto = augmentationByClass(argType);
         }
-        final DataContainerCodecContext<?, ?> context =
-                childNonNull(ctxProto, argType, "Class %s is not valid child of %s", argType, getBindingClass()).get();
+        final DataContainerCodecContext<?, ?> context = childNonNull(ctxProto, argType,
+            "Class %s is not valid child of %s", argType, getBindingClass()).get();
         if (context instanceof ChoiceNodeCodecContext) {
             final ChoiceNodeCodecContext<?> choice = (ChoiceNodeCodecContext<?>) context;
             choice.addYangPathArgument(arg, builder);
@@ -519,12 +523,6 @@ abstract class DataObjectCodecContext<D extends DataObject, T extends DataNodeCo
         return DataContainerCodecPrototype.from(augClass, augSchema.getKey(), augSchema.getValue(), factory());
     }
 
-    // Unlike BindingMapping.getGetterMethodForNonnull() this returns an interned String
-    @NonNull String getterNameForNonnullName(final String nonnullMethod) {
-        return verifyNotNull(nonnullToGetter.get(nonnullMethod), "Failed to look up getter method for %s",
-            nonnullMethod);
-    }
-
     @SuppressWarnings("rawtypes")
     @Nullable Object getBindingChildValue(final String method, final NormalizedNodeContainer domData) {
         final NodeCodecContext childContext = verifyNotNull(byMethod.get(method),
@@ -541,7 +539,7 @@ abstract class DataObjectCodecContext<D extends DataObject, T extends DataNodeCo
     @SuppressWarnings("checkstyle:illegalCatch")
     protected final D createBindingProxy(final NormalizedNodeContainer<?, ?, ?> node) {
         try {
-            return (D) proxyConstructor.invokeExact((InvocationHandler)new LazyDataObject<>(this, node));
+            return (D) proxyConstructor.invokeExact(node);
         } catch (final Throwable e) {
             Throwables.throwIfUnchecked(e);
             throw new IllegalStateException(e);
@@ -574,10 +572,6 @@ abstract class DataObjectCodecContext<D extends DataObject, T extends DataNodeCo
         return map;
     }
 
-    final Method[] propertyMethods() {
-        return propertyMethods;
-    }
-
     @Override
     public InstanceIdentifier.PathArgument deserializePathArgument(final YangInstanceIdentifier.PathArgument arg) {
         checkArgument(getDomPathArgument().equals(arg));
index b7413d375269c0ad4e1a7b7e16c67f7aca69c9b4..0da9d7a9921fd81b14d2fe74c42103ef5ff1d443 100644 (file)
@@ -9,6 +9,7 @@ package org.opendaylight.mdsal.binding.dom.codec.impl;
 
 import static org.opendaylight.mdsal.binding.spec.naming.BindingMapping.IDENTIFIABLE_KEY_NAME;
 
+import java.lang.reflect.Method;
 import java.util.List;
 import org.opendaylight.yangtools.yang.binding.DataObject;
 import org.opendaylight.yangtools.yang.binding.Identifiable;
@@ -25,13 +26,14 @@ final class KeyedListNodeCodecContext<D extends DataObject & Identifiable<?>> ex
     private final IdentifiableItemCodec codec;
 
     KeyedListNodeCodecContext(final DataContainerCodecPrototype<ListSchemaNode> prototype) {
-        super(prototype);
+        super(prototype, keyMethod(prototype.getBindingClass()));
+        this.codec = factory().getPathArgumentCodec(getBindingClass(), getSchema());
+    }
 
-        final Class<D> bindingClass = getBindingClass();
-        this.codec = factory().getPathArgumentCodec(bindingClass, getSchema());
+    private static Method keyMethod(final Class<?> bindingClass) {
         try {
             // This just verifies the method is present
-            bindingClass.getMethod(IDENTIFIABLE_KEY_NAME);
+            return bindingClass.getMethod(IDENTIFIABLE_KEY_NAME);
         } catch (NoSuchMethodException e) {
             throw new IllegalStateException("Required method not available", e);
         }
diff --git a/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/LazyDataObject.java b/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/LazyDataObject.java
deleted file mode 100644 (file)
index 5c7382c..0000000
+++ /dev/null
@@ -1,273 +0,0 @@
-/*
- * Copyright (c) 2014 Cisco Systems, Inc. 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 java.util.Objects.requireNonNull;
-import static org.opendaylight.mdsal.binding.spec.naming.BindingMapping.AUGMENTABLE_AUGMENTATION_NAME;
-import static org.opendaylight.mdsal.binding.spec.naming.BindingMapping.DATA_CONTAINER_IMPLEMENTED_INTERFACE_NAME;
-
-import com.google.common.base.MoreObjects;
-import com.google.common.base.MoreObjects.ToStringHelper;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import java.lang.reflect.InvocationHandler;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.lang.reflect.Proxy;
-import java.util.Arrays;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Optional;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
-import org.eclipse.jdt.annotation.NonNull;
-import org.opendaylight.mdsal.binding.dom.codec.util.AugmentationReader;
-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.DataObject;
-import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
-import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
-import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-class LazyDataObject<D extends DataObject> implements InvocationHandler, AugmentationReader {
-
-    private static final Logger LOG = LoggerFactory.getLogger(LazyDataObject.class);
-    private static final String TO_STRING = "toString";
-    private static final String EQUALS = "equals";
-    private static final String HASHCODE = "hashCode";
-    private static final String AUGMENTATIONS = "augmentations";
-    private static final @NonNull Object NULL_VALUE = new Object();
-
-    // Method.getName() is guaranteed to be interned and all getter methods have zero arguments, name is sufficient to
-    // identify the data, skipping Method.hashCode() computation.
-    private final ConcurrentHashMap<String, Object> cachedData = new ConcurrentHashMap<>();
-    private final NormalizedNodeContainer<?, PathArgument, NormalizedNode<?, ?>> data;
-    private final DataObjectCodecContext<D,?> context;
-
-    @SuppressWarnings("rawtypes")
-    private static final AtomicReferenceFieldUpdater<LazyDataObject, ImmutableMap> CACHED_AUGMENTATIONS_UPDATER =
-            AtomicReferenceFieldUpdater.newUpdater(LazyDataObject.class, ImmutableMap.class, "cachedAugmentations");
-    private volatile ImmutableMap<Class<? extends Augmentation<?>>, Augmentation<?>> cachedAugmentations = null;
-    private volatile Integer cachedHashcode = null;
-
-    @SuppressWarnings({ "rawtypes", "unchecked" })
-    LazyDataObject(final DataObjectCodecContext<D,?> ctx, final NormalizedNodeContainer data) {
-        this.context = requireNonNull(ctx, "Context must not be null");
-        this.data = requireNonNull(data, "Data must not be null");
-    }
-
-    @Override
-    public Object invoke(final Object proxy, final Method method, final Object[] args) {
-        switch (method.getParameterCount()) {
-            case 0:
-                final String methodName = method.getName();
-                switch (methodName) {
-                    case DATA_CONTAINER_IMPLEMENTED_INTERFACE_NAME:
-                        return context.getBindingClass();
-                    case TO_STRING:
-                        return bindingToString();
-                    case HASHCODE:
-                        return bindingHashCode();
-                    case AUGMENTATIONS:
-                        return getAugmentationsImpl();
-                    default:
-                        return method.isDefault() ? nonnullBindingData(methodName) : getBindingData(methodName);
-                }
-            case 1:
-                switch (method.getName()) {
-                    case AUGMENTABLE_AUGMENTATION_NAME:
-                        return getAugmentationImpl((Class<?>) args[0]);
-                    case EQUALS:
-                        return bindingEquals(args[0]);
-                    default:
-                        break;
-                }
-                break;
-            default:
-                break;
-        }
-
-        throw new UnsupportedOperationException("Unsupported method " + method);
-    }
-
-    private boolean bindingEquals(final Object other) {
-        if (other == null) {
-            return false;
-        }
-        final Class<D> bindingClass = context.getBindingClass();
-        if (!bindingClass.isAssignableFrom(other.getClass())) {
-            return false;
-        }
-        try {
-            for (final Method m : context.propertyMethods()) {
-                final Object thisValue = getBindingData(m.getName());
-                final Object otherValue = m.invoke(other);
-                /*
-                 *   added for valid byte array comparison, when list key type is binary
-                 *   deepEquals is not used since it does excessive amount of instanceof calls.
-                 */
-                if (thisValue instanceof byte[] && otherValue instanceof byte[]) {
-                    if (!Arrays.equals((byte[]) thisValue, (byte[]) otherValue)) {
-                        return false;
-                    }
-                } else if (!Objects.equals(thisValue, otherValue)) {
-                    return false;
-                }
-            }
-
-            if (Augmentable.class.isAssignableFrom(bindingClass)) {
-                if (!getAugmentationsImpl().equals(getAllAugmentations(other))) {
-                    return false;
-                }
-            }
-        } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
-            LOG.warn("Can not determine equality of {} and {}", this, other, e);
-            return false;
-        }
-        return true;
-    }
-
-    private static Map<Class<? extends Augmentation<?>>, Augmentation<?>> getAllAugmentations(final Object dataObject) {
-        if (dataObject instanceof AugmentationReader) {
-            return ((AugmentationReader) dataObject).getAugmentations(dataObject);
-        } else if (dataObject instanceof Augmentable<?>) {
-            return BindingReflections.getAugmentations((Augmentable<?>) dataObject);
-        }
-
-        throw new IllegalArgumentException("Unable to get all augmentations from " + dataObject);
-    }
-
-    private Integer bindingHashCode() {
-        final Integer cached = cachedHashcode;
-        if (cached != null) {
-            return cached;
-        }
-
-        final int prime = 31;
-        int result = 1;
-        for (final Method m : context.propertyMethods()) {
-            final Object value = getBindingData(m.getName());
-            result = prime * result + Objects.hashCode(value);
-        }
-        if (Augmentable.class.isAssignableFrom(context.getBindingClass())) {
-            result = prime * result + getAugmentationsImpl().hashCode();
-        }
-        final Integer ret = result;
-        cachedHashcode = ret;
-        return ret;
-    }
-
-    private Object nonnullBindingData(final String methodName) {
-        final Object value = getBindingData(context.getterNameForNonnullName(methodName));
-        return value != null ? value : ImmutableList.of();
-    }
-
-    // Internal invocation, can only target getFoo() methods
-    private Object getBindingData(final String methodName) {
-        final Object cached = cachedData.get(methodName);
-        if (cached != null) {
-            return unmaskNull(cached);
-        }
-
-        final Object value = context.getBindingChildValue(methodName, data);
-        final Object raced = cachedData.putIfAbsent(methodName, value == null ? NULL_VALUE : value);
-        // If we raced we need to return previously-stored value
-        return raced != null ? unmaskNull(raced) : value;
-    }
-
-    private static Object unmaskNull(final @NonNull Object masked) {
-        return masked == NULL_VALUE ? null : masked;
-    }
-
-    private Map<Class<? extends Augmentation<?>>, Augmentation<?>> getAugmentationsImpl() {
-        ImmutableMap<Class<? extends Augmentation<?>>, Augmentation<?>> local = cachedAugmentations;
-        if (local != null) {
-            return local;
-        }
-
-        local = ImmutableMap.copyOf(context.getAllAugmentationsFrom(data));
-        return CACHED_AUGMENTATIONS_UPDATER.compareAndSet(this, null, local) ? local : cachedAugmentations;
-    }
-
-    @Override
-    public Map<Class<? extends Augmentation<?>>, Augmentation<?>> getAugmentations(final Object obj) {
-        checkArgument(this == Proxy.getInvocationHandler(obj),
-                "Supplied object is not associated with this proxy handler");
-
-        return getAugmentationsImpl();
-    }
-
-    private Object getAugmentationImpl(final Class<?> cls) {
-        requireNonNull(cls, "Supplied augmentation must not be null.");
-
-        final ImmutableMap<Class<? extends Augmentation<?>>, Augmentation<?>> aug = cachedAugmentations;
-        if (aug != null) {
-            return aug.get(cls);
-        }
-
-        @SuppressWarnings({"unchecked","rawtypes"})
-        final Optional<DataContainerCodecContext<?, ?>> optAugCtx = context.possibleStreamChild((Class) cls);
-        if (optAugCtx.isPresent()) {
-            final DataContainerCodecContext<?, ?> augCtx = optAugCtx.get();
-            // Due to binding specification not representing grouping instantiations we can end up having the same
-            // augmentation applied to a grouping multiple times. While these augmentations have the same shape, they
-            // are still represented by distinct binding classes and therefore we need to make sure the result matches
-            // the augmentation the user is requesting -- otherwise a strict receiver would end up with a cryptic
-            // ClassCastException.
-            if (cls.isAssignableFrom(augCtx.getBindingClass())) {
-                final Optional<NormalizedNode<?, ?>> augData = data.getChild(augCtx.getDomPathArgument());
-                if (augData.isPresent()) {
-                    return augCtx.deserialize(augData.get());
-                }
-            }
-        }
-        return null;
-    }
-
-    public String bindingToString() {
-        final Class<D> bindingClass = context.getBindingClass();
-        final ToStringHelper helper = MoreObjects.toStringHelper(bindingClass).omitNullValues();
-
-        for (final Method m : context.propertyMethods()) {
-            final String methodName = m.getName();
-            helper.add(methodName, getBindingData(methodName));
-        }
-        if (Augmentable.class.isAssignableFrom(bindingClass)) {
-            helper.add("augmentations", getAugmentationsImpl());
-        }
-        return helper.toString();
-    }
-
-    @Override
-    public int hashCode() {
-        final int prime = 31;
-        int result = 1;
-        result = prime * result + context.hashCode();
-        result = prime * result + data.hashCode();
-        return result;
-    }
-
-    @Override
-    public boolean equals(final Object obj) {
-        if (this == obj) {
-            return true;
-        }
-        if (obj == null) {
-            return false;
-        }
-        if (getClass() != obj.getClass()) {
-            return false;
-        }
-        final LazyDataObject<?> other = (LazyDataObject<?>) obj;
-        return Objects.equals(context, other.context) && Objects.equals(data, other.data);
-    }
-}
index 744397b2ccfbee5495fb49c6c425e9151cc2320e..e6c9447f9047c900f7d42b48de71766a61c6b546 100644 (file)
@@ -7,6 +7,7 @@
  */
 package org.opendaylight.mdsal.binding.dom.codec.impl;
 
+import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.List;
 import org.opendaylight.yangtools.yang.binding.DataObject;
@@ -18,8 +19,9 @@ import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListNode;
 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
 
 class ListNodeCodecContext<D extends DataObject> extends DataObjectCodecContext<D,ListSchemaNode> {
-    protected ListNodeCodecContext(final DataContainerCodecPrototype<ListSchemaNode> prototype) {
-        super(prototype);
+    protected ListNodeCodecContext(final DataContainerCodecPrototype<ListSchemaNode> prototype,
+            final Method... additionalMethods) {
+        super(prototype, additionalMethods);
     }
 
     @Override
index 12c9d85201c5d86ba1132a6187b86d6164db1b71..5944d7951e26468fa110efa6b81be3746a04ab4a 100644 (file)
@@ -9,8 +9,10 @@ package org.opendaylight.mdsal.binding.dom.codec.impl;
 
 import com.google.common.collect.ImmutableMap;
 import java.util.List;
+import org.eclipse.jdt.annotation.NonNull;
 import org.eclipse.jdt.annotation.Nullable;
 import org.opendaylight.mdsal.binding.dom.codec.api.BindingCodecTreeNode;
+import org.opendaylight.mdsal.binding.dom.codec.loader.CodecClassLoader;
 import org.opendaylight.mdsal.binding.generator.util.BindingRuntimeContext;
 import org.opendaylight.yangtools.yang.binding.DataObjectSerializer;
 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
@@ -74,6 +76,13 @@ abstract class NodeCodecContext implements BindingCodecTreeNode {
          */
         IdentifiableItemCodec getPathArgumentCodec(Class<?> type, ListSchemaNode schema);
 
+        /**
+         * Return the codec loader associated with this factory.
+         *
+         * @return A codec loader instance
+         */
+        @NonNull CodecClassLoader getLoader();
+
         DataObjectSerializer getEventStreamSerializer(Class<?> type);
     }