2 * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
8 package org.opendaylight.mdsal.binding.dom.codec.impl;
10 import static java.util.Objects.requireNonNull;
12 import com.google.common.base.Throwables;
13 import com.google.common.collect.ImmutableList;
14 import java.lang.invoke.MethodHandle;
15 import java.lang.invoke.MethodHandles;
16 import java.lang.invoke.MethodType;
17 import java.lang.reflect.Constructor;
18 import java.util.ArrayList;
19 import java.util.Comparator;
20 import java.util.List;
22 import org.eclipse.jdt.annotation.NonNull;
23 import org.opendaylight.yangtools.util.ImmutableOffsetMap;
24 import org.opendaylight.yangtools.util.ImmutableOffsetMapTemplate;
25 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier.IdentifiableItem;
26 import org.opendaylight.yangtools.yang.binding.Key;
27 import org.opendaylight.yangtools.yang.binding.KeyAware;
28 import org.opendaylight.yangtools.yang.binding.contract.Naming;
29 import org.opendaylight.yangtools.yang.common.QName;
30 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
31 import org.opendaylight.yangtools.yang.model.api.stmt.KeyEffectiveStatement;
32 import org.opendaylight.yangtools.yang.model.api.stmt.ListEffectiveStatement;
33 import org.slf4j.Logger;
34 import org.slf4j.LoggerFactory;
37 * Codec support for extracting the {@link KeyAware#key()} method return from a MapEntryNode.
39 abstract sealed class IdentifiableItemCodec {
40 private static final class SingleKey extends IdentifiableItemCodec {
41 private static final MethodType CTOR_TYPE = MethodType.methodType(Key.class, Object.class);
43 private final ValueContext keyContext;
44 private final MethodHandle ctor;
45 private final QName keyName;
47 SingleKey(final ListEffectiveStatement schema, final Class<? extends Key<?>> keyClass,
48 final Class<?> identifiable, final QName keyName, final ValueContext keyContext) {
49 super(schema, keyClass, identifiable);
50 this.keyContext = requireNonNull(keyContext);
51 this.keyName = requireNonNull(keyName);
52 ctor = getConstructor(keyClass, 1).asType(CTOR_TYPE);
56 Key<?> deserializeIdentifierImpl(final NodeIdentifierWithPredicates nip) throws Throwable {
57 return (Key<?>) ctor.invokeExact(keyContext.deserialize(nip.getValue(keyName)));
61 NodeIdentifierWithPredicates serializeIdentifier(final QName qname, final Key<?> key) {
62 return NodeIdentifierWithPredicates.of(qname, keyName, keyContext.getAndSerialize(key));
66 private static final class MultiKey extends IdentifiableItemCodec {
67 private final ImmutableOffsetMapTemplate<QName> predicateTemplate;
68 private final ImmutableOffsetMap<QName, ValueContext> keyValueContexts;
69 private final ImmutableList<QName> keysInBindingOrder;
70 private final MethodHandle ctor;
72 MultiKey(final ListEffectiveStatement schema, final Class<? extends Key<?>> keyClass,
73 final Class<?> identifiable, final Map<QName, ValueContext> keyValueContexts) {
74 super(schema, keyClass, identifiable);
76 final var tmpCtor = getConstructor(keyClass, keyValueContexts.size());
77 final var inv = MethodHandles.spreadInvoker(tmpCtor.type(), 0);
78 ctor = inv.asType(inv.type().changeReturnType(Key.class)).bindTo(tmpCtor);
81 * We need to re-index to make sure we instantiate nodes in the order in which they are defined. We will
82 * also need to instantiate values in the same order.
84 final var keyDef = schema.findFirstEffectiveSubstatementArgument(KeyEffectiveStatement.class).orElseThrow();
85 predicateTemplate = ImmutableOffsetMapTemplate.ordered(keyDef);
86 this.keyValueContexts = predicateTemplate.instantiateTransformed(keyValueContexts, (key, value) -> value);
89 * When instantiating binding objects we need to specify constructor arguments in alphabetic order. If the
90 * order matches definition order, we try to reuse the key definition.
92 * BUG-2755: remove this if order is made declaration-order-dependent
94 final var tmp = new ArrayList<>(keyDef);
95 // This is not terribly efficient but gets the job done
96 tmp.sort(Comparator.comparing(leaf -> Naming.getPropertyName(leaf.getLocalName())));
97 keysInBindingOrder = ImmutableList.copyOf(tmp.equals(List.copyOf(keyDef)) ? keyDef : tmp);
101 Key<?> deserializeIdentifierImpl(final NodeIdentifierWithPredicates nip) throws Throwable {
102 final var bindingValues = new Object[keysInBindingOrder.size()];
104 for (var key : keysInBindingOrder) {
105 bindingValues[offset++] = keyValueContexts.get(key).deserialize(nip.getValue(key));
108 return (Key<?>) ctor.invokeExact(bindingValues);
112 NodeIdentifierWithPredicates serializeIdentifier(final QName qname, final Key<?> key) {
113 final var values = new Object[keyValueContexts.size()];
115 for (var valueCtx : keyValueContexts.values()) {
116 values[offset++] = valueCtx.getAndSerialize(key);
119 return NodeIdentifierWithPredicates.of(qname, predicateTemplate.instantiateWithValues(values));
123 private static final Logger LOG = LoggerFactory.getLogger(IdentifiableItemCodec.class);
125 private final Class<?> identifiable;
126 private final QName qname;
128 private IdentifiableItemCodec(final ListEffectiveStatement schema, final Class<? extends Key<?>> keyClass,
129 final Class<?> identifiable) {
130 this.identifiable = requireNonNull(identifiable);
131 qname = schema.argument();
134 static IdentifiableItemCodec of(final ListEffectiveStatement schema, final Class<? extends Key<?>> keyClass,
135 final Class<?> identifiable, final Map<QName, ValueContext> keyValueContexts) {
136 return switch (keyValueContexts.size()) {
137 case 0 -> throw new IllegalArgumentException(
138 "Key " + keyClass + " of " + identifiable + " has no components");
140 final var entry = keyValueContexts.entrySet().iterator().next();
141 yield new SingleKey(schema, keyClass, identifiable, entry.getKey(), entry.getValue());
143 default -> new MultiKey(schema, keyClass, identifiable, keyValueContexts);
147 @SuppressWarnings({ "rawtypes", "unchecked" })
148 final @NonNull IdentifiableItem<?, ?> domToBinding(final NodeIdentifierWithPredicates input) {
149 return IdentifiableItem.of((Class) identifiable, (Key) deserializeIdentifier(requireNonNull(input)));
152 final @NonNull NodeIdentifierWithPredicates bindingToDom(final IdentifiableItem<?, ?> input) {
153 return serializeIdentifier(qname, input.getKey());
156 @SuppressWarnings("checkstyle:illegalCatch")
157 final @NonNull Key<?> deserializeIdentifier(final @NonNull NodeIdentifierWithPredicates input) {
159 return deserializeIdentifierImpl(input);
160 } catch (Throwable e) {
161 Throwables.throwIfUnchecked(e);
162 throw new IllegalStateException("Failed to deserialize " + input, e);
166 @SuppressWarnings("checkstyle:illegalThrows")
167 abstract @NonNull Key<?> deserializeIdentifierImpl(@NonNull NodeIdentifierWithPredicates nip) throws Throwable;
169 abstract @NonNull NodeIdentifierWithPredicates serializeIdentifier(QName qname, Key<?> key);
171 static MethodHandle getConstructor(final Class<? extends Key<?>> clazz, final int nrArgs) {
172 for (var ctor : clazz.getConstructors()) {
173 // Check argument count
174 if (ctor.getParameterCount() != nrArgs) {
175 LOG.debug("Skipping {} due to argument count mismatch", ctor);
179 // Do not consider deprecated constructors
180 if (isDeprecated(ctor)) {
181 LOG.debug("Skipping deprecated constructor {}", ctor);
185 // Do not consider copy constructors
186 if (clazz.equals(ctor.getParameterTypes()[0])) {
187 LOG.debug("Skipping copy constructor {}", ctor);
192 return MethodHandles.publicLookup().unreflectConstructor(ctor);
193 } catch (IllegalAccessException e) {
194 throw new IllegalStateException("Cannot access constructor " + ctor + " in class " + clazz, e);
197 throw new IllegalArgumentException("Supplied class " + clazz + " does not have required constructor.");
200 // This could be inlined, but then it throws off Eclipse analysis, which thinks the return is always non-null
201 private static boolean isDeprecated(final Constructor<?> ctor) {
202 return ctor.getAnnotation(Deprecated.class) != null;