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.annotations.Beta;
13 import com.google.common.base.Throwables;
14 import com.google.common.collect.ImmutableList;
15 import java.lang.invoke.MethodHandle;
16 import java.lang.invoke.MethodHandles;
17 import java.lang.invoke.MethodType;
18 import java.lang.reflect.Constructor;
19 import java.util.ArrayList;
20 import java.util.Comparator;
21 import java.util.List;
23 import java.util.Map.Entry;
24 import org.opendaylight.mdsal.binding.spec.naming.BindingMapping;
25 import org.opendaylight.yangtools.concepts.Codec;
26 import org.opendaylight.yangtools.util.ImmutableOffsetMap;
27 import org.opendaylight.yangtools.util.ImmutableOffsetMapTemplate;
28 import org.opendaylight.yangtools.util.SharedSingletonMapTemplate;
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.ListSchemaNode;
37 * Codec support for extracting the {@link Identifiable#key()} method return from a MapEntryNode. This class is public
38 * only because implementation restrictions and can change at any time.
41 public abstract class IdentifiableItemCodec implements Codec<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 SharedSingletonMapTemplate<QName> predicateTemplate;
46 private final ValueContext keyContext;
47 private final MethodHandle ctor;
48 private final QName keyName;
50 SingleKey(final ListSchemaNode schema, final Class<? extends Identifier<?>> keyClass,
51 final Class<?> identifiable, final QName keyName, final ValueContext keyContext) {
52 super(schema, keyClass, identifiable);
53 this.keyContext = requireNonNull(keyContext);
54 this.keyName = requireNonNull(keyName);
55 predicateTemplate = SharedSingletonMapTemplate.ordered(keyName);
56 ctor = getConstructor(keyClass).asType(CTOR_TYPE);
60 Identifier<?> deserializeIdentifier(final Map<QName, Object> keyValues) throws Throwable {
61 return (Identifier<?>) ctor.invokeExact(keyContext.deserialize(keyValues.get(keyName)));
65 NodeIdentifierWithPredicates serializeIdentifier(final QName qname, final Identifier<?> key) {
66 return new NodeIdentifierWithPredicates(qname, predicateTemplate.instantiateWithValue(
67 keyContext.getAndSerialize(key)));
71 private static final class MultiKey extends IdentifiableItemCodec {
72 private final ImmutableOffsetMapTemplate<QName> predicateTemplate;
73 private final ImmutableOffsetMap<QName, ValueContext> keyValueContexts;
74 private final ImmutableList<QName> keysInBindingOrder;
75 private final MethodHandle ctor;
77 MultiKey(final ListSchemaNode schema, final Class<? extends Identifier<?>> keyClass,
78 final Class<?> identifiable, final Map<QName, ValueContext> keyValueContexts) {
79 super(schema, keyClass, identifiable);
81 final MethodHandle tmpCtor = getConstructor(keyClass);
82 final MethodHandle inv = MethodHandles.spreadInvoker(tmpCtor.type(), 0);
83 this.ctor = inv.asType(inv.type().changeReturnType(Identifier.class)).bindTo(tmpCtor);
86 * We need to re-index to make sure we instantiate nodes in the order in which they are defined. We will
87 * also need to instantiate values in the same order.
89 final List<QName> keyDef = schema.getKeyDefinition();
90 predicateTemplate = ImmutableOffsetMapTemplate.ordered(keyDef);
91 this.keyValueContexts = predicateTemplate.instantiateTransformed(keyValueContexts, (key, value) -> value);
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.
97 * BUG-2755: remove this if order is made declaration-order-dependent
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 this.keysInBindingOrder = ImmutableList.copyOf(tmp.equals(keyDef) ? keyDef : tmp);
106 Identifier<?> deserializeIdentifier(final Map<QName, Object> keyValues) throws Throwable {
107 final Object[] bindingValues = new Object[keysInBindingOrder.size()];
109 for (final QName key : keysInBindingOrder) {
110 bindingValues[offset++] = keyValueContexts.get(key).deserialize(keyValues.get(key));
113 return (Identifier<?>) ctor.invokeExact(bindingValues);
117 NodeIdentifierWithPredicates serializeIdentifier(final QName qname, final Identifier<?> key) {
118 final Object[] values = new Object[keyValueContexts.size()];
120 for (final ValueContext valueCtx : keyValueContexts.values()) {
121 values[offset++] = valueCtx.getAndSerialize(key);
124 return new NodeIdentifierWithPredicates(qname, predicateTemplate.instantiateWithValues(values));
128 private final Class<?> identifiable;
129 private final QName qname;
131 IdentifiableItemCodec(final ListSchemaNode schema, final Class<? extends Identifier<?>> keyClass,
132 final Class<?> identifiable) {
133 this.identifiable = requireNonNull(identifiable);
134 this.qname = schema.getQName();
137 static IdentifiableItemCodec of(final ListSchemaNode schema,
138 final Class<? extends Identifier<?>> keyClass, final Class<?> identifiable,
139 final Map<QName, ValueContext> keyValueContexts) {
140 switch (keyValueContexts.size()) {
142 throw new IllegalArgumentException("Key " + keyClass + " of " + identifiable + " has no components");
144 final Entry<QName, ValueContext> entry = keyValueContexts.entrySet().iterator().next();
145 return new SingleKey(schema, keyClass, identifiable, entry.getKey(), entry.getValue());
147 return new MultiKey(schema, keyClass, identifiable, keyValueContexts);
152 @SuppressWarnings({ "rawtypes", "unchecked", "checkstyle:illegalCatch" })
153 public final IdentifiableItem<?, ?> deserialize(final NodeIdentifierWithPredicates input) {
154 final Identifier<?> identifier;
156 identifier = deserializeIdentifier(input.getKeyValues());
157 } catch (Throwable e) {
158 Throwables.throwIfUnchecked(e);
159 throw new IllegalStateException("Failed to deserialize " + input, e);
162 return IdentifiableItem.of((Class) identifiable, (Identifier) identifier);
166 public final NodeIdentifierWithPredicates serialize(final IdentifiableItem<?, ?> input) {
167 return serializeIdentifier(qname, input.getKey());
170 @SuppressWarnings("checkstyle:illegalThrows")
171 abstract Identifier<?> deserializeIdentifier(Map<QName, Object> keyValues) throws Throwable;
173 abstract NodeIdentifierWithPredicates serializeIdentifier(QName qname, Identifier<?> key);
175 static MethodHandle getConstructor(final Class<? extends Identifier<?>> clazz) {
176 for (@SuppressWarnings("rawtypes") final Constructor constr : clazz.getConstructors()) {
177 final Class<?>[] parameters = constr.getParameterTypes();
178 if (!clazz.equals(parameters[0])) {
179 // It is not copy constructor;
181 return MethodHandles.publicLookup().unreflectConstructor(constr);
182 } catch (IllegalAccessException e) {
183 throw new IllegalStateException("Cannot access constructor " + constr + " in class " + clazz, e);
187 throw new IllegalArgumentException("Supplied class " + clazz + "does not have required constructor.");