import static java.util.Objects.requireNonNull;
-import com.google.common.annotations.Beta;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;
import java.lang.invoke.MethodHandle;
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.eclipse.jdt.annotation.NonNull;
import org.opendaylight.yangtools.util.ImmutableOffsetMap;
import org.opendaylight.yangtools.util.ImmutableOffsetMapTemplate;
-import org.opendaylight.yangtools.util.SharedSingletonMapTemplate;
-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.binding.Key;
+import org.opendaylight.yangtools.yang.binding.KeyAware;
+import org.opendaylight.yangtools.yang.binding.KeyStep;
+import org.opendaylight.yangtools.yang.binding.contract.Naming;
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;
/**
- * Codec support for extracting the {@link Identifiable#key()} method return from a MapEntryNode. This class is public
- * only because implementation restrictions and can change at any time.
+ * Codec support for extracting the {@link KeyAware#key()} method return from a MapEntryNode.
*/
-@Beta
-public abstract class IdentifiableItemCodec implements Codec<NodeIdentifierWithPredicates, IdentifiableItem<?, ?>> {
+abstract sealed class IdentifiableItemCodec {
private static final class SingleKey extends IdentifiableItemCodec {
- private static final MethodType CTOR_TYPE = MethodType.methodType(Identifier.class, Object.class);
+ private static final MethodType CTOR_TYPE = MethodType.methodType(Key.class, Object.class);
- private final SharedSingletonMapTemplate<QName> predicateTemplate;
private final ValueContext keyContext;
private final MethodHandle ctor;
private final QName keyName;
- SingleKey(final ListSchemaNode schema, final Class<? extends Identifier<?>> keyClass,
+ SingleKey(final ListEffectiveStatement schema, final Class<? extends Key<?>> 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);
+ ctor = getConstructor(keyClass, 1).asType(CTOR_TYPE);
}
@Override
- Identifier<?> deserializeIdentifier(final Map<QName, Object> keyValues) throws Throwable {
- return (Identifier<?>) ctor.invokeExact(keyContext.deserialize(keyValues.get(keyName)));
+ Key<?> deserializeIdentifierImpl(final NodeIdentifierWithPredicates nip) throws Throwable {
+ return (Key<?>) ctor.invokeExact(keyContext.deserialize(nip.getValue(keyName)));
}
@Override
- NodeIdentifierWithPredicates serializeIdentifier(final QName qname, final Identifier<?> key) {
- return new NodeIdentifierWithPredicates(qname, predicateTemplate.instantiateWithValue(
- keyContext.getAndSerialize(key)));
+ NodeIdentifierWithPredicates serializeIdentifier(final QName qname, final Key<?> key) {
+ return NodeIdentifierWithPredicates.of(qname, keyName, keyContext.getAndSerialize(key));
}
}
private final ImmutableList<QName> keysInBindingOrder;
private final MethodHandle ctor;
- MultiKey(final ListSchemaNode schema, final Class<? extends Identifier<?>> keyClass,
+ MultiKey(final ListEffectiveStatement schema, final Class<? extends Key<?>> 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);
+ final var tmpCtor = getConstructor(keyClass, keyValueContexts.size());
+ final var inv = MethodHandles.spreadInvoker(tmpCtor.type(), 0);
+ ctor = inv.asType(inv.type().changeReturnType(Key.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();
+ final var keyDef = schema.findFirstEffectiveSubstatementArgument(KeyEffectiveStatement.class).orElseThrow();
predicateTemplate = ImmutableOffsetMapTemplate.ordered(keyDef);
this.keyValueContexts = predicateTemplate.instantiateTransformed(keyValueContexts, (key, value) -> value);
*
* BUG-2755: remove this if order is made declaration-order-dependent
*/
- final List<QName> tmp = new ArrayList<>(keyDef);
+ final var tmp = new ArrayList<>(keyDef);
// This is not terribly efficient but gets the job done
- tmp.sort(Comparator.comparing(qname -> BindingMapping.getPropertyName(qname.getLocalName())));
- this.keysInBindingOrder = ImmutableList.copyOf(tmp.equals(keyDef) ? keyDef : tmp);
+ tmp.sort(Comparator.comparing(leaf -> Naming.getPropertyName(leaf.getLocalName())));
+ keysInBindingOrder = ImmutableList.copyOf(tmp.equals(List.copyOf(keyDef)) ? keyDef : tmp);
}
@Override
- Identifier<?> deserializeIdentifier(final Map<QName, Object> keyValues) throws Throwable {
- final Object[] bindingValues = new Object[keysInBindingOrder.size()];
+ Key<?> deserializeIdentifierImpl(final NodeIdentifierWithPredicates nip) throws Throwable {
+ final var bindingValues = new Object[keysInBindingOrder.size()];
int offset = 0;
- for (final QName key : keysInBindingOrder) {
- bindingValues[offset++] = keyValueContexts.get(key).deserialize(keyValues.get(key));
+ for (var key : keysInBindingOrder) {
+ bindingValues[offset++] = keyValueContexts.get(key).deserialize(nip.getValue(key));
}
- return (Identifier<?>) ctor.invokeExact(bindingValues);
+ return (Key<?>) ctor.invokeExact(bindingValues);
}
@Override
- NodeIdentifierWithPredicates serializeIdentifier(final QName qname, final Identifier<?> key) {
- final Object[] values = new Object[keyValueContexts.size()];
+ NodeIdentifierWithPredicates serializeIdentifier(final QName qname, final Key<?> key) {
+ final var values = new Object[keyValueContexts.size()];
int offset = 0;
- for (final ValueContext valueCtx : keyValueContexts.values()) {
+ for (var valueCtx : keyValueContexts.values()) {
values[offset++] = valueCtx.getAndSerialize(key);
}
- return new NodeIdentifierWithPredicates(qname, predicateTemplate.instantiateWithValues(values));
+ return NodeIdentifierWithPredicates.of(qname, predicateTemplate.instantiateWithValues(values));
}
}
- private final Class<?> identifiable;
- private final QName qname;
+ private static final Logger LOG = LoggerFactory.getLogger(IdentifiableItemCodec.class);
- IdentifiableItemCodec(final ListSchemaNode schema, final Class<? extends Identifier<?>> keyClass,
+ private final @NonNull Class<?> identifiable;
+ private final @NonNull QName qname;
+
+ private IdentifiableItemCodec(final ListEffectiveStatement schema, final Class<? extends Key<?>> keyClass,
final Class<?> identifiable) {
this.identifiable = requireNonNull(identifiable);
- this.qname = schema.getQName();
+ qname = schema.argument();
}
- 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);
- }
+ static IdentifiableItemCodec of(final ListEffectiveStatement schema, final Class<? extends Key<?>> keyClass,
+ final Class<?> identifiable, final Map<QName, ValueContext> keyValueContexts) {
+ return switch (keyValueContexts.size()) {
+ case 0 -> throw new IllegalArgumentException(
+ "Key " + keyClass + " of " + identifiable + " has no components");
+ case 1 -> {
+ final var entry = keyValueContexts.entrySet().iterator().next();
+ yield new SingleKey(schema, keyClass, identifiable, entry.getKey(), entry.getValue());
+ }
+ default -> new MultiKey(schema, keyClass, identifiable, keyValueContexts);
+ };
+ }
+
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ final @NonNull KeyStep<?, ?> domToBinding(final NodeIdentifierWithPredicates input) {
+ return new KeyStep(identifiable, deserializeIdentifier(requireNonNull(input)));
}
- @Override
- @SuppressWarnings({ "rawtypes", "unchecked", "checkstyle:illegalCatch" })
- public final IdentifiableItem<?, ?> deserialize(final NodeIdentifierWithPredicates input) {
- final Identifier<?> identifier;
+ final @NonNull NodeIdentifierWithPredicates bindingToDom(final KeyStep<?, ?> input) {
+ return serializeIdentifier(qname, input.key());
+ }
+
+ @SuppressWarnings("checkstyle:illegalCatch")
+ final @NonNull Key<?> deserializeIdentifier(final @NonNull NodeIdentifierWithPredicates input) {
try {
- identifier = deserializeIdentifier(input.getKeyValues());
+ return deserializeIdentifierImpl(input);
} catch (Throwable e) {
Throwables.throwIfUnchecked(e);
throw new IllegalStateException("Failed to deserialize " + input, e);
}
-
- return IdentifiableItem.of((Class) identifiable, (Identifier) identifier);
- }
-
- @Override
- public final NodeIdentifierWithPredicates serialize(final IdentifiableItem<?, ?> input) {
- return serializeIdentifier(qname, input.getKey());
}
@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;
- try {
- return MethodHandles.publicLookup().unreflectConstructor(constr);
- } catch (IllegalAccessException e) {
- throw new IllegalStateException("Cannot access constructor " + constr + " in class " + clazz, e);
- }
+ abstract @NonNull Key<?> deserializeIdentifierImpl(@NonNull NodeIdentifierWithPredicates nip) throws Throwable;
+
+ abstract @NonNull NodeIdentifierWithPredicates serializeIdentifier(QName qname, Key<?> key);
+
+ static MethodHandle getConstructor(final Class<? extends Key<?>> clazz, final int nrArgs) {
+ for (var 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;
+ }
+
+ 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;
}
}