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