Rehost BindingMapping in yang.binding.contract.Naming
[mdsal.git] / binding / mdsal-binding-dom-codec / src / main / java / org / opendaylight / mdsal / binding / dom / codec / impl / IdentifiableItemCodec.java
1 /*
2  * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
3  *
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
7  */
8 package org.opendaylight.mdsal.binding.dom.codec.impl;
9
10 import static java.util.Objects.requireNonNull;
11
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;
21 import java.util.Map;
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.Identifiable;
26 import org.opendaylight.yangtools.yang.binding.Identifier;
27 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier.IdentifiableItem;
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;
35
36 /**
37  * Codec support for extracting the {@link Identifiable#key()} method return from a MapEntryNode.
38  */
39 // FIXME: sealed class when we have JDK17+
40 abstract class IdentifiableItemCodec {
41     private static final class SingleKey extends IdentifiableItemCodec {
42         private static final MethodType CTOR_TYPE = MethodType.methodType(Identifier.class, Object.class);
43
44         private final ValueContext keyContext;
45         private final MethodHandle ctor;
46         private final QName keyName;
47
48         SingleKey(final ListEffectiveStatement schema, final Class<? extends Identifier<?>> keyClass,
49                 final Class<?> identifiable, final QName keyName, final ValueContext keyContext) {
50             super(schema, keyClass, identifiable);
51             this.keyContext = requireNonNull(keyContext);
52             this.keyName = requireNonNull(keyName);
53             ctor = getConstructor(keyClass, 1).asType(CTOR_TYPE);
54         }
55
56         @Override
57         Identifier<?> deserializeIdentifierImpl(final NodeIdentifierWithPredicates nip) throws Throwable {
58             return (Identifier<?>) ctor.invokeExact(keyContext.deserialize(nip.getValue(keyName)));
59         }
60
61         @Override
62         NodeIdentifierWithPredicates serializeIdentifier(final QName qname, final Identifier<?> key) {
63             return NodeIdentifierWithPredicates.of(qname, keyName, keyContext.getAndSerialize(key));
64         }
65     }
66
67     private static final class MultiKey extends IdentifiableItemCodec {
68         private final ImmutableOffsetMapTemplate<QName> predicateTemplate;
69         private final ImmutableOffsetMap<QName, ValueContext> keyValueContexts;
70         private final ImmutableList<QName> keysInBindingOrder;
71         private final MethodHandle ctor;
72
73         MultiKey(final ListEffectiveStatement schema, final Class<? extends Identifier<?>> keyClass,
74                 final Class<?> identifiable, final Map<QName, ValueContext> keyValueContexts) {
75             super(schema, keyClass, identifiable);
76
77             final var tmpCtor = getConstructor(keyClass, keyValueContexts.size());
78             final var inv = MethodHandles.spreadInvoker(tmpCtor.type(), 0);
79             ctor = inv.asType(inv.type().changeReturnType(Identifier.class)).bindTo(tmpCtor);
80
81             /*
82              * We need to re-index to make sure we instantiate nodes in the order in which they are defined. We will
83              * also need to instantiate values in the same order.
84              */
85             final var keyDef = schema.findFirstEffectiveSubstatementArgument(KeyEffectiveStatement.class).orElseThrow();
86             predicateTemplate = ImmutableOffsetMapTemplate.ordered(keyDef);
87             this.keyValueContexts = predicateTemplate.instantiateTransformed(keyValueContexts, (key, value) -> value);
88
89             /*
90              * When instantiating binding objects we need to specify constructor arguments in alphabetic order. If the
91              * order matches definition order, we try to reuse the key definition.
92              *
93              * BUG-2755: remove this if order is made declaration-order-dependent
94              */
95             final var tmp = new ArrayList<>(keyDef);
96             // This is not terribly efficient but gets the job done
97             tmp.sort(Comparator.comparing(leaf -> Naming.getPropertyName(leaf.getLocalName())));
98             keysInBindingOrder = ImmutableList.copyOf(tmp.equals(List.copyOf(keyDef)) ? keyDef : tmp);
99         }
100
101         @Override
102         Identifier<?> deserializeIdentifierImpl(final NodeIdentifierWithPredicates nip) throws Throwable {
103             final var bindingValues = new Object[keysInBindingOrder.size()];
104             int offset = 0;
105             for (var key : keysInBindingOrder) {
106                 bindingValues[offset++] = keyValueContexts.get(key).deserialize(nip.getValue(key));
107             }
108
109             return (Identifier<?>) ctor.invokeExact(bindingValues);
110         }
111
112         @Override
113         NodeIdentifierWithPredicates serializeIdentifier(final QName qname, final Identifier<?> key) {
114             final var values = new Object[keyValueContexts.size()];
115             int offset = 0;
116             for (var valueCtx : keyValueContexts.values()) {
117                 values[offset++] = valueCtx.getAndSerialize(key);
118             }
119
120             return NodeIdentifierWithPredicates.of(qname, predicateTemplate.instantiateWithValues(values));
121         }
122     }
123
124     private static final Logger LOG = LoggerFactory.getLogger(IdentifiableItemCodec.class);
125
126     private final Class<?> identifiable;
127     private final QName qname;
128
129     IdentifiableItemCodec(final ListEffectiveStatement schema, final Class<? extends Identifier<?>> keyClass,
130             final Class<?> identifiable) {
131         this.identifiable = requireNonNull(identifiable);
132         qname = schema.argument();
133     }
134
135     static IdentifiableItemCodec of(final ListEffectiveStatement schema, final Class<? extends Identifier<?>> keyClass,
136             final Class<?> identifiable, final Map<QName, ValueContext> keyValueContexts) {
137         return switch (keyValueContexts.size()) {
138             case 0 -> throw new IllegalArgumentException(
139                 "Key " + keyClass + " of " + identifiable + " has no components");
140             case 1 -> {
141                 final var entry = keyValueContexts.entrySet().iterator().next();
142                 yield new SingleKey(schema, keyClass, identifiable, entry.getKey(), entry.getValue());
143             }
144             default -> new MultiKey(schema, keyClass, identifiable, keyValueContexts);
145         };
146     }
147
148     @SuppressWarnings({ "rawtypes", "unchecked" })
149     final @NonNull IdentifiableItem<?, ?> domToBinding(final NodeIdentifierWithPredicates input) {
150         return IdentifiableItem.of((Class) identifiable, (Identifier) deserializeIdentifier(requireNonNull(input)));
151     }
152
153     final @NonNull NodeIdentifierWithPredicates bindingToDom(final IdentifiableItem<?, ?> input) {
154         return serializeIdentifier(qname, input.getKey());
155     }
156
157     @SuppressWarnings("checkstyle:illegalCatch")
158     final @NonNull Identifier<?> deserializeIdentifier(final @NonNull NodeIdentifierWithPredicates input) {
159         try {
160             return deserializeIdentifierImpl(input);
161         } catch (Throwable e) {
162             Throwables.throwIfUnchecked(e);
163             throw new IllegalStateException("Failed to deserialize " + input, e);
164         }
165     }
166
167     @SuppressWarnings("checkstyle:illegalThrows")
168     abstract @NonNull Identifier<?> deserializeIdentifierImpl(@NonNull NodeIdentifierWithPredicates nip)
169             throws Throwable;
170
171     abstract @NonNull NodeIdentifierWithPredicates serializeIdentifier(QName qname, Identifier<?> key);
172
173     static MethodHandle getConstructor(final Class<? extends Identifier<?>> clazz, final int nrArgs) {
174         for (var ctor : clazz.getConstructors()) {
175             // Check argument count
176             if (ctor.getParameterCount() != nrArgs) {
177                 LOG.debug("Skipping {} due to argument count mismatch", ctor);
178                 continue;
179             }
180
181             // Do not consider deprecated constructors
182             if (isDeprecated(ctor)) {
183                 LOG.debug("Skipping deprecated constructor {}", ctor);
184                 continue;
185             }
186
187             // Do not consider copy constructors
188             if (clazz.equals(ctor.getParameterTypes()[0])) {
189                 LOG.debug("Skipping copy constructor {}", ctor);
190                 continue;
191             }
192
193             try {
194                 return MethodHandles.publicLookup().unreflectConstructor(ctor);
195             } catch (IllegalAccessException e) {
196                 throw new IllegalStateException("Cannot access constructor " + ctor + " in class " + clazz, e);
197             }
198         }
199         throw new IllegalArgumentException("Supplied class " + clazz + " does not have required constructor.");
200     }
201
202     // This could be inlined, but then it throws off Eclipse analysis, which thinks the return is always non-null
203     private static boolean isDeprecated(final Constructor<?> ctor) {
204         return ctor.getAnnotation(Deprecated.class) != null;
205     }
206 }