15f914eb20defcf386c43b397d27d4f63f554f0c
[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 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
35 /**
36  * Codec support for extracting the {@link Identifiable#key()} method return from a MapEntryNode.
37  */
38 abstract class IdentifiableItemCodec
39         extends AbstractIllegalArgumentCodec<NodeIdentifierWithPredicates, IdentifiableItem<?, ?>> {
40     private static final class SingleKey extends IdentifiableItemCodec {
41         private static final MethodType CTOR_TYPE = MethodType.methodType(Identifier.class, Object.class);
42
43         private final ValueContext keyContext;
44         private final MethodHandle ctor;
45         private final QName keyName;
46
47         SingleKey(final ListSchemaNode schema, final Class<? extends Identifier<?>> keyClass,
48                 final Class<?> identifiable, final QName keyName, final ValueContext keyContext) {
49             super(schema, keyClass, identifiable);
50             this.keyContext = requireNonNull(keyContext);
51             this.keyName = requireNonNull(keyName);
52             ctor = getConstructor(keyClass).asType(CTOR_TYPE);
53         }
54
55         @Override
56         Identifier<?> deserializeIdentifierImpl(final NodeIdentifierWithPredicates nip) throws Throwable {
57             return (Identifier<?>) ctor.invokeExact(keyContext.deserialize(nip.getValue(keyName)));
58         }
59
60         @Override
61         NodeIdentifierWithPredicates serializeIdentifier(final QName qname, final Identifier<?> key) {
62             return NodeIdentifierWithPredicates.of(qname, keyName, keyContext.getAndSerialize(key));
63         }
64     }
65
66     private static final class MultiKey extends IdentifiableItemCodec {
67         private final ImmutableOffsetMapTemplate<QName> predicateTemplate;
68         private final ImmutableOffsetMap<QName, ValueContext> keyValueContexts;
69         private final ImmutableList<QName> keysInBindingOrder;
70         private final MethodHandle ctor;
71
72         MultiKey(final ListSchemaNode schema, final Class<? extends Identifier<?>> keyClass,
73                 final Class<?> identifiable, final Map<QName, ValueContext> keyValueContexts) {
74             super(schema, keyClass, identifiable);
75
76             final MethodHandle tmpCtor = getConstructor(keyClass);
77             final MethodHandle inv = MethodHandles.spreadInvoker(tmpCtor.type(), 0);
78             this.ctor = inv.asType(inv.type().changeReturnType(Identifier.class)).bindTo(tmpCtor);
79
80             /*
81              * We need to re-index to make sure we instantiate nodes in the order in which they are defined. We will
82              * also need to instantiate values in the same order.
83              */
84             final List<QName> keyDef = schema.getKeyDefinition();
85             predicateTemplate = ImmutableOffsetMapTemplate.ordered(keyDef);
86             this.keyValueContexts = predicateTemplate.instantiateTransformed(keyValueContexts, (key, value) -> value);
87
88             /*
89              * When instantiating binding objects we need to specify constructor arguments in alphabetic order. If the
90              * order matches definition order, we try to reuse the key definition.
91              *
92              * BUG-2755: remove this if order is made declaration-order-dependent
93              */
94             final List<QName> tmp = new ArrayList<>(keyDef);
95             // This is not terribly efficient but gets the job done
96             tmp.sort(Comparator.comparing(qname -> BindingMapping.getPropertyName(qname.getLocalName())));
97             this.keysInBindingOrder = ImmutableList.copyOf(tmp.equals(keyDef) ? keyDef : tmp);
98         }
99
100         @Override
101         Identifier<?> deserializeIdentifierImpl(final NodeIdentifierWithPredicates nip) throws Throwable {
102             final Object[] bindingValues = new Object[keysInBindingOrder.size()];
103             int offset = 0;
104             for (final QName key : keysInBindingOrder) {
105                 bindingValues[offset++] = keyValueContexts.get(key).deserialize(nip.getValue(key));
106             }
107
108             return (Identifier<?>) ctor.invokeExact(bindingValues);
109         }
110
111         @Override
112         NodeIdentifierWithPredicates serializeIdentifier(final QName qname, final Identifier<?> key) {
113             final Object[] values = new Object[keyValueContexts.size()];
114             int offset = 0;
115             for (final ValueContext valueCtx : keyValueContexts.values()) {
116                 values[offset++] = valueCtx.getAndSerialize(key);
117             }
118
119             return NodeIdentifierWithPredicates.of(qname, predicateTemplate.instantiateWithValues(values));
120         }
121     }
122
123     private final Class<?> identifiable;
124     private final QName qname;
125
126     IdentifiableItemCodec(final ListSchemaNode schema, final Class<? extends Identifier<?>> keyClass,
127             final Class<?> identifiable) {
128         this.identifiable = requireNonNull(identifiable);
129         this.qname = schema.getQName();
130     }
131
132     static IdentifiableItemCodec of(final ListSchemaNode schema,
133             final Class<? extends Identifier<?>> keyClass, final Class<?> identifiable,
134                     final Map<QName, ValueContext> keyValueContexts) {
135         switch (keyValueContexts.size()) {
136             case 0:
137                 throw new IllegalArgumentException("Key " + keyClass + " of " + identifiable + " has no components");
138             case 1:
139                 final Entry<QName, ValueContext> entry = keyValueContexts.entrySet().iterator().next();
140                 return new SingleKey(schema, keyClass, identifiable, entry.getKey(), entry.getValue());
141             default:
142                 return new MultiKey(schema, keyClass, identifiable, keyValueContexts);
143         }
144     }
145
146     @Override
147     @SuppressWarnings({ "rawtypes", "unchecked" })
148     protected final IdentifiableItem<?, ?> deserializeImpl(final NodeIdentifierWithPredicates input) {
149         final Identifier<?> identifier = deserializeIdentifier(input);
150         return IdentifiableItem.of((Class) identifiable, (Identifier) identifier);
151     }
152
153     @Override
154     protected final NodeIdentifierWithPredicates serializeImpl(final IdentifiableItem<?, ?> input) {
155         return serializeIdentifier(qname, input.getKey());
156     }
157
158     @SuppressWarnings("checkstyle:illegalCatch")
159     final @NonNull Identifier<?> deserializeIdentifier(final NodeIdentifierWithPredicates input) {
160         try {
161             return deserializeIdentifierImpl(input);
162         } catch (Throwable e) {
163             Throwables.throwIfUnchecked(e);
164             throw new IllegalStateException("Failed to deserialize " + input, e);
165         }
166     }
167
168     @SuppressWarnings("checkstyle:illegalThrows")
169     abstract @NonNull Identifier<?> deserializeIdentifierImpl(@NonNull NodeIdentifierWithPredicates nip)
170             throws Throwable;
171
172     abstract @NonNull NodeIdentifierWithPredicates serializeIdentifier(QName qname, Identifier<?> key);
173
174     static MethodHandle getConstructor(final Class<? extends Identifier<?>> clazz) {
175         for (@SuppressWarnings("rawtypes") final Constructor constr : clazz.getConstructors()) {
176             final Class<?>[] parameters = constr.getParameterTypes();
177             if (!clazz.equals(parameters[0])) {
178                 // It is not copy constructor...
179                 try {
180                     return MethodHandles.publicLookup().unreflectConstructor(constr);
181                 } catch (IllegalAccessException e) {
182                     throw new IllegalStateException("Cannot access constructor " + constr + " in class " + clazz, e);
183                 }
184             }
185         }
186         throw new IllegalArgumentException("Supplied class " + clazz + "does not have required constructor.");
187     }
188 }