Split up DataObjectCodecPrototype
[mdsal.git] / binding / mdsal-binding-dom-codec / src / main / java / org / opendaylight / mdsal / binding / dom / codec / impl / DataContainerCodecPrototype.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 java.lang.invoke.MethodHandles;
13 import java.lang.invoke.VarHandle;
14 import org.eclipse.jdt.annotation.NonNull;
15 import org.opendaylight.mdsal.binding.dom.codec.api.CommonDataObjectCodecTreeNode.ChildAddressabilitySummary;
16 import org.opendaylight.mdsal.binding.runtime.api.RuntimeType;
17 import org.opendaylight.mdsal.binding.runtime.api.RuntimeTypeContainer;
18 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier.Item;
19 import org.opendaylight.yangtools.yang.common.QNameModule;
20 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
21 import org.opendaylight.yangtools.yang.model.api.AnydataSchemaNode;
22 import org.opendaylight.yangtools.yang.model.api.AnyxmlSchemaNode;
23 import org.opendaylight.yangtools.yang.model.api.AugmentationSchemaNode;
24 import org.opendaylight.yangtools.yang.model.api.CaseSchemaNode;
25 import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
26 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
27 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
28 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
29 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
30 import org.opendaylight.yangtools.yang.model.api.TypedDataSchemaNode;
31 import org.slf4j.Logger;
32 import org.slf4j.LoggerFactory;
33
34 abstract sealed class DataContainerCodecPrototype<T extends RuntimeTypeContainer> implements CodecContextSupplier
35         permits AugmentationCodecPrototype, DataObjectCodecPrototype {
36     private static final Logger LOG = LoggerFactory.getLogger(DataContainerCodecPrototype.class);
37
38     private static final VarHandle INSTANCE;
39
40     static {
41         try {
42             INSTANCE = MethodHandles.lookup().findVarHandle(DataContainerCodecPrototype.class,
43                 "instance", DataContainerCodecContext.class);
44         } catch (NoSuchFieldException | IllegalAccessException e) {
45             throw new ExceptionInInitializerError(e);
46         }
47     }
48
49     private final @NonNull T type;
50     private final @NonNull QNameModule namespace;
51     private final @NonNull CodecContextFactory factory;
52     private final @NonNull Item<?> bindingArg;
53     private final @NonNull ChildAddressabilitySummary childAddressabilitySummary;
54
55     // multiple paths represent augmentation wrapper
56     // FIXME: this means it is either this or 'childArgs'
57
58     // Accessed via INSTANCE
59     @SuppressWarnings("unused")
60     private volatile DataContainerCodecContext<?, T> instance;
61
62     DataContainerCodecPrototype(final Item<?> bindingArg, final QNameModule namespace, final T type,
63             final CodecContextFactory factory) {
64         this.bindingArg = requireNonNull(bindingArg);
65         this.namespace = requireNonNull(namespace);
66         this.type = requireNonNull(type);
67         this.factory = requireNonNull(factory);
68
69         childAddressabilitySummary = type instanceof RuntimeType runtimeType
70             ? computeChildAddressabilitySummary(runtimeType.statement())
71                 // BindingRuntimeTypes, does not matter
72                 : ChildAddressabilitySummary.MIXED;
73     }
74
75     private static @NonNull ChildAddressabilitySummary computeChildAddressabilitySummary(final Object nodeSchema) {
76         // FIXME: rework this to work on EffectiveStatements
77         if (nodeSchema instanceof DataNodeContainer contaner) {
78             boolean haveAddressable = false;
79             boolean haveUnaddressable = false;
80             for (DataSchemaNode child : contaner.getChildNodes()) {
81                 if (child instanceof ContainerSchemaNode || child instanceof AugmentationSchemaNode) {
82                     haveAddressable = true;
83                 } else if (child instanceof ListSchemaNode list) {
84                     if (list.getKeyDefinition().isEmpty()) {
85                         haveUnaddressable = true;
86                     } else {
87                         haveAddressable = true;
88                     }
89                 } else if (child instanceof AnydataSchemaNode || child instanceof AnyxmlSchemaNode
90                         || child instanceof TypedDataSchemaNode) {
91                     haveUnaddressable = true;
92                 } else if (child instanceof ChoiceSchemaNode choice) {
93                     switch (computeChildAddressabilitySummary(choice)) {
94                         case ADDRESSABLE -> haveAddressable = true;
95                         case UNADDRESSABLE -> haveUnaddressable = true;
96                         case MIXED -> {
97                             haveAddressable = true;
98                             haveUnaddressable = true;
99                         }
100                         default -> throw new IllegalStateException("Unhandled accessibility summary for " + child);
101                     }
102                 } else {
103                     LOG.warn("Unhandled child node {}", child);
104                 }
105             }
106
107             if (!haveAddressable) {
108                 // Empty or all are unaddressable
109                 return ChildAddressabilitySummary.UNADDRESSABLE;
110             }
111
112             return haveUnaddressable ? ChildAddressabilitySummary.MIXED : ChildAddressabilitySummary.ADDRESSABLE;
113         } else if (nodeSchema instanceof ChoiceSchemaNode choice) {
114             return computeChildAddressabilitySummary(choice);
115         }
116
117         // No child nodes possible: return unaddressable
118         return ChildAddressabilitySummary.UNADDRESSABLE;
119     }
120
121     private static @NonNull ChildAddressabilitySummary computeChildAddressabilitySummary(
122             final ChoiceSchemaNode choice) {
123         boolean haveAddressable = false;
124         boolean haveUnaddressable = false;
125         for (CaseSchemaNode child : choice.getCases()) {
126             switch (computeChildAddressabilitySummary(child)) {
127                 case ADDRESSABLE:
128                     haveAddressable = true;
129                     break;
130                 case UNADDRESSABLE:
131                     haveUnaddressable = true;
132                     break;
133                 case MIXED:
134                     // A child is mixed, which means we are mixed, too
135                     return ChildAddressabilitySummary.MIXED;
136                 default:
137                     throw new IllegalStateException("Unhandled accessibility summary for " + child);
138             }
139         }
140
141         if (!haveAddressable) {
142             // Empty or all are unaddressable
143             return ChildAddressabilitySummary.UNADDRESSABLE;
144         }
145
146         return haveUnaddressable ? ChildAddressabilitySummary.MIXED : ChildAddressabilitySummary.ADDRESSABLE;
147     }
148
149     final @NonNull T getType() {
150         return type;
151     }
152
153     final @NonNull ChildAddressabilitySummary getChildAddressabilitySummary() {
154         return childAddressabilitySummary;
155     }
156
157     final @NonNull QNameModule getNamespace() {
158         return namespace;
159     }
160
161     final @NonNull CodecContextFactory getFactory() {
162         return factory;
163     }
164
165     final @NonNull Class<?> getBindingClass() {
166         return bindingArg.getType();
167     }
168
169     final @NonNull Item<?> getBindingArg() {
170         return bindingArg;
171     }
172
173     abstract @NonNull NodeIdentifier getYangArg();
174
175     @Override
176     public final DataContainerCodecContext<?, T> get() {
177         final var existing = (DataContainerCodecContext<?, T>) INSTANCE.getAcquire(this);
178         return existing != null ? existing : loadInstance();
179     }
180
181     private @NonNull DataContainerCodecContext<?, T> loadInstance() {
182         final var tmp = createInstance();
183         final var witness = (DataContainerCodecContext<?, T>) INSTANCE.compareAndExchangeRelease(this, null, tmp);
184         return witness == null ? tmp : witness;
185     }
186
187     // This method must allow concurrent loading, i.e. nothing in it may have effects outside of the loaded object
188     abstract @NonNull DataContainerCodecContext<?, T> createInstance();
189 }