X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=blobdiff_plain;f=binding%2Fmdsal-binding-dom-codec%2Fsrc%2Fmain%2Fjava%2Forg%2Fopendaylight%2Fmdsal%2Fbinding%2Fdom%2Fcodec%2Fimpl%2FIdentifiableItemCodec.java;h=f7ef432da873185892d7d356afd62fe11fc75d5d;hb=refs%2Fchanges%2F45%2F98245%2F100;hp=38eee358916691fef1eac99e7236accc887be9f3;hpb=f6557f4f6831805301ea4690596310fcc03247c3;p=mdsal.git 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..f7ef432da8 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,125 +7,208 @@ */ 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 java.util.Set; +import org.eclipse.jdt.annotation.NonNull; import org.opendaylight.mdsal.binding.spec.naming.BindingMapping; -import org.opendaylight.yangtools.concepts.Codec; -import org.opendaylight.yangtools.util.ImmutableMapTemplate; +import org.opendaylight.yangtools.concepts.AbstractIllegalArgumentCodec; +import org.opendaylight.yangtools.util.ImmutableOffsetMap; +import org.opendaylight.yangtools.util.ImmutableOffsetMapTemplate; +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; +import org.opendaylight.yangtools.yang.model.api.stmt.KeyEffectiveStatement; +import org.opendaylight.yangtools.yang.model.api.stmt.ListEffectiveStatement; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -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; +/** + * Codec support for extracting the {@link Identifiable#key()} method return from a MapEntryNode. + */ +abstract class IdentifiableItemCodec + extends AbstractIllegalArgumentCodec> { + 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 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 ListEffectiveStatement 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); + ctor = getConstructor(keyClass, 1).asType(CTOR_TYPE); + } + + @Override + Identifier deserializeIdentifierImpl(final NodeIdentifierWithPredicates nip) throws Throwable { + return (Identifier) ctor.invokeExact(keyContext.deserialize(nip.getValue(keyName))); } - 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 + NodeIdentifierWithPredicates serializeIdentifier(final QName qname, final Identifier key) { + return NodeIdentifierWithPredicates.of(qname, keyName, 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 ListEffectiveStatement schema, final Class> keyClass, + final Class identifiable, final Map keyValueContexts) { + super(schema, keyClass, identifiable); + + final MethodHandle tmpCtor = getConstructor(keyClass, keyValueContexts.size()); + final MethodHandle inv = MethodHandles.spreadInvoker(tmpCtor.type(), 0); + 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 Set keyDef = schema.findFirstEffectiveSubstatementArgument(KeyEffectiveStatement.class) + .orElseThrow(); + 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; + keysInBindingOrder = ImmutableList.copyOf(tmp.equals(List.copyOf(keyDef)) ? keyDef : tmp); + } + + @Override + Identifier deserializeIdentifierImpl(final NodeIdentifierWithPredicates nip) throws Throwable { + final Object[] bindingValues = new Object[keysInBindingOrder.size()]; + int offset = 0; + for (final QName key : keysInBindingOrder) { + bindingValues[offset++] = keyValueContexts.get(key).deserialize(nip.getValue(key)); + } + + return (Identifier) ctor.invokeExact(bindingValues); } - this.keysInBindingOrder = ImmutableList.copyOf(sortedKeys); + @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 NodeIdentifierWithPredicates.of(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 static final Logger LOG = LoggerFactory.getLogger(IdentifiableItemCodec.class); + + private final Class identifiable; + private final QName qname; + + IdentifiableItemCodec(final ListEffectiveStatement schema, final Class> keyClass, + final Class identifiable) { + this.identifiable = requireNonNull(identifiable); + qname = schema.argument(); + } + + static IdentifiableItemCodec of(final ListEffectiveStatement 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" }) + protected final IdentifiableItem deserializeImpl(final NodeIdentifierWithPredicates input) { + final Identifier identifier = deserializeIdentifier(input); + return IdentifiableItem.of((Class) identifiable, (Identifier) identifier); + } + + @Override + protected final NodeIdentifierWithPredicates serializeImpl(final IdentifiableItem input) { + return serializeIdentifier(qname, input.getKey()); + } - final Identifier identifier; + @SuppressWarnings("checkstyle:illegalCatch") + final @NonNull Identifier deserializeIdentifier(final NodeIdentifierWithPredicates input) { try { - identifier = (Identifier) ctor.invokeExact(bindingValues); + return deserializeIdentifierImpl(input); } catch (Throwable e) { Throwables.throwIfUnchecked(e); - throw new RuntimeException(e); + throw new IllegalStateException("Failed to deserialize " + input, e); } - - @SuppressWarnings({ "rawtypes", "unchecked" }) - final IdentifiableItem identifiableItem = IdentifiableItem.of((Class) identifiable, (Identifier) identifier); - return identifiableItem; } - @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); - } + @SuppressWarnings("checkstyle:illegalThrows") + abstract @NonNull Identifier deserializeIdentifierImpl(@NonNull NodeIdentifierWithPredicates nip) + throws Throwable; - return new NodeIdentifierWithPredicates(schema.getQName(), predicateTemplate.instantiateWithValues(values)); - } + abstract @NonNull NodeIdentifierWithPredicates serializeIdentifier(QName qname, Identifier key); + + static MethodHandle getConstructor(final Class> clazz, final int nrArgs) { + for (final Constructor ctor : clazz.getConstructors()) { + // Check argument count + if (ctor.getParameterCount() != nrArgs) { + LOG.debug("Skipping {} due to argument count mismatch", ctor); + continue; + } - @SuppressWarnings("unchecked") - private static Constructor> 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; + // Do not consider deprecated constructors + if (isDeprecated(ctor)) { + LOG.debug("Skipping deprecated constructor {}", ctor); + continue; + } + + // Do not consider copy constructors + if (clazz.equals(ctor.getParameterTypes()[0])) { + LOG.debug("Skipping copy constructor {}", ctor); + continue; + } + + try { + return MethodHandles.publicLookup().unreflectConstructor(ctor); + } catch (IllegalAccessException e) { + throw new IllegalStateException("Cannot access constructor " + ctor + " in class " + clazz, e); } } - throw new IllegalArgumentException("Supplied class " + clazz + "does not have required constructor."); + throw new IllegalArgumentException("Supplied class " + clazz + " does not have required constructor."); + } + + // This could be inlined, but then it throws off Eclipse analysis, which thinks the return is always non-null + private static boolean isDeprecated(final Constructor ctor) { + return ctor.getAnnotation(Deprecated.class) != null; } }