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=0b89bff24fda6e2ea4e277388b5c999d72c8c9a2;hb=984dfcd854a006724cbf0b20efc8bac6094bad48;hp=57b45a2d7199e1efec7f590dbb64cfa710165eaa;hpb=67d0d71b7218015f5eaf311d732a0db1dd079021;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 57b45a2d71..0b89bff24f 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,127 +7,204 @@ */ 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 com.google.common.collect.ImmutableMap; 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.Collections; -import java.util.LinkedHashMap; +import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.Map.Entry; -import org.opendaylight.yangtools.concepts.Codec; +import java.util.Set; +import org.eclipse.jdt.annotation.NonNull; +import org.opendaylight.mdsal.binding.spec.naming.BindingMapping; +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 Map keyValueContexts; - private final List keysInBindingOrder; - private final ListSchemaNode schema; - private final Class identifiable; - private final MethodHandle ctorInvoker; - private final MethodHandle ctor; +/** + * Codec support for extracting the {@link Identifiable#key()} method return from a MapEntryNode. + */ +// FIXME: sealed class when we have JDK17+ +abstract class IdentifiableItemCodec { + 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; - try { - ctor = 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); } - final MethodHandle inv = MethodHandles.spreadInvoker(ctor.type(), 0); - this.ctorInvoker = inv.asType(inv.type().changeReturnType(Identifier.class)); - - /* - * We need to re-index to make sure we instantiate nodes in the order in which - * they are defined. - */ - final Map keys = new LinkedHashMap<>(); - for (final QName qname : schema.getKeyDefinition()) { - keys.put(qname, keyValueContexts.get(qname)); + + @Override + Identifier deserializeIdentifierImpl(final NodeIdentifierWithPredicates nip) throws Throwable { + return (Identifier) ctor.invokeExact(keyContext.deserialize(nip.getValue(keyName))); } - this.keyValueContexts = ImmutableMap.copyOf(keys); - - /* - * 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); - Collections.sort(tmp, (q1, q2) -> q1.getLocalName().compareToIgnoreCase(q2.getLocalName())); - sortedKeys = tmp; - } else { - sortedKeys = 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); - this.keysInBindingOrder = ImmutableList.copyOf(sortedKeys); + /* + * 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()))); + 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); + } + + @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 Object[] bindingValues = new Object[keysInBindingOrder.size()]; - int offset = 0; + private static final Logger LOG = LoggerFactory.getLogger(IdentifiableItemCodec.class); - for (final QName key : keysInBindingOrder) { - final Object yangValue = input.getKeyValues().get(key); - bindingValues[offset++] = keyValueContexts.get(key).deserialize(yangValue); + 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); } + } - final Identifier identifier; + @SuppressWarnings({ "rawtypes", "unchecked" }) + final @NonNull IdentifiableItem domToBinding(final NodeIdentifierWithPredicates input) { + return IdentifiableItem.of((Class) identifiable, (Identifier) deserializeIdentifier(requireNonNull(input))); + } + + final @NonNull NodeIdentifierWithPredicates bindingToDom(final IdentifiableItem input) { + return serializeIdentifier(qname, input.getKey()); + } + + @SuppressWarnings("checkstyle:illegalCatch") + final @NonNull Identifier deserializeIdentifier(final @NonNull NodeIdentifierWithPredicates input) { try { - identifier = (Identifier) ctorInvoker.invokeExact(ctor, 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 = new IdentifiableItem(identifiable, identifier); - return identifiableItem; } - @Override - public NodeIdentifierWithPredicates serialize(final IdentifiableItem input) { - final Object value = input.getKey(); + @SuppressWarnings("checkstyle:illegalThrows") + abstract @NonNull Identifier deserializeIdentifierImpl(@NonNull NodeIdentifierWithPredicates nip) + throws Throwable; - final Map values = new LinkedHashMap<>(); - for (final Entry valueCtx : keyValueContexts.entrySet()) { - values.put(valueCtx.getKey(), valueCtx.getValue().getAndSerialize(value)); - } - return new NodeIdentifierWithPredicates(schema.getQName(), 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; + } + + // 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; + } - @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; + 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; } }