Bug 1439, Bug 1443 Binding Codec NormalizedNodeWriter support
[mdsal.git] / code-generator / binding-data-codec / src / main / java / org / opendaylight / yangtools / binding / data / codec / impl / BindingCodecContext.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.yangtools.binding.data.codec.impl;
9
10 import com.google.common.base.Preconditions;
11 import com.google.common.collect.ImmutableMap;
12
13 import java.lang.reflect.Constructor;
14 import java.lang.reflect.InvocationTargetException;
15 import java.lang.reflect.Method;
16 import java.util.AbstractMap.SimpleEntry;
17 import java.util.ArrayList;
18 import java.util.HashMap;
19 import java.util.LinkedList;
20 import java.util.List;
21 import java.util.Map;
22 import java.util.Map.Entry;
23
24 import org.opendaylight.yangtools.binding.data.codec.impl.NodeCodecContext.CodecContextFactory;
25 import org.opendaylight.yangtools.concepts.Codec;
26 import org.opendaylight.yangtools.concepts.Immutable;
27 import org.opendaylight.yangtools.sal.binding.generator.util.BindingRuntimeContext;
28 import org.opendaylight.yangtools.util.ClassLoaderUtils;
29 import org.opendaylight.yangtools.yang.binding.BaseIdentity;
30 import org.opendaylight.yangtools.yang.binding.BindingMapping;
31 import org.opendaylight.yangtools.yang.binding.BindingStreamEventWriter;
32 import org.opendaylight.yangtools.yang.binding.Identifiable;
33 import org.opendaylight.yangtools.yang.binding.Identifier;
34 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
35 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier.IdentifiableItem;
36 import org.opendaylight.yangtools.yang.binding.util.BindingReflections;
37 import org.opendaylight.yangtools.yang.common.QName;
38 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
39 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
40 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
41 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
42 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
43 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
44 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
45 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
46 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
47 import org.opendaylight.yangtools.yang.model.api.type.BooleanTypeDefinition;
48 import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition;
49 import org.opendaylight.yangtools.yang.model.api.type.InstanceIdentifierTypeDefinition;
50 import org.opendaylight.yangtools.yang.model.api.type.UnionTypeDefinition;
51
52 class BindingCodecContext implements CodecContextFactory, Immutable {
53
54     private static final String GETTER_PREFIX = "get";
55     private final SchemaRootCodecContext root;
56     private final BindingRuntimeContext context;
57     private final Codec<YangInstanceIdentifier,InstanceIdentifier<?>> instanceIdentifierCodec;
58     private final Codec<QName,Class<?>> identityCodec;
59
60     public BindingCodecContext(final BindingRuntimeContext context) {
61         this.context =  Preconditions.checkNotNull(context, "Bidning Runtime Context is required.");
62         this.root = SchemaRootCodecContext.create(this);
63         this.instanceIdentifierCodec = new InstanceIdentifierCodec();
64         this.identityCodec = new IdentityCodec();
65     }
66
67     @Override
68     public BindingRuntimeContext getRuntimeContext() {
69         return context;
70     }
71
72     public Codec<YangInstanceIdentifier,InstanceIdentifier<?>> getInstanceIdentifierCodec() {
73         return instanceIdentifierCodec;
74     }
75
76     public Codec<QName, Class<?>> getIdentityCodec() {
77         return identityCodec;
78     }
79
80     public Entry<YangInstanceIdentifier, BindingStreamEventWriter> newWriter(final InstanceIdentifier<?> path,
81             final NormalizedNodeStreamWriter domWriter) {
82         LinkedList<YangInstanceIdentifier.PathArgument> yangArgs = new LinkedList<>();
83         DataContainerCodecContext<?> codecContext = getCodecContextNode(path, yangArgs);
84         BindingStreamEventWriter writer = new BindingToNormalizedStreamWriter(codecContext, domWriter);
85         return new SimpleEntry<>(YangInstanceIdentifier.create(yangArgs), writer);
86     }
87
88     public BindingStreamEventWriter newWriterWithoutIdentifier(final InstanceIdentifier<?> path,
89             final NormalizedNodeStreamWriter domWriter) {
90         return new BindingToNormalizedStreamWriter(getCodecContextNode(path, null), domWriter);
91     }
92
93     public DataContainerCodecContext<?> getCodecContextNode(final InstanceIdentifier<?> binding,
94             final List<YangInstanceIdentifier.PathArgument> builder) {
95         DataContainerCodecContext<?> currentNode = root;
96         for (InstanceIdentifier.PathArgument bindingArg : binding.getPathArguments()) {
97             currentNode = currentNode.getIdentifierChild(bindingArg, builder);
98         }
99         return currentNode;
100     }
101
102     public NodeCodecContext getCodecContextNode(final YangInstanceIdentifier dom,
103             final List<InstanceIdentifier.PathArgument> builder) {
104         NodeCodecContext currentNode = root;
105         ListNodeCodecContext currentList = null;
106         for (YangInstanceIdentifier.PathArgument domArg : dom.getPathArguments()) {
107             Preconditions.checkArgument(currentNode instanceof DataContainerCodecContext<?>);
108             DataContainerCodecContext<?> previous = (DataContainerCodecContext<?>) currentNode;
109             NodeCodecContext nextNode = previous.getYangIdentifierChild(domArg);
110             /*
111              * List representation in YANG Instance Identifier consists of two
112              * arguments: first is list as a whole, second is list as an item so
113              * if it is /list it means list as whole, if it is /list/list - it
114              * is wildcarded and if it is /list/list[key] it is concrete item,
115              * all this variations are expressed in Binding Aware Instance
116              * Identifier as Item or IdentifiableItem
117              */
118             if (currentList != null) {
119
120                 if (currentList == nextNode) {
121
122                     // We entered list, so now we have all information to emit
123                     // list
124                     // path using second list argument.
125                     builder.add(currentList.getBindingPathArgument(domArg));
126                     currentList = null;
127                     currentNode = nextNode;
128                 } else {
129                     throw new IllegalArgumentException(
130                             "List should be referenced two times in YANG Instance Identifier");
131                 }
132             } else if (nextNode instanceof ListNodeCodecContext) {
133                 // We enter list, we do not update current Node yet,
134                 // since we need to verify
135                 currentList = (ListNodeCodecContext) nextNode;
136             } else if (nextNode instanceof ChoiceNodeCodecContext) {
137                 // We do not add path argument for choice, since
138                 // it is not supported by binding instance identifier.
139                 currentNode = nextNode;
140             }else if (nextNode instanceof DataContainerCodecContext<?>) {
141                 builder.add(((DataContainerCodecContext<?>) nextNode).getBindingPathArgument(domArg));
142                 currentNode = nextNode;
143             } else if (nextNode instanceof LeafNodeCodecContext) {
144                 Preconditions.checkArgument(builder == null,"Instance Identifier for leaf is not representable.");
145             }
146         }
147         // Algorithm ended in list as whole representation
148         // we sill need to emit identifier for list
149         if (currentList != null) {
150             builder.add(currentList.getBindingPathArgument(null));
151             return currentList;
152         }
153         return currentNode;
154     }
155
156     @Override
157     public ImmutableMap<String, LeafNodeCodecContext> getLeafNodes(final Class<?> parentClass, final DataNodeContainer childSchema) {
158         HashMap<String, DataSchemaNode> getterToLeafSchema = new HashMap<>();
159         for (DataSchemaNode leaf : childSchema.getChildNodes()) {
160             final TypeDefinition<?> typeDef;
161             if (leaf instanceof LeafSchemaNode) {
162                 typeDef = ((LeafSchemaNode) leaf).getType();
163             } else if (leaf instanceof LeafListSchemaNode) {
164                 typeDef = ((LeafListSchemaNode) leaf).getType();
165             } else {
166                 continue;
167             }
168
169             String getterName =  getGetterName(leaf.getQName(),typeDef);
170             getterToLeafSchema.put(getterName, leaf);
171         }
172         return getLeafNodesUsingReflection(parentClass, getterToLeafSchema);
173     }
174
175     private String getGetterName(final QName qName, TypeDefinition<?> typeDef) {
176         String suffix = BindingMapping.getClassName(qName);
177
178         while(typeDef.getBaseType() != null) {
179             typeDef = typeDef.getBaseType();
180         }
181         if(typeDef instanceof BooleanTypeDefinition) {
182             return "is" + suffix;
183         }
184         return GETTER_PREFIX + suffix;
185     }
186
187     private ImmutableMap<String, LeafNodeCodecContext> getLeafNodesUsingReflection(final Class<?> parentClass,
188             final Map<String, DataSchemaNode> getterToLeafSchema) {
189         Map<String, LeafNodeCodecContext> leaves = new HashMap<>();
190         for (Method method : parentClass.getMethods()) {
191             if (method.getParameterTypes().length == 0) {
192                 DataSchemaNode schema = getterToLeafSchema.get(method.getName());
193                 final LeafNodeCodecContext leafNode;
194                 if (schema instanceof LeafSchemaNode) {
195                     leafNode = leafNodeFrom(method.getReturnType(), schema);
196
197                 } else {
198                     // FIXME: extract inner list value
199                     leafNode = null;
200                 }
201                 if (leafNode != null) {
202                     leaves.put(schema.getQName().getLocalName(), leafNode);
203                 }
204             }
205         }
206         return ImmutableMap.copyOf(leaves);
207     }
208
209
210     private LeafNodeCodecContext leafNodeFrom(final Class<?> returnType, final DataSchemaNode schema) {
211         return new LeafNodeCodecContext(schema, getCodec(returnType,schema));
212     }
213
214     private Codec<Object, Object> getCodec(final Class<?> returnType, final DataSchemaNode schema) {
215         if(Class.class.equals(returnType)) {
216             @SuppressWarnings({ "unchecked", "rawtypes" })
217             final Codec<Object,Object>casted = (Codec) identityCodec;
218             return casted;
219         } else if(InstanceIdentifier.class.equals(returnType)) {
220             @SuppressWarnings({ "unchecked", "rawtypes" })
221             final Codec<Object,Object>casted = (Codec) instanceIdentifierCodec;
222             return casted;
223         } else if(BindingReflections.isBindingClass(returnType)) {
224             final TypeDefinition<?> instantiatedType;
225             if(schema instanceof LeafSchemaNode) {
226                 instantiatedType = ((LeafSchemaNode) schema).getType();
227             } else if(schema instanceof LeafListSchemaNode) {
228                 instantiatedType = ((LeafListSchemaNode) schema).getType();
229             } else {
230                 instantiatedType = null;
231             }
232             if(instantiatedType != null) {
233                 return getCodec(returnType,instantiatedType);
234             }
235         }
236         return ValueTypeCodec.NOOP_CODEC;
237     }
238
239     private Codec<Object, Object> getCodec(final Class<?> returnType, final TypeDefinition<?> instantiatedType) {
240         @SuppressWarnings("rawtypes")
241         TypeDefinition rootType = instantiatedType;
242         while(rootType.getBaseType() != null) {
243             rootType = rootType.getBaseType();
244         }
245          if (rootType instanceof IdentityrefTypeDefinition) {
246             return ValueTypeCodec.encapsulatedValueCodecFor(returnType,identityCodec);
247         } else if (rootType instanceof InstanceIdentifierTypeDefinition) {
248             return ValueTypeCodec.encapsulatedValueCodecFor(returnType,instanceIdentifierCodec);
249         } else if(rootType instanceof UnionTypeDefinition) {
250             // FIXME: Return union codec
251             return ValueTypeCodec.NOOP_CODEC;
252         }
253         return ValueTypeCodec.getCodecFor(returnType, instantiatedType);
254     }
255
256     private class InstanceIdentifierCodec implements Codec<YangInstanceIdentifier,InstanceIdentifier<?>> {
257
258         @Override
259         public YangInstanceIdentifier serialize(final InstanceIdentifier<?> input) {
260             List<YangInstanceIdentifier.PathArgument> domArgs = new LinkedList<>();
261             getCodecContextNode(input, domArgs);
262             return YangInstanceIdentifier.create(domArgs);
263         }
264
265         @Override
266         public InstanceIdentifier<?> deserialize(final YangInstanceIdentifier input) {
267             List<InstanceIdentifier.PathArgument> builder = new LinkedList<>();
268             getCodecContextNode(input, builder);
269             return InstanceIdentifier.create(builder);
270         }
271     }
272
273     private class IdentityCodec implements Codec<QName,Class<?>> {
274
275
276         @Override
277         public Class<?> deserialize(final QName input) {
278             Preconditions.checkArgument(input != null, "Input must not be null.");
279             return context.getIdentityClass(input);
280         }
281
282         @Override
283         public QName serialize(final Class<?> input) {
284             Preconditions.checkArgument(BaseIdentity.class.isAssignableFrom(input));
285             return BindingReflections.findQName(input);
286         }
287     }
288
289     private static class ValueContext {
290
291         Method getter;
292         Codec<Object,Object> codec;
293
294         public ValueContext(final Class<?> identifier, final LeafNodeCodecContext leaf) {
295             final String getterName = GETTER_PREFIX + BindingMapping.getClassName(leaf.getDomPathArgument().getNodeType());
296             try {
297                 getter =identifier.getMethod(getterName);
298             } catch (NoSuchMethodException | SecurityException e) {
299                 throw new IllegalStateException(e);
300             }
301             codec = leaf.getValueCodec();
302         }
303
304         public Object getAndSerialize(final Object obj) {
305             try {
306                 Object value = getter.invoke(obj);
307                 return codec.serialize(value);
308             } catch (IllegalAccessException | InvocationTargetException e) {
309                 throw new IllegalArgumentException(e);
310             }
311         }
312
313         public Object deserialize(final Object obj) {
314             return codec.deserialize(obj);
315         }
316
317     }
318
319     private class IdentifiableItemCodec implements Codec<NodeIdentifierWithPredicates, IdentifiableItem<?, ?>> {
320
321         private final Class<? extends Identifier<?>> keyClass;
322         private final ImmutableMap<QName, ValueContext> keyValueContexts;
323         private final QName name;
324         private final Constructor<? extends Identifier<?>> constructor;
325         private final Class<?> identifiable;
326
327         public IdentifiableItemCodec(final QName name,final Class<? extends Identifier<?>> keyClass,final Class<?> identifiable,final Map<QName, ValueContext> keyValueContexts) {
328             this.name = name;
329             this.identifiable = identifiable;
330             this.keyClass = keyClass;
331             this.keyValueContexts = ImmutableMap.copyOf(keyValueContexts);
332             this.constructor = getConstructor(keyClass);
333         }
334
335         @Override
336         public IdentifiableItem<?,?> deserialize(final NodeIdentifierWithPredicates input) {
337             ArrayList<Object> bindingValues = new ArrayList<>();
338             for(Entry<QName, Object> yangEntry : input.getKeyValues().entrySet()) {
339                 QName yangName = yangEntry.getKey();
340                 Object yangValue = yangEntry.getValue();
341                 bindingValues.add(keyValueContexts.get(yangName).deserialize(yangValue));
342             }
343             try {
344                 Identifier<?> identifier = constructor.newInstance(bindingValues.toArray());
345                 return new IdentifiableItem(identifiable, identifier);
346             } catch (InstantiationException | IllegalAccessException
347                     | InvocationTargetException e) {
348                 throw new IllegalStateException(e);
349             }
350         }
351
352         @Override
353         public NodeIdentifierWithPredicates serialize(final IdentifiableItem<?, ?> input) {
354             Object value = input.getKey();
355
356             Map<QName,Object> values = new HashMap<>();
357             for(Entry<QName, ValueContext> valueCtx : keyValueContexts.entrySet()) {
358                 values.put(valueCtx.getKey(), valueCtx.getValue().getAndSerialize(value));
359             }
360             return new NodeIdentifierWithPredicates(name, values);
361         }
362
363     }
364
365     private static Constructor<? extends Identifier<?>> getConstructor(final Class<? extends Identifier<?>> clazz) {
366         for(Constructor constr : clazz.getConstructors()) {
367             Class<?>[] parameters = constr.getParameterTypes();
368             if (!clazz.equals(parameters[0])) {
369                 // It is not copy constructor;
370                 return constr;
371             }
372         }
373         throw new IllegalArgumentException("Supplied class " + clazz +"does not have required constructor.");
374     }
375
376
377     @Override
378     public Codec<NodeIdentifierWithPredicates, IdentifiableItem<?, ?>> getPathArgumentCodec(final Class<?> listClz,
379             final ListSchemaNode schema) {
380         Class<? extends Identifier<?>> identifier =ClassLoaderUtils.findFirstGenericArgument(listClz, Identifiable.class);
381         Map<QName, ValueContext> valueCtx = new HashMap<>();
382         for(LeafNodeCodecContext leaf : getLeafNodes(identifier, schema).values()) {
383             QName name = leaf.getDomPathArgument().getNodeType();
384             valueCtx.put(name, new ValueContext(identifier,leaf));
385         }
386         return new IdentifiableItemCodec(schema.getQName(), identifier, listClz, valueCtx);
387     }
388
389 }