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