Migrate getDataChildByName() users
[mdsal.git] / binding / mdsal-binding-dom-codec / src / main / java / org / opendaylight / mdsal / binding / dom / codec / impl / DataObjectCodecContext.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 com.google.common.base.Preconditions.checkArgument;
11 import static com.google.common.base.Verify.verify;
12
13 import com.google.common.annotations.Beta;
14 import com.google.common.base.Throwables;
15 import com.google.common.collect.ImmutableMap;
16 import com.google.common.collect.ImmutableMap.Builder;
17 import java.lang.invoke.MethodHandle;
18 import java.lang.invoke.MethodHandles;
19 import java.lang.invoke.MethodType;
20 import java.lang.invoke.VarHandle;
21 import java.lang.reflect.Method;
22 import java.util.HashMap;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.Map.Entry;
26 import java.util.Optional;
27 import org.eclipse.jdt.annotation.NonNull;
28 import org.eclipse.jdt.annotation.Nullable;
29 import org.opendaylight.mdsal.binding.dom.codec.api.IncorrectNestingException;
30 import org.opendaylight.mdsal.binding.model.api.DefaultType;
31 import org.opendaylight.mdsal.binding.model.api.Type;
32 import org.opendaylight.mdsal.binding.runtime.api.BindingRuntimeContext;
33 import org.opendaylight.mdsal.binding.spec.reflect.BindingReflections;
34 import org.opendaylight.yangtools.yang.binding.Augmentable;
35 import org.opendaylight.yangtools.yang.binding.Augmentation;
36 import org.opendaylight.yangtools.yang.binding.DataObject;
37 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
38 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier.Item;
39 import org.opendaylight.yangtools.yang.binding.OpaqueObject;
40 import org.opendaylight.yangtools.yang.common.QName;
41 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
42 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
43 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
44 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
45 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
46 import org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode;
47 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
48 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer;
49 import org.opendaylight.yangtools.yang.model.api.AugmentationSchemaNode;
50 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
51 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
52 import org.opendaylight.yangtools.yang.model.api.DocumentedNode.WithStatus;
53 import org.opendaylight.yangtools.yang.model.util.SchemaNodeUtils;
54 import org.slf4j.Logger;
55 import org.slf4j.LoggerFactory;
56
57 /**
58  * This class is an implementation detail. It is public only due to technical reasons and may change at any time.
59  */
60 @Beta
61 public abstract class DataObjectCodecContext<D extends DataObject, T extends DataNodeContainer & WithStatus>
62         extends DataContainerCodecContext<D, T> {
63     private static final Logger LOG = LoggerFactory.getLogger(DataObjectCodecContext.class);
64     private static final MethodType CONSTRUCTOR_TYPE = MethodType.methodType(void.class,
65         DataObjectCodecContext.class, NormalizedNodeContainer.class);
66     private static final MethodType DATAOBJECT_TYPE = MethodType.methodType(DataObject.class,
67         DataObjectCodecContext.class, NormalizedNodeContainer.class);
68     private static final VarHandle MISMATCHED_AUGMENTED;
69
70     static {
71         try {
72             MISMATCHED_AUGMENTED = MethodHandles.lookup().findVarHandle(DataObjectCodecContext.class,
73                 "mismatchedAugmented", ImmutableMap.class);
74         } catch (NoSuchFieldException | IllegalAccessException e) {
75             throw new ExceptionInInitializerError(e);
76         }
77     }
78
79     private final ImmutableMap<String, ValueNodeCodecContext> leafChild;
80     private final ImmutableMap<YangInstanceIdentifier.PathArgument, NodeContextSupplier> byYang;
81     private final ImmutableMap<Class<?>, DataContainerCodecPrototype<?>> byStreamClass;
82     private final ImmutableMap<Class<?>, DataContainerCodecPrototype<?>> byBindingArgClass;
83     private final ImmutableMap<YangInstanceIdentifier.PathArgument, DataContainerCodecPrototype<?>> augmentationByYang;
84     private final ImmutableMap<Class<?>, DataContainerCodecPrototype<?>> augmentationByStream;
85     private final @NonNull Class<? extends CodecDataObject<?>> generatedClass;
86     private final MethodHandle proxyConstructor;
87
88     // Note this the content of this field depends only of invariants expressed as this class's fields or
89     // BindingRuntimeContext. It is only accessed via MISMATCHED_AUGMENTED above.
90     @SuppressWarnings("unused")
91     private volatile ImmutableMap<Class<?>, DataContainerCodecPrototype<?>> mismatchedAugmented = ImmutableMap.of();
92
93     DataObjectCodecContext(final DataContainerCodecPrototype<T> prototype) {
94         this(prototype, null);
95     }
96
97     DataObjectCodecContext(final DataContainerCodecPrototype<T> prototype, final Method keyMethod) {
98         super(prototype);
99
100         final Class<D> bindingClass = getBindingClass();
101
102         final ImmutableMap<Method, ValueNodeCodecContext> tmpLeaves = factory().getLeafNodes(bindingClass, getSchema());
103         final Map<Class<?>, Method> clsToMethod = BindingReflections.getChildrenClassToMethod(bindingClass);
104
105         final Map<YangInstanceIdentifier.PathArgument, NodeContextSupplier> byYangBuilder = new HashMap<>();
106         final Map<Class<?>, DataContainerCodecPrototype<?>> byStreamClassBuilder = new HashMap<>();
107         final Map<Class<?>, DataContainerCodecPrototype<?>> byBindingArgClassBuilder = new HashMap<>();
108
109         // Adds leaves to mapping
110         final Builder<String, ValueNodeCodecContext> leafChildBuilder =
111                 ImmutableMap.builderWithExpectedSize(tmpLeaves.size());
112         for (final Entry<Method, ValueNodeCodecContext> entry : tmpLeaves.entrySet()) {
113             final ValueNodeCodecContext leaf = entry.getValue();
114             leafChildBuilder.put(leaf.getSchema().getQName().getLocalName(), leaf);
115             byYangBuilder.put(leaf.getDomPathArgument(), leaf);
116         }
117         this.leafChild = leafChildBuilder.build();
118
119         final Map<Method, Class<?>> tmpDataObjects = new HashMap<>();
120         for (final Entry<Class<?>, Method> childDataObj : clsToMethod.entrySet()) {
121             final Method method = childDataObj.getValue();
122             verify(!method.isDefault(), "Unexpected default method %s in %s", method, bindingClass);
123
124             final Class<?> retClass = childDataObj.getKey();
125             if (OpaqueObject.class.isAssignableFrom(retClass)) {
126                 // Filter OpaqueObjects, they are not containers
127                 continue;
128             }
129
130             final DataContainerCodecPrototype<?> childProto = loadChildPrototype(retClass);
131             tmpDataObjects.put(method, childProto.getBindingClass());
132             byStreamClassBuilder.put(childProto.getBindingClass(), childProto);
133             byYangBuilder.put(childProto.getYangArg(), childProto);
134             if (childProto.isChoice()) {
135                 final ChoiceNodeCodecContext<?> choice = (ChoiceNodeCodecContext<?>) childProto.get();
136                 for (final Class<?> cazeChild : choice.getCaseChildrenClasses()) {
137                     byBindingArgClassBuilder.put(cazeChild, childProto);
138                 }
139             }
140         }
141
142         this.byYang = ImmutableMap.copyOf(byYangBuilder);
143         this.byStreamClass = ImmutableMap.copyOf(byStreamClassBuilder);
144
145         // Slight footprint optimization: we do not want to copy byStreamClass, as that would force its entrySet view
146         // to be instantiated. Furthermore the two maps can easily end up being equal -- hence we can reuse
147         // byStreamClass for the purposes of both.
148         byBindingArgClassBuilder.putAll(byStreamClassBuilder);
149         this.byBindingArgClass = byStreamClassBuilder.equals(byBindingArgClassBuilder) ? this.byStreamClass
150                 : ImmutableMap.copyOf(byBindingArgClassBuilder);
151
152         final ImmutableMap<AugmentationIdentifier, Type> possibleAugmentations;
153         if (Augmentable.class.isAssignableFrom(bindingClass)) {
154             possibleAugmentations = factory().getRuntimeContext().getAvailableAugmentationTypes(getSchema());
155             generatedClass = CodecDataObjectGenerator.generateAugmentable(prototype.getFactory().getLoader(),
156                 bindingClass, tmpLeaves, tmpDataObjects, keyMethod);
157         } else {
158             possibleAugmentations = ImmutableMap.of();
159             generatedClass = CodecDataObjectGenerator.generate(prototype.getFactory().getLoader(), bindingClass,
160                 tmpLeaves, tmpDataObjects, keyMethod);
161         }
162
163         // Iterate over all possible augmentations, indexing them as needed
164         final Map<PathArgument, DataContainerCodecPrototype<?>> augByYang = new HashMap<>();
165         final Map<Class<?>, DataContainerCodecPrototype<?>> augByStream = new HashMap<>();
166         for (final Type augment : possibleAugmentations.values()) {
167             final DataContainerCodecPrototype<?> augProto = getAugmentationPrototype(augment);
168             final PathArgument augYangArg = augProto.getYangArg();
169             if (augByYang.putIfAbsent(augYangArg, augProto) == null) {
170                 LOG.trace("Discovered new YANG mapping {} -> {} in {}", augYangArg, augProto, this);
171             }
172             final Class<?> augBindingClass = augProto.getBindingClass();
173             if (augByStream.putIfAbsent(augBindingClass, augProto) == null) {
174                 LOG.trace("Discovered new class mapping {} -> {} in {}", augBindingClass, augProto, this);
175             }
176         }
177         augmentationByYang = ImmutableMap.copyOf(augByYang);
178         augmentationByStream = ImmutableMap.copyOf(augByStream);
179
180         final MethodHandle ctor;
181         try {
182             ctor = MethodHandles.publicLookup().findConstructor(generatedClass, CONSTRUCTOR_TYPE);
183         } catch (NoSuchMethodException | IllegalAccessException e) {
184             throw new LinkageError("Failed to find contructor for class " + generatedClass, e);
185         }
186
187         proxyConstructor = ctor.asType(DATAOBJECT_TYPE);
188     }
189
190     @SuppressWarnings("unchecked")
191     @Override
192     public <C extends DataObject> DataContainerCodecContext<C, ?> streamChild(final Class<C> childClass) {
193         final DataContainerCodecPrototype<?> childProto = streamChildPrototype(childClass);
194         return (DataContainerCodecContext<C, ?>) childNonNull(childProto, childClass, " Child %s is not valid child.",
195                 childClass).get();
196     }
197
198     private DataContainerCodecPrototype<?> streamChildPrototype(final Class<?> childClass) {
199         final DataContainerCodecPrototype<?> childProto = byStreamClass.get(childClass);
200         if (childProto != null) {
201             return childProto;
202         }
203         if (Augmentation.class.isAssignableFrom(childClass)) {
204             return augmentationByClass(childClass);
205         }
206         return null;
207     }
208
209     @SuppressWarnings("unchecked")
210     @Override
211     public <C extends DataObject> Optional<DataContainerCodecContext<C, ?>> possibleStreamChild(
212             final Class<C> childClass) {
213         final DataContainerCodecPrototype<?> childProto = streamChildPrototype(childClass);
214         if (childProto != null) {
215             return Optional.of((DataContainerCodecContext<C, ?>) childProto.get());
216         }
217         return Optional.empty();
218     }
219
220     @Override
221     public DataContainerCodecContext<?,?> bindingPathArgumentChild(final InstanceIdentifier.PathArgument arg,
222             final List<YangInstanceIdentifier.PathArgument> builder) {
223
224         final Class<? extends DataObject> argType = arg.getType();
225         DataContainerCodecPrototype<?> ctxProto = byBindingArgClass.get(argType);
226         if (ctxProto == null && Augmentation.class.isAssignableFrom(argType)) {
227             ctxProto = augmentationByClass(argType);
228         }
229         final DataContainerCodecContext<?, ?> context = childNonNull(ctxProto, argType,
230             "Class %s is not valid child of %s", argType, getBindingClass()).get();
231         if (context instanceof ChoiceNodeCodecContext) {
232             final ChoiceNodeCodecContext<?> choice = (ChoiceNodeCodecContext<?>) context;
233             choice.addYangPathArgument(arg, builder);
234
235             final Optional<? extends Class<? extends DataObject>> caseType = arg.getCaseType();
236             final Class<? extends DataObject> type = arg.getType();
237             final DataContainerCodecContext<?, ?> caze;
238             if (caseType.isPresent()) {
239                 // Non-ambiguous addressing this should not pose any problems
240                 caze = choice.streamChild(caseType.get());
241             } else {
242                 caze = choice.getCaseByChildClass(type);
243             }
244
245             caze.addYangPathArgument(arg, builder);
246             return caze.bindingPathArgumentChild(arg, builder);
247         }
248         context.addYangPathArgument(arg, builder);
249         return context;
250     }
251
252     @Override
253     public NodeCodecContext yangPathArgumentChild(final YangInstanceIdentifier.PathArgument arg) {
254         final NodeContextSupplier childSupplier;
255         if (arg instanceof NodeIdentifierWithPredicates) {
256             childSupplier = byYang.get(new NodeIdentifier(arg.getNodeType()));
257         } else if (arg instanceof AugmentationIdentifier) {
258             childSupplier = augmentationByYang.get(arg);
259         } else {
260             childSupplier = byYang.get(arg);
261         }
262
263         return childNonNull(childSupplier, arg, "Argument %s is not valid child of %s", arg, getSchema()).get();
264     }
265
266     protected final ValueNodeCodecContext getLeafChild(final String name) {
267         final ValueNodeCodecContext value = leafChild.get(name);
268         if (value == null) {
269             throw IncorrectNestingException.create("Leaf %s is not valid for %s", name, getBindingClass());
270         }
271         return value;
272     }
273
274     private DataContainerCodecPrototype<?> loadChildPrototype(final Class<?> childClass) {
275         final DataSchemaNode origDef = factory().getRuntimeContext().getSchemaDefinition(childClass);
276         // Direct instantiation or use in same module in which grouping
277         // was defined.
278         DataSchemaNode sameName;
279         try {
280             sameName = getSchema().dataChildByName(origDef.getQName());
281         } catch (final IllegalArgumentException e) {
282             LOG.trace("Failed to find schema for {}", origDef, e);
283             sameName = null;
284         }
285         final DataSchemaNode childSchema;
286         if (sameName != null) {
287             // Check if it is:
288             // - exactly same schema node, or
289             // - instantiated node was added via uses statement and is instantiation of same grouping
290             if (origDef.equals(sameName) || origDef.equals(SchemaNodeUtils.getRootOriginalIfPossible(sameName))) {
291                 childSchema = sameName;
292             } else {
293                 // Node has same name, but clearly is different
294                 childSchema = null;
295             }
296         } else {
297             // We are looking for instantiation via uses in other module
298             final QName instantiedName = origDef.getQName().bindTo(namespace());
299             final DataSchemaNode potential = getSchema().dataChildByName(instantiedName);
300             // We check if it is really instantiated from same definition as class was derived
301             if (potential != null && origDef.equals(SchemaNodeUtils.getRootOriginalIfPossible(potential))) {
302                 childSchema = potential;
303             } else {
304                 childSchema = null;
305             }
306         }
307         final DataSchemaNode nonNullChild =
308                 childNonNull(childSchema, childClass, "Node %s does not have child named %s", getSchema(), childClass);
309         return DataContainerCodecPrototype.from(createBindingArg(childClass, nonNullChild), nonNullChild, factory());
310     }
311
312     @SuppressWarnings("unchecked")
313     Item<?> createBindingArg(final Class<?> childClass, final DataSchemaNode childSchema) {
314         return Item.of((Class<? extends DataObject>) childClass);
315     }
316
317     private @Nullable DataContainerCodecPrototype<?> augmentationByClass(final @NonNull Class<?> childClass) {
318         final DataContainerCodecPrototype<?> childProto = augmentationByStream.get(childClass);
319         return childProto != null ? childProto : mismatchedAugmentationByClass(childClass);
320     }
321
322     private @Nullable DataContainerCodecPrototype<?> mismatchedAugmentationByClass(final @NonNull Class<?> childClass) {
323         /*
324          * It is potentially mismatched valid augmentation - we look up equivalent augmentation using reflection
325          * and walk all stream child and compare augmentations classes if they are equivalent. When we find a match
326          * we'll cache it so we do not need to perform reflection operations again.
327          */
328         final ImmutableMap<Class<?>, DataContainerCodecPrototype<?>> local =
329                 (ImmutableMap<Class<?>, DataContainerCodecPrototype<?>>) MISMATCHED_AUGMENTED.getAcquire(this);
330         final DataContainerCodecPrototype<?> mismatched = local.get(childClass);
331         return mismatched != null ? mismatched : loadMismatchedAugmentation(local, childClass);
332
333     }
334
335     private @Nullable DataContainerCodecPrototype<?> loadMismatchedAugmentation(
336             final ImmutableMap<Class<?>, DataContainerCodecPrototype<?>> oldMismatched,
337             final @NonNull Class<?> childClass) {
338         @SuppressWarnings("rawtypes")
339         final Class<?> augTarget = BindingReflections.findAugmentationTarget((Class) childClass);
340         // Do not bother with proposals which are not augmentations of our class, or do not match what the runtime
341         // context would load.
342         if (getBindingClass().equals(augTarget) && belongsToRuntimeContext(childClass)) {
343             for (final DataContainerCodecPrototype<?> realChild : augmentationByStream.values()) {
344                 if (Augmentation.class.isAssignableFrom(realChild.getBindingClass())
345                         && BindingReflections.isSubstitutionFor(childClass, realChild.getBindingClass())) {
346                     return cacheMismatched(oldMismatched, childClass, realChild);
347                 }
348             }
349         }
350         LOG.trace("Failed to resolve {} as a valid augmentation in {}", childClass, this);
351         return null;
352     }
353
354     private @NonNull DataContainerCodecPrototype<?> cacheMismatched(
355             final @NonNull ImmutableMap<Class<?>, DataContainerCodecPrototype<?>> oldMismatched,
356             final @NonNull Class<?> childClass, final @NonNull DataContainerCodecPrototype<?> prototype) {
357
358         ImmutableMap<Class<?>, DataContainerCodecPrototype<?>> expected = oldMismatched;
359         while (true) {
360             final Map<Class<?>, DataContainerCodecPrototype<?>> newMismatched =
361                     ImmutableMap.<Class<?>, DataContainerCodecPrototype<?>>builderWithExpectedSize(expected.size() + 1)
362                         .putAll(expected)
363                         .put(childClass, prototype)
364                         .build();
365
366             final Object witness = MISMATCHED_AUGMENTED.compareAndExchangeRelease(this, expected, newMismatched);
367             if (witness == expected) {
368                 LOG.trace("Cached mismatched augmentation {} -> {} in {}", childClass, prototype, this);
369                 return prototype;
370             }
371
372             expected = (ImmutableMap<Class<?>, DataContainerCodecPrototype<?>>) witness;
373             final DataContainerCodecPrototype<?> existing = expected.get(childClass);
374             if (existing != null) {
375                 LOG.trace("Using raced mismatched augmentation {} -> {} in {}", childClass, existing, this);
376                 return existing;
377             }
378         }
379     }
380
381     private boolean belongsToRuntimeContext(final Class<?> cls) {
382         final BindingRuntimeContext ctx = factory().getRuntimeContext();
383         final Class<?> loaded;
384         try {
385             loaded = ctx.loadClass(DefaultType.of(cls));
386         } catch (ClassNotFoundException e) {
387             LOG.debug("Proposed {} cannot be loaded in {}", cls, ctx, e);
388             return false;
389         }
390         return cls.equals(loaded);
391     }
392
393     private @NonNull DataContainerCodecPrototype<?> getAugmentationPrototype(final Type value) {
394         final BindingRuntimeContext ctx = factory().getRuntimeContext();
395
396         final Class<? extends Augmentation<?>> augClass;
397         try {
398             augClass = ctx.loadClass(value);
399         } catch (final ClassNotFoundException e) {
400             throw new IllegalStateException("RuntimeContext references type " + value + " but failed to its class", e);
401         }
402
403         final Entry<AugmentationIdentifier, AugmentationSchemaNode> augSchema =
404                 ctx.getResolvedAugmentationSchema(getSchema(), augClass);
405         return DataContainerCodecPrototype.from(augClass, augSchema.getKey(), augSchema.getValue(), factory());
406     }
407
408     @SuppressWarnings("checkstyle:illegalCatch")
409     protected final @NonNull D createBindingProxy(final NormalizedNodeContainer<?, ?, ?> node) {
410         try {
411             return (D) proxyConstructor.invokeExact(this, node);
412         } catch (final Throwable e) {
413             Throwables.throwIfUnchecked(e);
414             throw new IllegalStateException(e);
415         }
416     }
417
418     @SuppressWarnings("unchecked")
419     Map<Class<? extends Augmentation<?>>, Augmentation<?>> getAllAugmentationsFrom(
420             final NormalizedNodeContainer<?, PathArgument, NormalizedNode<?, ?>> data) {
421
422         @SuppressWarnings("rawtypes")
423         final Map map = new HashMap<>();
424
425         for (final NormalizedNode<?, ?> childValue : data.getValue()) {
426             if (childValue instanceof AugmentationNode) {
427                 final AugmentationNode augDomNode = (AugmentationNode) childValue;
428                 final DataContainerCodecPrototype<?> codecProto = augmentationByYang.get(augDomNode.getIdentifier());
429                 if (codecProto != null) {
430                     final DataContainerCodecContext<?, ?> codec = codecProto.get();
431                     map.put(codec.getBindingClass(), codec.deserializeObject(augDomNode));
432                 }
433             }
434         }
435         for (final DataContainerCodecPrototype<?> value : augmentationByStream.values()) {
436             final Optional<NormalizedNode<?, ?>> augData = data.getChild(value.getYangArg());
437             if (augData.isPresent()) {
438                 map.put(value.getBindingClass(), value.get().deserializeObject(augData.get()));
439             }
440         }
441         return map;
442     }
443
444     final @NonNull Class<? extends CodecDataObject<?>> generatedClass() {
445         return generatedClass;
446     }
447
448     @Override
449     public InstanceIdentifier.PathArgument deserializePathArgument(final YangInstanceIdentifier.PathArgument arg) {
450         checkArgument(getDomPathArgument().equals(arg));
451         return bindingArg();
452     }
453
454     @Override
455     public YangInstanceIdentifier.PathArgument serializePathArgument(final InstanceIdentifier.PathArgument arg) {
456         checkArgument(bindingArg().equals(arg));
457         return getDomPathArgument();
458     }
459 }