Separate out single-key Identifiable handling 10/78110/13
authorRobert Varga <robert.varga@pantheon.tech>
Sun, 25 Nov 2018 15:01:34 +0000 (16:01 +0100)
committerRobert Varga <nite@hq.sk>
Tue, 27 Nov 2018 12:46:48 +0000 (12:46 +0000)
We can further optimize IdentifiableItemCodec by separating
out the common case of a single-key from multiple-keys.

This allows us to specialize ImmutableMapTemplates we use for
the two cases, more efficiently binding to
NodeIdentifierWithPredicates constructor.

Furthermore we can specialize key constructor invocation such
that we directly invoke the constructor for single-key identifiers,
without the need to iterate and allocate arrays.

JIRA: MDSAL-387
Change-Id: I2fc26dc7285bfb3fe829b8b0efd9937daf6fbc8d
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
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/IdentifiableItemCodec.java

index f42b4808f62b18abbec6127f75d224679858f27f..8c235cc099406ec54be45b37965b7aa5a8a92461 100644 (file)
@@ -325,7 +325,7 @@ final class BindingCodecContext implements CodecContextFactory, BindingCodecTree
             final QName name = leaf.getDomPathArgument().getNodeType();
             valueCtx.put(name, new ValueContext(identifier, leaf));
         }
-        return new IdentifiableItemCodec(schema, identifier, listClz, valueCtx);
+        return IdentifiableItemCodec.of(schema, identifier, listClz, valueCtx);
     }
 
     @SuppressWarnings("unchecked")
index 38eee358916691fef1eac99e7236accc887be9f3..0b38fef314919ed43728b17dfc7321c94977d600 100644 (file)
  */
 package org.opendaylight.mdsal.binding.dom.codec.impl;
 
+import static java.util.Objects.requireNonNull;
+
 import com.google.common.base.Throwables;
 import com.google.common.collect.ImmutableList;
 import java.lang.invoke.MethodHandle;
 import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
 import java.lang.reflect.Constructor;
 import java.util.ArrayList;
 import java.util.Comparator;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 import org.opendaylight.mdsal.binding.spec.naming.BindingMapping;
 import org.opendaylight.yangtools.concepts.Codec;
-import org.opendaylight.yangtools.util.ImmutableMapTemplate;
+import org.opendaylight.yangtools.util.ImmutableOffsetMap;
+import org.opendaylight.yangtools.util.ImmutableOffsetMapTemplate;
+import org.opendaylight.yangtools.util.SharedSingletonMapTemplate;
 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;
 
-final class IdentifiableItemCodec implements Codec<NodeIdentifierWithPredicates, IdentifiableItem<?, ?>> {
-    private final ImmutableMapTemplate<QName> predicateTemplate;
-    private final Map<QName, ValueContext> keyValueContexts;
-    private final List<QName> keysInBindingOrder;
-    private final ListSchemaNode schema;
-    private final Class<?> identifiable;
-    private final MethodHandle ctor;
+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);
 
