Cache NodeContextSuppliers in CodecDataObject 15/81715/8
authorRobert Varga <robert.varga@pantheon.tech>
Tue, 23 Apr 2019 10:26:47 +0000 (12:26 +0200)
committerRobert Varga <robert.varga@pantheon.tech>
Wed, 24 Apr 2019 13:28:57 +0000 (15:28 +0200)
The binding between DataContainerCodecContext and CodecDataObject
is required only to support decoding child leaves, where we are
looking offset-based lookup.

Since each method has a fixed offset, which directly corresponds
to a NodeContextSupplier, we can short-circuit this lookup by
resolving NodeContextSupplier for each method into constant and
then pass it down to CodecDataObject -- thus side-stepping any
lookups and allowing the constant to be properly propagated.

That unfortunately means we need to initialize the generated class
early, so the fields are resolved in a well-controlled environment
-- for which we are using Customizer.customizeLoading() coupled
with a thread-local context managed by CodecDataObjectBridge.

JIRA: MDSAL-443
Change-Id: I9e24c30535c536d70b4eb60035b5aa744fc6ec9d
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
binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/CodecDataObject.java
binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/CodecDataObjectBridge.java [new file with mode: 0644]
binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/CodecDataObjectCustomizer.java
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/DataContainerCodecPrototype.java
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/IdentifiableItemCodec.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/ListNodeCodecContext.java
binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/NodeContextSupplier.java

index dcc5e5bdbd64e6a3e769bb9762fb329ce6928cd4..43edeadb86a597cab8533ecf1a868b759533e87a 100644 (file)
@@ -14,6 +14,7 @@ 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.NonNull;
 import org.eclipse.jdt.annotation.Nullable;
 import org.opendaylight.mdsal.binding.dom.codec.util.AugmentationReader;
 import org.opendaylight.mdsal.binding.spec.reflect.BindingReflections;
