From: Robert Varga Date: Sun, 25 Nov 2018 15:01:34 +0000 (+0100) Subject: Separate out single-key Identifiable handling X-Git-Tag: v3.0.3~22 X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=commitdiff_plain;h=581410c9bc93c396631b8a09be0df7bc3faf03d3;p=mdsal.git Separate out single-key Identifiable handling 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 --- diff --git a/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/BindingCodecContext.java b/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/BindingCodecContext.java index f42b4808f6..8c235cc099 100644 --- a/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/BindingCodecContext.java +++ b/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/BindingCodecContext.java @@ -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") 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 38eee35891..0b38fef314 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 @@ -7,123 +7,174 @@ */ 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> { - private final ImmutableMapTemplate predicateTemplate; - private final Map keyValueContexts; - private final List keysInBindingOrder; - private final ListSchemaNode schema; - private final Class identifiable; - private final MethodHandle ctor; +abstract class IdentifiableItemCodec implements Codec> { + 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> keyClass, - final Class identifiable, final Map keyValueContexts) { - this.schema = schema; - this.identifiable = identifiable; + private final SharedSingletonMapTemplate 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> 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 unsortedKeys = schema.getKeyDefinition(); - final List sortedKeys; - if (unsortedKeys.size() > 1) { - final List tmp = new ArrayList<>(unsortedKeys); + + @Override + Identifier deserializeIdentifier(final Map 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 predicateTemplate; + private final ImmutableOffsetMap keyValueContexts; + private final ImmutableList keysInBindingOrder; + private final MethodHandle ctor; + + MultiKey(final ListSchemaNode schema, final Class> keyClass, + final Class identifiable, final Map 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 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 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 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 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> keyClass, + final Class identifiable) { + this.identifiable = requireNonNull(identifiable); + this.qname = schema.getQName(); + } + + static IdentifiableItemCodec of(final ListSchemaNode schema, + final Class> keyClass, final Class identifiable, + final Map keyValueContexts) { + switch (keyValueContexts.size()) { + case 0: + throw new IllegalArgumentException("Key " + keyClass + " of " + identifiable + " has no components"); + case 1: + final Entry 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> getConstructor(final Class> clazz) { + @SuppressWarnings("checkstyle:illegalThrows") + abstract Identifier deserializeIdentifier(Map keyValues) throws Throwable; + + abstract NodeIdentifierWithPredicates serializeIdentifier(QName qname, Identifier key); + + static MethodHandle getConstructor(final Class> 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.");