-    IdentifiableItemCodec(final ListSchemaNode schema, final Class<? extends Identifier<?>> keyClass,
-            final Class<?> identifiable, final Map<QName, ValueContext> keyValueContexts) {
-        this.schema = schema;
-        this.identifiable = identifiable;
+        private final SharedSingletonMapTemplate<QName> predicateTemplate;
+        private final ValueContext keyContext;
+        private final MethodHandle ctor;
+        private final QName keyName;
 
-        final MethodHandle tmpCtor;
-        try {
-            tmpCtor = MethodHandles.publicLookup().unreflectConstructor(getConstructor(keyClass));
-        } catch (IllegalAccessException e) {
-            throw new IllegalArgumentException("Missing constructor in class " + keyClass, e);
+        SingleKey(final ListSchemaNode schema, final Class<? extends Identifier<?>> keyClass,
+                final Class<?> identifiable, final QName keyName, final ValueContext keyContext) {
+            super(schema, keyClass, identifiable);
+            this.keyContext = requireNonNull(keyContext);
+            this.keyName = requireNonNull(keyName);
+            predicateTemplate = SharedSingletonMapTemplate.ordered(keyName);
+            ctor = getConstructor(keyClass).asType(CTOR_TYPE);
         }
-        final MethodHandle inv = MethodHandles.spreadInvoker(tmpCtor.type(), 0);
-        this.ctor = inv.asType(inv.type().changeReturnType(Identifier.class)).bindTo(tmpCtor);
-
-        /*
-         * We need to re-index to make sure we instantiate nodes in the order in which they are defined. We will also
-         * need to instantiate values in the same order.
-         */
-        predicateTemplate = ImmutableMapTemplate.ordered(schema.getKeyDefinition());
-        this.keyValueContexts = predicateTemplate.instantiateTransformed(keyValueContexts, (key, value) -> value);
-
-        /*
-         * When instantiating binding objects we need to specify constructor arguments
-         * in alphabetic order. We play a couple of tricks here to optimize CPU/memory
-         * trade-offs.
-         *
-         * We do not have to perform a sort if the source collection has less than two
-         * elements.
-         *
-         * We always perform an ImmutableList.copyOf(), as that will turn into a no-op
-         * if the source is already immutable. It will also produce optimized implementations
-         * for empty and singleton collections.
-         *
-         * BUG-2755: remove this if order is made declaration-order-dependent
-         */
-        final List<QName> unsortedKeys = schema.getKeyDefinition();
-        final List<QName> sortedKeys;
-        if (unsortedKeys.size() > 1) {
-            final List<QName> tmp = new ArrayList<>(unsortedKeys);
+
+        @Override
+        Identifier<?> deserializeIdentifier(final Map<QName, Object> keyValues) throws Throwable {
+            return (Identifier<?>) ctor.invokeExact(keyContext.deserialize(keyValues.get(keyName)));
+        }
+
+        @Override
+        NodeIdentifierWithPredicates serializeIdentifier(final QName qname, final Identifier<?> key) {
+            return new NodeIdentifierWithPredicates(qname, predicateTemplate.instantiateWithValue(
+                keyContext.getAndSerialize(key)));
+        }
+    }
+
+    private static final class MultiKey extends IdentifiableItemCodec {
+        private final ImmutableOffsetMapTemplate<QName> predicateTemplate;
+        private final ImmutableOffsetMap<QName, ValueContext> keyValueContexts;
+        private final ImmutableList<QName> keysInBindingOrder;
+        private final MethodHandle ctor;
+
+        MultiKey(final ListSchemaNode schema, final Class<? extends Identifier<?>> keyClass,
+                final Class<?> identifiable, final Map<QName, ValueContext> keyValueContexts) {
+            super(schema, keyClass, identifiable);
+
+            final MethodHandle tmpCtor = getConstructor(keyClass);
+            final MethodHandle inv = MethodHandles.spreadInvoker(tmpCtor.type(), 0);
+            this.ctor = inv.asType(inv.type().changeReturnType(Identifier.class)).bindTo(tmpCtor);
+
+            /*
+             * We need to re-index to make sure we instantiate nodes in the order in which they are defined. We will
+             * also need to instantiate values in the same order.
+             */
+            final List<QName> keyDef = schema.getKeyDefinition();
+            predicateTemplate = ImmutableOffsetMapTemplate.ordered(keyDef);
+            this.keyValueContexts = predicateTemplate.instantiateTransformed(keyValueContexts, (key, value) -> value);
+
+            /*
+             * When instantiating binding objects we need to specify constructor arguments in alphabetic order. If the
+             * order matches definition order, we try to reuse the key definition.
+             *
+             * BUG-2755: remove this if order is made declaration-order-dependent
+             */
+            final List<QName> tmp = new ArrayList<>(keyDef);
             // This is not terribly efficient but gets the job done
             tmp.sort(Comparator.comparing(qname -> BindingMapping.getPropertyName(qname.getLocalName())));
-            sortedKeys = tmp;
-        } else {
-            sortedKeys = unsortedKeys;
+            this.keysInBindingOrder = ImmutableList.copyOf(tmp.equals(keyDef) ? keyDef : tmp);
         }
 
-        this.keysInBindingOrder = ImmutableList.copyOf(sortedKeys);
+        @Override
+        Identifier<?> deserializeIdentifier(final Map<QName, Object> keyValues) throws Throwable {
+            final Object[] bindingValues = new Object[keysInBindingOrder.size()];
+            int offset = 0;
+            for (final QName key : keysInBindingOrder) {
+                bindingValues[offset++] = keyValueContexts.get(key).deserialize(keyValues.get(key));
+            }
+
+            return (Identifier<?>) ctor.invokeExact(bindingValues);
+        }
+
+        @Override
+        NodeIdentifierWithPredicates serializeIdentifier(final QName qname, final Identifier<?> key) {
+            final Object[] values = new Object[keyValueContexts.size()];
+            int offset = 0;
+            for (final ValueContext valueCtx : keyValueContexts.values()) {
+                values[offset++] = valueCtx.getAndSerialize(key);
+            }
+
+            return new NodeIdentifierWithPredicates(qname, predicateTemplate.instantiateWithValues(values));
+        }
     }
 
-    @Override
-    @SuppressWarnings("checkstyle:illegalCatch")
-    public IdentifiableItem<?, ?> deserialize(final NodeIdentifierWithPredicates input) {
-        final Map<QName, Object> keyValues = input.getKeyValues();
-        final Object[] bindingValues = new Object[keysInBindingOrder.size()];
-        int offset = 0;
-        for (final QName key : keysInBindingOrder) {
-            bindingValues[offset++] = keyValueContexts.get(key).deserialize(keyValues.get(key));
+    private final Class<?> identifiable;
+    private final QName qname;
+
+    IdentifiableItemCodec(final ListSchemaNode schema, final Class<? extends Identifier<?>> keyClass,
+            final Class<?> identifiable) {
+        this.identifiable = requireNonNull(identifiable);
+        this.qname = schema.getQName();
+    }
+
+    static IdentifiableItemCodec of(final ListSchemaNode schema,
+            final Class<? extends Identifier<?>> keyClass, final Class<?> identifiable,
+                    final Map<QName, ValueContext> keyValueContexts) {
+        switch (keyValueContexts.size()) {
+            case 0:
+                throw new IllegalArgumentException("Key " + keyClass + " of " + identifiable + " has no components");
+            case 1:
+                final Entry<QName, ValueContext> entry = keyValueContexts.entrySet().iterator().next();
+                return new SingleKey(schema, keyClass, identifiable, entry.getKey(), entry.getValue());
+            default:
+                return new MultiKey(schema, keyClass, identifiable, keyValueContexts);
         }
+    }
 
+    @Override
+    @SuppressWarnings({ "rawtypes", "unchecked", "checkstyle:illegalCatch" })
+    public final IdentifiableItem<?, ?> deserialize(final NodeIdentifierWithPredicates input) {
         final Identifier<?> identifier;
         try {
-            identifier = (Identifier<?>) ctor.invokeExact(bindingValues);
+            identifier = deserializeIdentifier(input.getKeyValues());
         } catch (Throwable e) {
             Throwables.throwIfUnchecked(e);
             throw new RuntimeException(e);
         }
 
-        @SuppressWarnings({ "rawtypes", "unchecked" })
-        final IdentifiableItem identifiableItem = IdentifiableItem.of((Class) identifiable, (Identifier) identifier);
-        return identifiableItem;
+        return IdentifiableItem.of((Class) identifiable, (Identifier) identifier);
     }
 
     @Override
-    public NodeIdentifierWithPredicates serialize(final IdentifiableItem<?, ?> input) {
-        final Object value = input.getKey();
-        final Object[] values = new Object[keyValueContexts.size()];
-        int offset = 0;
-        for (final ValueContext valueCtx : keyValueContexts.values()) {
-            values[offset++] = valueCtx.getAndSerialize(value);
-        }
-
-        return new NodeIdentifierWithPredicates(schema.getQName(), predicateTemplate.instantiateWithValues(values));
+    public final NodeIdentifierWithPredicates serialize(final IdentifiableItem<?, ?> input) {
+        return serializeIdentifier(qname, input.getKey());
     }
 
-    @SuppressWarnings("unchecked")
-    private static Constructor<? extends Identifier<?>> getConstructor(final Class<? extends Identifier<?>> clazz) {
+    @SuppressWarnings("checkstyle:illegalThrows")
+    abstract Identifier<?> deserializeIdentifier(Map<QName, Object> keyValues) throws Throwable;
+
+    abstract NodeIdentifierWithPredicates serializeIdentifier(QName qname, Identifier<?> key);
+
+    static MethodHandle getConstructor(final Class<? extends Identifier<?>> clazz) {
         for (@SuppressWarnings("rawtypes") final Constructor constr : clazz.getConstructors()) {
             final Class<?>[] parameters = constr.getParameterTypes();
             if (!clazz.equals(parameters[0])) {
                 // It is not copy constructor;
-                return constr;
+                try {
+                    return MethodHandles.publicLookup().unreflectConstructor(constr);
+                } catch (IllegalAccessException e) {
+                    throw new IllegalArgumentException("Cannot access constructor " + constr + " in class " + clazz, e);
+                }
             }
         }
         throw new IllegalArgumentException("Supplied class " + clazz + "does not have required constructor.");