@@ -33,15 +34,18 @@ import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer;
  */
 public abstract class AugmentableCodecDataObject<T extends DataObject & Augmentable<T>>
         extends CodecDataObject<T> implements Augmentable<T>, AugmentationHolder<T> {
+    private final @NonNull DataObjectCodecContext<T, ?> context;
+
     @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,
+    public AugmentableCodecDataObject(final DataObjectCodecContext<T, ?> context,
             final NormalizedNodeContainer<?, ?, ?> data) {
-        super(ctx, data);
+        super(data);
+        this.context = requireNonNull(context, "Context must not be null");
     }
 
     @Override
index 78d8bc7cb89143819a6184966a245bca6f484e3b..a9446a139455f1a9db29cfe916b41b1c11a6c731 100644 (file)
@@ -7,14 +7,18 @@
  */
 package org.opendaylight.mdsal.binding.dom.codec.impl;
 
+import static com.google.common.base.Verify.verify;
 import static java.util.Objects.requireNonNull;
 
 import com.google.common.base.MoreObjects;
 import com.google.common.base.MoreObjects.ToStringHelper;
+import java.util.Optional;
 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.MapEntryNode;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer;
 
 /**
@@ -29,12 +33,10 @@ public abstract class CodecDataObject<T extends DataObject> implements DataObjec
 
     @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");
+    public CodecDataObject(final NormalizedNodeContainer<?, ?, ?> data) {
         this.data = requireNonNull(data, "Data must not be null");
     }
 
@@ -75,9 +77,15 @@ public abstract class CodecDataObject<T extends DataObject> implements DataObjec
 
     // TODO: consider switching to VarHandles for Java 9+
     protected final Object codecMember(final AtomicReferenceFieldUpdater<CodecDataObject<?>, Object> updater,
-            final int offset) {
+            final NodeContextSupplier supplier) {
         final Object cached = updater.get(this);
-        return cached != null ? unmaskNull(cached) : loadMember(updater, offset);
+        return cached != null ? unmaskNull(cached) : loadMember(updater, supplier);
+    }
+
+    protected final Object codecMember(final AtomicReferenceFieldUpdater<CodecDataObject<?>, Object> updater,
+            final IdentifiableItemCodec codec) {
+        final Object cached = updater.get(this);
+        return cached != null ? unmaskNull(cached) : loadKey(updater, codec);
     }
 
     protected abstract int codecHashCode();
@@ -108,9 +116,28 @@ public abstract class CodecDataObject<T extends DataObject> implements DataObjec
 
     // Helpers split out of codecMember to aid its inlining
     private Object loadMember(final AtomicReferenceFieldUpdater<CodecDataObject<?>, Object> updater,
-            final int offset) {
-        final Object decoded = context.getBindingChildValue(data, offset);
-        return updater.compareAndSet(this, null, maskNull(decoded)) ? decoded : unmaskNull(updater.get(this));
+            final NodeContextSupplier supplier) {
+        final NodeCodecContext context = supplier.get();
+
+        @SuppressWarnings("unchecked")
+        final Optional<NormalizedNode<?, ?>> child = data.getChild(context.getDomPathArgument());
+
+        // We do not want to use Optional.map() here because we do not want to invoke defaultObject() when we have
+        // normal value because defaultObject() may end up throwing an exception intentionally.
+        return updateCache(updater, child.isPresent() ? context.deserializeObject(child.get())
+                : context.defaultObject());
+    }
+
+    // Helpers split out of codecMember to aid its inlining
+    private Object loadKey(final AtomicReferenceFieldUpdater<CodecDataObject<?>, Object> updater,
+            final IdentifiableItemCodec codec) {
+        verify(data instanceof MapEntryNode, "Unsupported value %s", data);
+        return updateCache(updater, codec.deserialize(((MapEntryNode) data).getIdentifier()).getKey());
+    }
+
+    private Object updateCache(final AtomicReferenceFieldUpdater<CodecDataObject<?>, Object> updater,
+            final Object obj) {
+        return updater.compareAndSet(this, null, maskNull(obj)) ? obj : unmaskNull(updater.get(this));
     }
 
     private static @NonNull Object maskNull(final @Nullable Object unmasked) {
diff --git a/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/CodecDataObjectBridge.java b/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/CodecDataObjectBridge.java
new file mode 100644 (file)
index 0000000..8324f11
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * 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 com.google.common.base.Verify.verifyNotNull;
+
+import com.google.common.annotations.Beta;
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * Bridge for initializing {@link CodecDataObject} instance constants during class loading time. This class is public
+ * only due to implementation restrictions and can change at any time.
+ */
+@Beta
+public final class CodecDataObjectBridge {
+    private static final ThreadLocal<CodecDataObjectCustomizer> CURRENT_CUSTOMIZER = new ThreadLocal<>();
+
+    private CodecDataObjectBridge() {
+
+    }
+
+    public static @NonNull NodeContextSupplier resolve(final @NonNull String methodName) {
+        return current().resolve(methodName);
+    }
+
+    public static @NonNull IdentifiableItemCodec resolveKey(final @NonNull String methodName) {
+        return current().resolveKey(methodName);
+    }
+
+    static @Nullable CodecDataObjectCustomizer setup(final @NonNull CodecDataObjectCustomizer next) {
+        final CodecDataObjectCustomizer prev = CURRENT_CUSTOMIZER.get();
+        CURRENT_CUSTOMIZER.set(verifyNotNull(next));
+        return prev;
+    }
+
+    static void tearDown(final @Nullable CodecDataObjectCustomizer prev) {
+        if (prev == null) {
+            CURRENT_CUSTOMIZER.remove();
+        } else {
+            CURRENT_CUSTOMIZER.set(prev);
+        }
+    }
+
+    private static @NonNull CodecDataObjectCustomizer current() {
+        return verifyNotNull(CURRENT_CUSTOMIZER.get(), "No customizer attached");
+    }
+}
index 6e90037e5ee9d7e0053839dd48fe5d59836b3793..f9a3b1154082212e66697c8da188410956f6431b 100644 (file)
@@ -7,19 +7,26 @@
  */
 package org.opendaylight.mdsal.binding.dom.codec.impl;
 
+import static com.google.common.base.Verify.verify;
+import static com.google.common.base.Verify.verifyNotNull;
 import static java.util.Objects.requireNonNull;
 
 import com.google.common.base.MoreObjects.ToStringHelper;
+import com.google.common.base.Supplier;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
 import java.util.List;
+import java.util.Map.Entry;
+import java.util.Optional;
 import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
 import javassist.CannotCompileException;
 import javassist.CtClass;
 import javassist.CtField;
 import javassist.CtMethod;
 import javassist.NotFoundException;
+import org.eclipse.jdt.annotation.NonNull;
 import org.eclipse.jdt.annotation.Nullable;
 import org.opendaylight.mdsal.binding.dom.codec.loader.CodecClassLoader;
 import org.opendaylight.mdsal.binding.dom.codec.loader.CodecClassLoader.Customizer;
@@ -32,23 +39,24 @@ import org.slf4j.LoggerFactory;
  * Private support for generating AbstractDataObject specializations.
  */
 final class CodecDataObjectCustomizer implements Customizer {
-    static final int KEY_OFFSET = -1;
-
     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_DATAOBJECT = StaticClassPool.findClass(DataObject.class);
+    private static final CtClass CT_HELPER = StaticClassPool.findClass(ToStringHelper.class);
+    private static final CtClass CT_IIC = StaticClassPool.findClass(IdentifiableItemCodec.class);
     private static final CtClass CT_INT = StaticClassPool.findClass(int.class);
+    private static final CtClass CT_NCS = StaticClassPool.findClass(NodeContextSupplier.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 Method keyMethod;
+    private final ImmutableMap<Method, NodeContextSupplier> properties;
+    private final Entry<Method, IdentifiableItemCodec> keyMethod;
 
-    CodecDataObjectCustomizer(final List<Method> properties, final @Nullable Method keyMethod) {
+    CodecDataObjectCustomizer(final ImmutableMap<Method, NodeContextSupplier> properties,
+            final @Nullable Entry<Method, IdentifiableItemCodec> keyMethod) {
         this.properties = requireNonNull(properties);
         this.keyMethod = keyMethod;
     }
@@ -60,12 +68,11 @@ final class CodecDataObjectCustomizer implements Customizer {
         LOG.trace("Generating class {}", generated.getName());
         generated.addInterface(bindingClass);
 
-        int offset = 0;
-        for (Method method : properties) {
-            generateMethod(loader, generated, method, offset++);
+        for (Method method : properties.keySet()) {
+            generateMethod(loader, generated, method, CT_NCS, "resolve");
         }
         if (keyMethod != null) {
-            generateMethod(loader, generated, keyMethod, KEY_OFFSET);
+            generateMethod(loader, generated, keyMethod.getKey(), CT_IIC, "resolveKey");
         }
 
         // Final bits: codecHashCode() ...
@@ -90,13 +97,52 @@ final class CodecDataObjectCustomizer implements Customizer {
         return ImmutableList.of();
     }
 
+    @Override
+    public Class<?> customizeLoading(final @NonNull Supplier<Class<?>> loader) {
+        final CodecDataObjectCustomizer prev = CodecDataObjectBridge.setup(this);
+        try {
+            final Class<?> result = loader.get();
+
+            /*
+             * This a bit of magic to support NodeContextSupplier constants. These constants need to be resolved while
+             * we have the information needed to find them -- that information is being held in this instance and we
+             * leak it to a thread-local variable held by CodecDataObjectBridge.
+             *
+             * By default the JVM will defer class initialization to first use, which unfortunately is too late for
+             * us, and hence we need to force class to initialize.
+             */
+            try {
+                Class.forName(result.getName(), true, result.getClassLoader());
+            } catch (ClassNotFoundException e) {
+                throw new LinkageError("Failed to find newly-defined " + result, e);
+            }
+
+            return result;
+        } finally {
+            CodecDataObjectBridge.tearDown(prev);
+        }
+    }
+
+
+    @NonNull NodeContextSupplier resolve(final @NonNull String methodName) {
+        final Optional<Entry<Method, NodeContextSupplier>> found = properties.entrySet().stream()
+                .filter(entry -> methodName.equals(entry.getKey().getName())).findAny();
+        verify(found.isPresent(), "Failed to find property for %s in %s", methodName, this);
+        return verifyNotNull(found.get().getValue());
+    }
+
+    @NonNull IdentifiableItemCodec resolveKey(final @NonNull String methodName) {
+        return verifyNotNull(verifyNotNull(keyMethod, "No key method attached for %s in %s", methodName, this)
+            .getValue());
+    }
+
     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) {
+        for (Method method : properties.keySet()) {
             sb.append("result = prime * result + java.util.").append(utilClass(method)).append(".hashCode(")
             .append(method.getName()).append("());\n");
         }
@@ -111,7 +157,7 @@ final class CodecDataObjectCustomizer implements Customizer {
                 .append("final ").append(ifaceName).append(" other = $1;")
                 .append("return true");
 
-        for (Method method : properties) {
+        for (Method method : properties.keySet()) {
             final String methodName = method.getName();
             sb.append("\n&& java.util.").append(utilClass(method)).append(".equals(").append(methodName)
             .append("(), other.").append(methodName).append("())");
@@ -125,7 +171,7 @@ final class CodecDataObjectCustomizer implements Customizer {
         final StringBuilder sb = new StringBuilder()
                 .append("{\n")
                 .append("return $1");
-        for (Method method : properties) {
+        for (Method method : properties.keySet()) {
             final String methodName = method.getName();
             sb.append("\n.add(\"").append(methodName).append("\", ").append(methodName).append("())");
         }
@@ -135,12 +181,19 @@ final class CodecDataObjectCustomizer implements Customizer {
     }
 
     private static void generateMethod(final CodecClassLoader loader, final CtClass generated, final Method method,
-            final int offset) throws CannotCompileException, NotFoundException {
+            final CtClass contextType, final String resolveMethod) throws CannotCompileException, NotFoundException {
         LOG.trace("Generating for method {}", method);
         final String methodName = method.getName();
         final String methodArfu = methodName + "$$$ARFU";
+        final String methodNcs = methodName + "$$$NCS";
+
+        // NodeContextSupplier ...
+        final CtField ncsField = new CtField(contextType, methodNcs, generated);
+        ncsField.setModifiers(Modifier.PRIVATE | Modifier.STATIC | Modifier.FINAL);
+        generated.addField(ncsField, new StringBuilder().append(CodecDataObjectBridge.class.getName())
+            .append('.').append(resolveMethod).append("(\"").append(methodName).append("\")").toString());
 
-        // AtomicReferenceFieldUpdater ...
+        // ... 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(")
@@ -159,8 +212,8 @@ final class CodecDataObjectCustomizer implements Customizer {
 
         getter.setBody(new StringBuilder()
             .append("{\n")
-            .append("return (").append(retName).append(") codecMember(").append(methodArfu).append(", ").append(offset)
-                .append(");\n")
+            .append("return (").append(retName).append(") codecMember(").append(methodArfu).append(", ")
+                .append(methodNcs).append(");\n")
             .append('}').toString());
         getter.setModifiers(Modifier.PUBLIC | Modifier.FINAL);
         generated.addMethod(getter);
index d999cbc14ff4d85c35c283927af5160c510dd038..738b5011deb8eac6e69ee407c32cf7d56b809ec3 100644 (file)
@@ -7,6 +7,8 @@
  */
 package org.opendaylight.mdsal.binding.dom.codec.impl;
 
+import static java.util.Objects.requireNonNull;
+
 import com.google.common.collect.ImmutableCollection;
 import com.google.common.collect.ImmutableSet;
 import java.io.IOException;
@@ -32,12 +34,12 @@ import org.opendaylight.yangtools.yang.model.api.DocumentedNode.WithStatus;
 
 abstract class DataContainerCodecContext<D extends DataObject, T extends WithStatus> extends NodeCodecContext
         implements BindingDataObjectCodecTreeNode<D>  {
+    private final @NonNull DataContainerCodecPrototype<T> prototype;
 
-    private final DataContainerCodecPrototype<T> prototype;
     private volatile DataObjectSerializer eventStreamSerializer;
 
     protected DataContainerCodecContext(final DataContainerCodecPrototype<T> prototype) {
-        this.prototype = prototype;
+        this.prototype = requireNonNull(prototype);
     }
 
     @Override
index f2dd80ebbab75bcfedff1e22a45f4456b0a570ee..35b37a1e76b6ca6067f3a03216fb4b7bed62b02f 100644 (file)
@@ -224,8 +224,9 @@ final class DataContainerCodecPrototype<T extends WithStatus> implements NodeCon
         if (schema instanceof ContainerSchemaNode) {
             return new ContainerNodeCodecContext(this);
         } else if (schema instanceof ListSchemaNode) {
-            return Identifiable.class.isAssignableFrom(getBindingClass()) ? new KeyedListNodeCodecContext(this)
-                    : new ListNodeCodecContext(this);
+            return Identifiable.class.isAssignableFrom(getBindingClass())
+                    ? KeyedListNodeCodecContext.create((DataContainerCodecPrototype<ListSchemaNode>) this)
+                            : new ListNodeCodecContext(this);
         } else if (schema instanceof ChoiceSchemaNode) {
             return new ChoiceNodeCodecContext(this);
         } else if (schema instanceof AugmentationSchemaNode) {
index 3ac825690dcaebeae17ead4053296cb9baf14b1a..c5360d940f307181fe0281e689235adbcf86231a 100644 (file)
@@ -20,7 +20,7 @@ import java.lang.invoke.MethodHandle;
 import java.lang.invoke.MethodHandles;
 import java.lang.invoke.MethodType;
 import java.lang.reflect.Method;
-import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Comparator;
 import java.util.HashMap;
 import java.util.Iterator;
@@ -78,23 +78,24 @@ 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, DataObjectCodecContext.class,
-        NormalizedNodeContainer.class);
+    private static final MethodType CONSTRUCTOR_TYPE = MethodType.methodType(void.class, NormalizedNodeContainer.class);
+    private static final MethodType AUGMENTABLE_CONSTRUCTOR_TYPE = MethodType.methodType(void.class,
+        DataObjectCodecContext.class, NormalizedNodeContainer.class);
     private static final MethodType DATAOBJECT_TYPE = MethodType.methodType(DataObject.class,
+        NormalizedNodeContainer.class);
+    private static final MethodType AUGMENTABLE_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 CtClass SUPERCLASS = StaticClassPool.findClass(CodecDataObject.class);
     private static final CtClass AUGMENTABLE_SUPERCLASS = StaticClassPool.findClass(
         AugmentableCodecDataObject.class);
-    private static final NodeContextSupplier[] EMPTY_METHODS = new NodeContextSupplier[0];
 
     private final ImmutableMap<String, ValueNodeCodecContext> leafChild;
     private final ImmutableMap<YangInstanceIdentifier.PathArgument, NodeContextSupplier> byYang;
     private final ImmutableMap<Class<?>, DataContainerCodecPrototype<?>> byStreamClass;
     private final ImmutableMap<Class<?>, DataContainerCodecPrototype<?>> byBindingArgClass;
     private final ImmutableMap<AugmentationIdentifier, Type> possibleAugmentations;
-    private final NodeContextSupplier[] byMethod;
     private final MethodHandle proxyConstructor;
 
     @SuppressWarnings("rawtypes")
@@ -109,7 +110,8 @@ abstract class DataObjectCodecContext<D extends DataObject, T extends DataNodeCo
         this(prototype, null);
     }
 
-    DataObjectCodecContext(final DataContainerCodecPrototype<T> prototype, final @Nullable Method keyMethod) {
+    DataObjectCodecContext(final DataContainerCodecPrototype<T> prototype,
+            final Entry<Method, IdentifiableItemCodec> keyMethod) {
         super(prototype);
 
         final Class<D> bindingClass = getBindingClass();
@@ -151,16 +153,12 @@ abstract class DataObjectCodecContext<D extends DataObject, T extends DataNodeCo
         }
 
         // Make sure properties are alpha-sorted
-        final List<Method> properties = new ArrayList<>(tmpMethodToSupplier.keySet());
-        properties.sort(METHOD_BY_ALPHABET);
-        if (!properties.isEmpty()) {
-            byMethod = new NodeContextSupplier[properties.size()];
-            int offset = 0;
-            for (Method prop : properties) {
-                byMethod[offset++] = verifyNotNull(tmpMethodToSupplier.get(prop));
-            }
-        } else {
-            byMethod = EMPTY_METHODS;
+        final Method[] properties = tmpMethodToSupplier.keySet().toArray(new Method[0]);
+        Arrays.sort(properties, METHOD_BY_ALPHABET);
+        final Builder<Method, NodeContextSupplier> propBuilder = ImmutableMap.builderWithExpectedSize(
+            properties.length);
+        for (Method prop : properties) {
+            propBuilder.put(prop, verifyNotNull(tmpMethodToSupplier.get(prop)));
         }
 
         this.byYang = ImmutableMap.copyOf(byYangBuilder);
@@ -169,29 +167,38 @@ abstract class DataObjectCodecContext<D extends DataObject, T extends DataNodeCo
         this.byBindingArgClass = ImmutableMap.copyOf(byBindingArgClassBuilder);
 
         final CtClass superClass;
+        final MethodType ctorType;
         if (Augmentable.class.isAssignableFrom(bindingClass)) {
             this.possibleAugmentations = factory().getRuntimeContext().getAvailableAugmentationTypes(getSchema());
             superClass = AUGMENTABLE_SUPERCLASS;
+            ctorType = AUGMENTABLE_CONSTRUCTOR_TYPE;
         } else {
             this.possibleAugmentations = ImmutableMap.of();
             superClass = SUPERCLASS;
+            ctorType = CONSTRUCTOR_TYPE;
         }
         reloadAllAugmentations();
 
         final Class<?> generatedClass;
         try {
             generatedClass = prototype.getFactory().getLoader().generateSubclass(superClass, bindingClass, "codecImpl",
-                new CodecDataObjectCustomizer(properties, keyMethod));
+                new CodecDataObjectCustomizer(propBuilder.build(), keyMethod));
         } catch (CannotCompileException | IOException | NotFoundException e) {
             throw new LinkageError("Failed to generated class for " + bindingClass, e);
         }
 
+        final MethodHandle ctor;
         try {
-            proxyConstructor = MethodHandles.publicLookup().findConstructor(generatedClass, CONSTRUCTOR_TYPE)
-                    .asType(DATAOBJECT_TYPE).bindTo(this);
+            ctor = MethodHandles.publicLookup().findConstructor(generatedClass, ctorType);
         } catch (NoSuchMethodException | IllegalAccessException e) {
             throw new LinkageError("Failed to find contructor for class " + generatedClass, e);
         }
+
+        if (Augmentable.class.isAssignableFrom(bindingClass)) {
+            proxyConstructor = ctor.asType(AUGMENTABLE_DATAOBJECT_TYPE).bindTo(this);
+        } else {
+            proxyConstructor = ctor.asType(DATAOBJECT_TYPE);
+        }
     }
 
     // This method could be synchronized, but that would mean that concurrent attempts to load an invalid augmentation
@@ -516,18 +523,6 @@ abstract class DataObjectCodecContext<D extends DataObject, T extends DataNodeCo
         return DataContainerCodecPrototype.from(augClass, augSchema.getKey(), augSchema.getValue(), factory());
     }
 
-    @SuppressWarnings("rawtypes")
-    @Nullable Object getBindingChildValue(final NormalizedNodeContainer domData, final int offset) {
-        final NodeCodecContext childContext = byMethod[offset].get();
-
-        @SuppressWarnings("unchecked")
-        final Optional<NormalizedNode<?, ?>> domChild = domData.getChild(childContext.getDomPathArgument());
-
-        // We do not want to use Optional.map() here because we do not want to invoke defaultObject() when we have
-        // normal value because defaultObject() may end up throwing an exception intentionally.
-        return domChild.isPresent() ? childContext.deserializeObject(domChild.get()) : childContext.defaultObject();
-    }
-
     @SuppressWarnings("checkstyle:illegalCatch")
     protected final D createBindingProxy(final NormalizedNodeContainer<?, ?, ?> node) {
         try {
index 6c90da7643d54b03ec12412921da7a8801b8c637..776cb8e5589158a45b0c08aa2afa82827278abf8 100644 (file)
@@ -9,6 +9,7 @@ package org.opendaylight.mdsal.binding.dom.codec.impl;
 
 import static java.util.Objects.requireNonNull;
 
+import com.google.common.annotations.Beta;
 import com.google.common.base.Throwables;
 import com.google.common.collect.ImmutableList;
 import java.lang.invoke.MethodHandle;
@@ -25,13 +26,19 @@ import org.opendaylight.yangtools.concepts.Codec;
 import org.opendaylight.yangtools.util.ImmutableOffsetMap;
 import org.opendaylight.yangtools.util.ImmutableOffsetMapTemplate;
 import org.opendaylight.yangtools.util.SharedSingletonMapTemplate;
+import org.opendaylight.yangtools.yang.binding.Identifiable;
 import org.opendaylight.yangtools.yang.binding.Identifier;
 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier.IdentifiableItem;
 import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
 
-abstract class IdentifiableItemCodec implements Codec<NodeIdentifierWithPredicates, IdentifiableItem<?, ?>> {
+/**
+ * Codec support for extracting the {@link Identifiable#key()} method return from a MapEntryNode. This class is public
+ * only because implementation restrictions and can change at any time.
+ */
+@Beta
+public abstract class IdentifiableItemCodec implements Codec<NodeIdentifierWithPredicates, IdentifiableItem<?, ?>> {
     private static final class SingleKey extends IdentifiableItemCodec {
         private static final MethodType CTOR_TYPE = MethodType.methodType(Identifier.class, Object.class);
 
index 2c16d7a49d4111ea614f62fe0dbe04bbf616f8d7..166cad8b44c279d93280a5cc690ae55371502439 100644 (file)
@@ -7,9 +7,11 @@
  */
 package org.opendaylight.mdsal.binding.dom.codec.impl;
 
+import static java.util.Objects.requireNonNull;
 import static org.opendaylight.mdsal.binding.spec.naming.BindingMapping.IDENTIFIABLE_KEY_NAME;
 
 import java.lang.reflect.Method;
+import java.util.AbstractMap.SimpleImmutableEntry;
 import java.util.List;
 import org.opendaylight.yangtools.yang.binding.DataObject;
 import org.opendaylight.yangtools.yang.binding.Identifiable;
@@ -18,25 +20,30 @@ import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier.IdentifiableItem;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
-import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
-import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer;
 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
 
 final class KeyedListNodeCodecContext<D extends DataObject & Identifiable<?>> extends ListNodeCodecContext<D> {
     private final IdentifiableItemCodec codec;
 
-    KeyedListNodeCodecContext(final DataContainerCodecPrototype<ListSchemaNode> prototype) {
-        super(prototype, keyMethod(prototype.getBindingClass()));
-        this.codec = factory().getPathArgumentCodec(getBindingClass(), getSchema());
+    private KeyedListNodeCodecContext(final DataContainerCodecPrototype<ListSchemaNode> prototype,
+            final Method keyMethod, final IdentifiableItemCodec codec) {
+        super(prototype, new SimpleImmutableEntry<>(keyMethod, codec));
+        this.codec = requireNonNull(codec);
     }
 
-    private static Method keyMethod(final Class<?> bindingClass) {
+    @SuppressWarnings("rawtypes")
+    static KeyedListNodeCodecContext create(final DataContainerCodecPrototype<ListSchemaNode> prototype) {
+        final Class<?> bindingClass = prototype.getBindingClass();
+        final Method keyMethod;
         try {
-            // This just verifies the method is present
-            return bindingClass.getMethod(IDENTIFIABLE_KEY_NAME);
+            keyMethod = bindingClass.getMethod(IDENTIFIABLE_KEY_NAME);
         } catch (NoSuchMethodException e) {
             throw new IllegalStateException("Required method not available", e);
         }
+
+        final IdentifiableItemCodec codec = prototype.getFactory().getPathArgumentCodec(bindingClass,
+            prototype.getSchema());
+        return new KeyedListNodeCodecContext<>(prototype, keyMethod, codec);
     }
 
     @Override
@@ -59,16 +66,6 @@ final class KeyedListNodeCodecContext<D extends DataObject & Identifiable<?>> ex
         }
     }
 
-    @Override
-    @SuppressWarnings("rawtypes")
-    Object getBindingChildValue(final NormalizedNodeContainer dom, final int offset) {
-        if (offset == CodecDataObjectCustomizer.KEY_OFFSET && dom instanceof MapEntryNode) {
-            NodeIdentifierWithPredicates identifier = ((MapEntryNode) dom).getIdentifier();
-            return codec.deserialize(identifier).getKey();
-        }
-        return super.getBindingChildValue(dom, offset);
-    }
-
     @Override
     protected InstanceIdentifier.PathArgument getBindingPathArgument(final YangInstanceIdentifier.PathArgument domArg) {
         if (domArg instanceof NodeIdentifierWithPredicates) {
index 7380700b8525eb7032b11038560bc6c39d358f4c..a3755a31b8b3d02da9857a4b647c292eac299b21 100644 (file)
@@ -10,6 +10,7 @@ package org.opendaylight.mdsal.binding.dom.codec.impl;
 import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map.Entry;
 import org.opendaylight.yangtools.yang.binding.DataObject;
 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
@@ -18,12 +19,13 @@ import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListEntryNode;
 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> {
+class ListNodeCodecContext<D extends DataObject> extends DataObjectCodecContext<D, ListSchemaNode> {
     ListNodeCodecContext(final DataContainerCodecPrototype<ListSchemaNode> prototype) {
         super(prototype);
     }
 
-    ListNodeCodecContext(final DataContainerCodecPrototype<ListSchemaNode> prototype, final Method keyMethod) {
+    ListNodeCodecContext(final DataContainerCodecPrototype<ListSchemaNode> prototype,
+            final Entry<Method, IdentifiableItemCodec> keyMethod) {
         super(prototype, keyMethod);
     }
 
index 76d40c23c8bf9782ad6aa34c05b255ce5d234f5a..b9c84703f2f0f18fdad32e3934e03b123c46353f 100644 (file)
@@ -7,13 +7,16 @@
  */
 package org.opendaylight.mdsal.binding.dom.codec.impl;
 
-import java.util.function.Supplier;
-import org.eclipse.jdt.annotation.NonNull;
+import com.google.common.annotations.Beta;
+import org.eclipse.jdt.annotation.NonNullByDefault;
 
 /**
- * Type capture of an entity producing NodeCodecContexts.
+ * Type capture of an entity producing NodeCodecContexts. Implementations are required to perform memoization. This
+ * interface does not form API surface and is exposed only for generated code. It can change at any time.
  */
-interface NodeContextSupplier extends Supplier<NodeCodecContext> {
-    @Override
-    @NonNull NodeCodecContext get();
+@Beta
+@FunctionalInterface
+@NonNullByDefault
+public interface NodeContextSupplier {
+    NodeCodecContext get();
 }