From 49b411fd05d076a363c1c86dca881387f453487f Mon Sep 17 00:00:00 2001 From: Robert Varga Date: Tue, 23 Apr 2019 12:26:47 +0200 Subject: [PATCH] Cache NodeContextSuppliers in CodecDataObject 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 --- .../impl/AugmentableCodecDataObject.java | 8 +- .../dom/codec/impl/CodecDataObject.java | 43 +++++++-- .../dom/codec/impl/CodecDataObjectBridge.java | 53 +++++++++++ .../codec/impl/CodecDataObjectCustomizer.java | 89 +++++++++++++++---- .../codec/impl/DataContainerCodecContext.java | 6 +- .../impl/DataContainerCodecPrototype.java | 5 +- .../codec/impl/DataObjectCodecContext.java | 57 ++++++------ .../dom/codec/impl/IdentifiableItemCodec.java | 9 +- .../codec/impl/KeyedListNodeCodecContext.java | 33 ++++--- .../dom/codec/impl/ListNodeCodecContext.java | 6 +- .../dom/codec/impl/NodeContextSupplier.java | 15 ++-- 11 files changed, 234 insertions(+), 90 deletions(-) create mode 100644 binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/CodecDataObjectBridge.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 index dcc5e5bdbd..43edeadb86 100644 --- 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 @@ -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> extends CodecDataObject implements Augmentable, AugmentationHolder { + private final @NonNull DataObjectCodecContext context; + @SuppressWarnings("rawtypes") private static final AtomicReferenceFieldUpdater CACHED_AUGMENTATIONS_UPDATER = AtomicReferenceFieldUpdater.newUpdater(AugmentableCodecDataObject.class, ImmutableClassToInstanceMap.class, "cachedAugmentations"); private volatile ImmutableClassToInstanceMap> cachedAugmentations; - public AugmentableCodecDataObject(final DataObjectCodecContext ctx, + public AugmentableCodecDataObject(final DataObjectCodecContext context, final NormalizedNodeContainer data) { - super(ctx, data); + super(data); + this.context = requireNonNull(context, "Context must not be null"); } @Override 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 index 78d8bc7cb8..a9446a1394 100644 --- 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 @@ -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 implements DataObjec @SuppressWarnings("rawtypes") private final @NonNull NormalizedNodeContainer data; - final @NonNull DataObjectCodecContext context; private volatile Integer cachedHashcode = null; - public CodecDataObject(final DataObjectCodecContext 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 implements DataObjec // TODO: consider switching to VarHandles for Java 9+ protected final Object codecMember(final AtomicReferenceFieldUpdater, 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, 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 implements DataObjec // Helpers split out of codecMember to aid its inlining private Object loadMember(final AtomicReferenceFieldUpdater, 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> 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, 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, 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 index 0000000000..8324f11dd8 --- /dev/null +++ b/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/CodecDataObjectBridge.java @@ -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 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"); + } +} 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 index 6e90037e5e..f9a3b11540 100644 --- 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 @@ -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 properties; - private final Method keyMethod; + private final ImmutableMap properties; + private final Entry keyMethod; - CodecDataObjectCustomizer(final List properties, final @Nullable Method keyMethod) { + CodecDataObjectCustomizer(final ImmutableMap properties, + final @Nullable Entry 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> 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> 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); diff --git a/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/DataContainerCodecContext.java b/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/DataContainerCodecContext.java index d999cbc14f..738b5011de 100644 --- a/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/DataContainerCodecContext.java +++ b/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/DataContainerCodecContext.java @@ -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 extends NodeCodecContext implements BindingDataObjectCodecTreeNode { + private final @NonNull DataContainerCodecPrototype prototype; - private final DataContainerCodecPrototype prototype; private volatile DataObjectSerializer eventStreamSerializer; protected DataContainerCodecContext(final DataContainerCodecPrototype prototype) { - this.prototype = prototype; + this.prototype = requireNonNull(prototype); } @Override diff --git a/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/DataContainerCodecPrototype.java b/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/DataContainerCodecPrototype.java index f2dd80ebba..35b37a1e76 100644 --- a/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/DataContainerCodecPrototype.java +++ b/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/DataContainerCodecPrototype.java @@ -224,8 +224,9 @@ final class DataContainerCodecPrototype 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) this) + : new ListNodeCodecContext(this); } else if (schema instanceof ChoiceSchemaNode) { return new ChoiceNodeCodecContext(this); } else if (schema instanceof AugmentationSchemaNode) { diff --git a/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/DataObjectCodecContext.java b/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/DataObjectCodecContext.java index 3ac825690d..c5360d940f 100644 --- a/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/DataObjectCodecContext.java +++ b/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/DataObjectCodecContext.java @@ -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 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 leafChild; private final ImmutableMap byYang; private final ImmutableMap, DataContainerCodecPrototype> byStreamClass; private final ImmutableMap, DataContainerCodecPrototype> byBindingArgClass; private final ImmutableMap possibleAugmentations; - private final NodeContextSupplier[] byMethod; private final MethodHandle proxyConstructor; @SuppressWarnings("rawtypes") @@ -109,7 +110,8 @@ abstract class DataObjectCodecContext prototype, final @Nullable Method keyMethod) { + DataObjectCodecContext(final DataContainerCodecPrototype prototype, + final Entry keyMethod) { super(prototype); final Class bindingClass = getBindingClass(); @@ -151,16 +153,12 @@ abstract class DataObjectCodecContext 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 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 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> 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 { diff --git a/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/IdentifiableItemCodec.java b/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/IdentifiableItemCodec.java index 6c90da7643..776cb8e558 100644 --- a/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/IdentifiableItemCodec.java +++ b/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/IdentifiableItemCodec.java @@ -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> { +/** + * 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> { private static final class SingleKey extends IdentifiableItemCodec { private static final MethodType CTOR_TYPE = MethodType.methodType(Identifier.class, Object.class); diff --git a/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/KeyedListNodeCodecContext.java b/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/KeyedListNodeCodecContext.java index 2c16d7a49d..166cad8b44 100644 --- a/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/KeyedListNodeCodecContext.java +++ b/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/KeyedListNodeCodecContext.java @@ -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> extends ListNodeCodecContext { private final IdentifiableItemCodec codec; - KeyedListNodeCodecContext(final DataContainerCodecPrototype prototype) { - super(prototype, keyMethod(prototype.getBindingClass())); - this.codec = factory().getPathArgumentCodec(getBindingClass(), getSchema()); + private KeyedListNodeCodecContext(final DataContainerCodecPrototype 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 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> 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) { diff --git a/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/ListNodeCodecContext.java b/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/ListNodeCodecContext.java index 7380700b85..a3755a31b8 100644 --- a/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/ListNodeCodecContext.java +++ b/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/ListNodeCodecContext.java @@ -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 extends DataObjectCodecContext { +class ListNodeCodecContext extends DataObjectCodecContext { ListNodeCodecContext(final DataContainerCodecPrototype prototype) { super(prototype); } - ListNodeCodecContext(final DataContainerCodecPrototype prototype, final Method keyMethod) { + ListNodeCodecContext(final DataContainerCodecPrototype prototype, + final Entry keyMethod) { super(prototype, keyMethod); } diff --git a/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/NodeContextSupplier.java b/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/NodeContextSupplier.java index 76d40c23c8..b9c84703f2 100644 --- a/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/NodeContextSupplier.java +++ b/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/NodeContextSupplier.java @@ -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 { - @Override - @NonNull NodeCodecContext get(); +@Beta +@FunctionalInterface +@NonNullByDefault +public interface NodeContextSupplier { + NodeCodecContext get(); } -- 2.36.6