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