BUG-1513: introduce ChoiceSchemaNode
[mdsal.git] / code-generator / binding-data-codec / src / main / java / org / opendaylight / yangtools / binding / data / codec / impl / SchemaRootCodecContext.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.Optional;
11 import com.google.common.base.Preconditions;
12 import com.google.common.cache.CacheBuilder;
13 import com.google.common.cache.CacheLoader;
14 import com.google.common.cache.LoadingCache;
15 import org.opendaylight.yangtools.util.ClassLoaderUtils;
16 import org.opendaylight.yangtools.yang.binding.BindingMapping;
17 import org.opendaylight.yangtools.yang.binding.ChildOf;
18 import org.opendaylight.yangtools.yang.binding.DataContainer;
19 import org.opendaylight.yangtools.yang.binding.DataRoot;
20 import org.opendaylight.yangtools.yang.binding.Notification;
21 import org.opendaylight.yangtools.yang.binding.util.BindingReflections;
22 import org.opendaylight.yangtools.yang.common.QName;
23 import org.opendaylight.yangtools.yang.common.QNameModule;
24 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
25 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
26 import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
27 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
28 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
29 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
30 import org.opendaylight.yangtools.yang.model.api.NotificationDefinition;
31 import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
32 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
33 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
34 import org.opendaylight.yangtools.yang.model.util.SchemaContextUtil;
35 import org.opendaylight.yangtools.yang.model.util.SchemaNodeUtils;
36
37 final class SchemaRootCodecContext extends DataContainerCodecContext<SchemaContext> {
38
39     private final LoadingCache<Class<?>, DataContainerCodecContext<?>> childrenByClass = CacheBuilder.newBuilder()
40             .build(new CacheLoader<Class<?>, DataContainerCodecContext<?>>() {
41                 @Override
42                 public DataContainerCodecContext<?> load(final Class<?> key) {
43                     return createDataTreeChildContext(key);
44                 }
45
46             });
47
48     private final LoadingCache<Class<?>, ContainerNodeCodecContext> rpcDataByClass = CacheBuilder.newBuilder().build(
49             new CacheLoader<Class<?>, ContainerNodeCodecContext>() {
50                 @Override
51                 public ContainerNodeCodecContext load(final Class<?> key) {
52                     return createRpcDataContext(key);
53                 }
54             });
55
56     private final LoadingCache<Class<?>, NotificationCodecContext> notificationsByClass = CacheBuilder.newBuilder()
57             .build(new CacheLoader<Class<?>, NotificationCodecContext>() {
58                 @Override
59                 public NotificationCodecContext load(final Class<?> key) {
60                     return createNotificationDataContext(key);
61                 }
62             });
63
64     private final LoadingCache<QName, DataContainerCodecContext<?>> childrenByQName = CacheBuilder.newBuilder().build(
65             new CacheLoader<QName, DataContainerCodecContext<?>>() {
66                 @Override
67                 public DataContainerCodecContext<?> load(final QName qname) {
68                     final DataSchemaNode childSchema = schema().getDataChildByName(qname);
69                     Preconditions.checkArgument(childSchema != null, "Argument %s is not valid child of %s", qname,
70                             schema());
71
72                     if (childSchema instanceof DataNodeContainer || childSchema instanceof ChoiceSchemaNode) {
73                         final Class<?> childCls = factory().getRuntimeContext().getClassForSchema(childSchema);
74                         return getStreamChild(childCls);
75                     } else {
76                         throw new UnsupportedOperationException("Unsupported child type " + childSchema.getClass());
77                     }
78                 }
79             });
80
81     private final LoadingCache<SchemaPath, ContainerNodeCodecContext> rpcDataByPath = CacheBuilder.newBuilder().build(
82             new CacheLoader<SchemaPath, ContainerNodeCodecContext>() {
83
84                 @SuppressWarnings({ "rawtypes", "unchecked" })
85                 @Override
86                 public ContainerNodeCodecContext load(final SchemaPath key) {
87                     final ContainerSchemaNode schema = SchemaContextUtil.getRpcDataSchema(schema(), key);
88                     final Class cls = factory().getRuntimeContext().getClassForSchema(schema);
89                     return getRpc(cls);
90                 }
91             });
92
93     private final LoadingCache<SchemaPath, NotificationCodecContext> notificationsByPath = CacheBuilder.newBuilder()
94             .build(new CacheLoader<SchemaPath, NotificationCodecContext>() {
95
96                 @SuppressWarnings({ "rawtypes", "unchecked" })
97                 @Override
98                 public NotificationCodecContext load(final SchemaPath key) throws Exception {
99                     final NotificationDefinition schema = SchemaContextUtil.getNotificationSchema(schema(), key);
100                     final Class clz = factory().getRuntimeContext().getClassForSchema(schema);
101                     return getNotification(clz);
102                 }
103             });
104
105     private SchemaRootCodecContext(final DataContainerCodecPrototype<SchemaContext> dataPrototype) {
106         super(dataPrototype);
107     }
108
109     /**
110      * Creates RootNode from supplied CodecContextFactory.
111      *
112      * @param factory
113      *            CodecContextFactory
114      * @return
115      */
116     static SchemaRootCodecContext create(final CodecContextFactory factory) {
117         final DataContainerCodecPrototype<SchemaContext> prototype = DataContainerCodecPrototype.rootPrototype(factory);
118         return new SchemaRootCodecContext(prototype);
119     }
120
121     @Override
122     protected DataContainerCodecContext<?> getStreamChild(final Class<?> childClass) {
123         return childrenByClass.getUnchecked(childClass);
124     }
125
126     @Override
127     protected Optional<DataContainerCodecContext<?>> getPossibleStreamChild(final Class<?> childClass) {
128         throw new UnsupportedOperationException("Not supported");
129     }
130
131     @Override
132     protected NodeCodecContext getYangIdentifierChild(final PathArgument arg) {
133         return childrenByQName.getUnchecked(arg.getNodeType());
134     }
135
136     @Override
137     protected Object dataFromNormalizedNode(final NormalizedNode<?, ?> normalizedNode) {
138         throw new UnsupportedOperationException("Could not create Binding data representation for root");
139     }
140
141     ContainerNodeCodecContext getRpc(final Class<? extends DataContainer> rpcInputOrOutput) {
142         return rpcDataByClass.getUnchecked(rpcInputOrOutput);
143     }
144
145     NotificationCodecContext getNotification(final Class<? extends Notification> notification) {
146         return notificationsByClass.getUnchecked(notification);
147     }
148
149     NotificationCodecContext getNotification(final SchemaPath notification) {
150         return notificationsByPath.getUnchecked(notification);
151     }
152
153     ContainerNodeCodecContext getRpc(final SchemaPath notification) {
154         return rpcDataByPath.getUnchecked(notification);
155     }
156
157     private DataContainerCodecContext<?> createDataTreeChildContext(final Class<?> key) {
158         final Class<Object> parent = ClassLoaderUtils.findFirstGenericArgument(key, ChildOf.class);
159         Preconditions.checkArgument(DataRoot.class.isAssignableFrom(parent));
160         final QName qname = BindingReflections.findQName(key);
161         final DataSchemaNode childSchema = schema().getDataChildByName(qname);
162         return DataContainerCodecPrototype.from(key, childSchema, factory()).get();
163     }
164
165     private ContainerNodeCodecContext createRpcDataContext(final Class<?> key) {
166         Preconditions.checkArgument(DataContainer.class.isAssignableFrom(key));
167         final QName qname = BindingReflections.findQName(key);
168         final QNameModule module = qname.getModule();
169         RpcDefinition rpc = null;
170         for (final RpcDefinition potential : schema().getOperations()) {
171             final QName potentialQName = potential.getQName();
172             /*
173              *
174              * Check if rpc and class represents data from same module and then
175              * checks if rpc local name produces same class name as class name
176              * appended with Input/Output based on QName associated with bidning
177              * class.
178              *
179              * FIXME: Rework this to have more precise logic regarding Binding
180              * Specification.
181              */
182             if (module.equals(potentialQName.getModule())
183                     && key.getSimpleName().equals(
184                             BindingMapping.getClassName(potentialQName) + BindingMapping.getClassName(qname))) {
185                 rpc = potential;
186                 break;
187             }
188         }
189         Preconditions.checkArgument(rpc != null, "Supplied class %s is not valid RPC class.", key);
190         final ContainerSchemaNode schema = SchemaNodeUtils.getRpcDataSchema(rpc, qname);
191         Preconditions.checkArgument(schema != null, "Schema for %s does not define input / output.", rpc.getQName());
192         return (ContainerNodeCodecContext) DataContainerCodecPrototype.from(key, schema, factory()).get();
193     }
194
195     private NotificationCodecContext createNotificationDataContext(final Class<?> notificationType) {
196         Preconditions.checkArgument(Notification.class.isAssignableFrom(notificationType));
197         Preconditions.checkArgument(notificationType.isInterface(), "Supplied class must be interface.");
198         final QName qname = BindingReflections.findQName(notificationType);
199         /**
200          *  FIXME: After Lithium cleanup of yang-model-api, use direct call on schema context
201          *  to retrieve notification via index.
202          */
203         final NotificationDefinition schema = SchemaContextUtil.getNotificationSchema(schema(),
204                 SchemaPath.create(true, qname));
205         Preconditions.checkArgument(schema != null, "Supplied %s is not valid notification", notificationType);
206
207         return new NotificationCodecContext(notificationType, schema, factory());
208     }
209
210 }