*/
package org.opendaylight.mdsal.binding.dom.codec.impl;
-import com.google.common.collect.Iterables;
-import javax.annotation.concurrent.GuardedBy;
+import static com.google.common.base.Verify.verify;
+import static java.util.Objects.requireNonNull;
+
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.VarHandle;
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.mdsal.binding.dom.codec.api.CommonDataObjectCodecTreeNode.ChildAddressabilitySummary;
import org.opendaylight.mdsal.binding.dom.codec.impl.NodeCodecContext.CodecContextFactory;
-import org.opendaylight.yangtools.yang.binding.DataRoot;
-import org.opendaylight.yangtools.yang.binding.Identifiable;
-import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+import org.opendaylight.mdsal.binding.runtime.api.CompositeRuntimeType;
+import org.opendaylight.mdsal.binding.runtime.api.RuntimeType;
+import org.opendaylight.mdsal.binding.runtime.api.RuntimeTypeContainer;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier.Item;
+import org.opendaylight.yangtools.yang.common.QName;
import org.opendaylight.yangtools.yang.common.QNameModule;
-import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
-import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
-import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
-import org.opendaylight.yangtools.yang.model.api.AugmentationSchema;
-import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode;
+import org.opendaylight.yangtools.yang.model.api.AnydataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.AnyxmlSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.AugmentationSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.CaseSchemaNode;
import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
-import org.opendaylight.yangtools.yang.model.api.NotificationDefinition;
-import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.model.api.TypedDataSchemaNode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+abstract sealed class DataContainerCodecPrototype<T extends RuntimeTypeContainer> implements NodeContextSupplier
+ permits AugmentationCodecPrototype, DataObjectCodecPrototype {
+ private static final Logger LOG = LoggerFactory.getLogger(DataContainerCodecPrototype.class);
+
+ private static final VarHandle INSTANCE;
+
+ static {
+ try {
+ INSTANCE = MethodHandles.lookup().findVarHandle(DataContainerCodecPrototype.class,
+ "instance", DataContainerCodecContext.class);
+ } catch (NoSuchFieldException | IllegalAccessException e) {
+ throw new ExceptionInInitializerError(e);
+ }
+ }
+
+ private final @NonNull T type;
+ private final @NonNull QNameModule namespace;
+ private final @NonNull CodecContextFactory factory;
+ private final @NonNull Item<?> bindingArg;
+ private final @NonNull ChildAddressabilitySummary childAddressabilitySummary;
-final class DataContainerCodecPrototype<T> implements NodeContextSupplier {
+ // multiple paths represent augmentation wrapper
+ // FIXME: this means it is either this or 'childArgs'
- private final T schema;
- private final QNameModule namespace;
- private final CodecContextFactory factory;
- private final Class<?> bindingClass;
- private final InstanceIdentifier.Item<?> bindingArg;
- private final YangInstanceIdentifier.PathArgument yangArg;
- private volatile DataContainerCodecContext<?,T> instance = null;
+ // Accessed via INSTANCE
+ @SuppressWarnings("unused")
+ private volatile DataContainerCodecContext<?, T> instance;
- @SuppressWarnings({"rawtypes", "unchecked"})
- private DataContainerCodecPrototype(final Class<?> cls, final YangInstanceIdentifier.PathArgument arg, final T nodeSchema,
+ DataContainerCodecPrototype(final Item<?> bindingArg, final QNameModule namespace, final T type,
final CodecContextFactory factory) {
- super();
- this.bindingClass = cls;
- this.yangArg = arg;
- this.schema = nodeSchema;
- this.factory = factory;
- this.bindingArg = new InstanceIdentifier.Item(bindingClass);
-
- if (arg instanceof AugmentationIdentifier) {
- this.namespace = Iterables.getFirst(((AugmentationIdentifier) arg).getPossibleChildNames(), null).getModule();
- } else {
- this.namespace = arg.getNodeType().getModule();
+ this.bindingArg = requireNonNull(bindingArg);
+ this.namespace = requireNonNull(namespace);
+ this.type = requireNonNull(type);
+ this.factory = requireNonNull(factory);
+
+ childAddressabilitySummary = type instanceof RuntimeType runtimeType
+ ? computeChildAddressabilitySummary(runtimeType.statement())
+ // BindingRuntimeTypes, does not matter
+ : ChildAddressabilitySummary.MIXED;
+ }
+
+ private static @NonNull ChildAddressabilitySummary computeChildAddressabilitySummary(final Object nodeSchema) {
+ // FIXME: rework this to work on EffectiveStatements
+ if (nodeSchema instanceof DataNodeContainer contaner) {
+ boolean haveAddressable = false;
+ boolean haveUnaddressable = false;
+ for (DataSchemaNode child : contaner.getChildNodes()) {
+ if (child instanceof ContainerSchemaNode || child instanceof AugmentationSchemaNode) {
+ haveAddressable = true;
+ } else if (child instanceof ListSchemaNode list) {
+ if (list.getKeyDefinition().isEmpty()) {
+ haveUnaddressable = true;
+ } else {
+ haveAddressable = true;
+ }
+ } else if (child instanceof AnydataSchemaNode || child instanceof AnyxmlSchemaNode
+ || child instanceof TypedDataSchemaNode) {
+ haveUnaddressable = true;
+ } else if (child instanceof ChoiceSchemaNode choice) {
+ switch (computeChildAddressabilitySummary(choice)) {
+ case ADDRESSABLE -> haveAddressable = true;
+ case UNADDRESSABLE -> haveUnaddressable = true;
+ case MIXED -> {
+ haveAddressable = true;
+ haveUnaddressable = true;
+ }
+ default -> throw new IllegalStateException("Unhandled accessibility summary for " + child);
+ }
+ } else {
+ LOG.warn("Unhandled child node {}", child);
+ }
+ }
+
+ if (!haveAddressable) {
+ // Empty or all are unaddressable
+ return ChildAddressabilitySummary.UNADDRESSABLE;
+ }
+
+ return haveUnaddressable ? ChildAddressabilitySummary.MIXED : ChildAddressabilitySummary.ADDRESSABLE;
+ } else if (nodeSchema instanceof ChoiceSchemaNode choice) {
+ return computeChildAddressabilitySummary(choice);
}
+
+ // No child nodes possible: return unaddressable
+ return ChildAddressabilitySummary.UNADDRESSABLE;
}
- @SuppressWarnings({ "unchecked", "rawtypes" })
- static <T extends DataSchemaNode> DataContainerCodecPrototype<T> from(final Class<?> cls, final T schema,
- final CodecContextFactory factory) {
- return new DataContainerCodecPrototype(cls, NodeIdentifier.create(schema.getQName()), schema, factory);
+ private static @NonNull ChildAddressabilitySummary computeChildAddressabilitySummary(
+ final ChoiceSchemaNode choice) {
+ boolean haveAddressable = false;
+ boolean haveUnaddressable = false;
+ for (CaseSchemaNode child : choice.getCases()) {
+ switch (computeChildAddressabilitySummary(child)) {
+ case ADDRESSABLE:
+ haveAddressable = true;
+ break;
+ case UNADDRESSABLE:
+ haveUnaddressable = true;
+ break;
+ case MIXED:
+ // A child is mixed, which means we are mixed, too
+ return ChildAddressabilitySummary.MIXED;
+ default:
+ throw new IllegalStateException("Unhandled accessibility summary for " + child);
+ }
+ }
+
+ if (!haveAddressable) {
+ // Empty or all are unaddressable
+ return ChildAddressabilitySummary.UNADDRESSABLE;
+ }
+
+ return haveUnaddressable ? ChildAddressabilitySummary.MIXED : ChildAddressabilitySummary.ADDRESSABLE;
}
- static DataContainerCodecPrototype<SchemaContext> rootPrototype(final CodecContextFactory factory) {
- final SchemaContext schema = factory.getRuntimeContext().getSchemaContext();
- final NodeIdentifier arg = NodeIdentifier.create(schema.getQName());
- return new DataContainerCodecPrototype<SchemaContext>(DataRoot.class, arg, schema, factory);
+ static <T extends CompositeRuntimeType> DataContainerCodecPrototype<T> from(final Class<?> cls, final T type,
+ final CodecContextFactory factory) {
+ return new DataObjectCodecPrototype<>(cls, createIdentifier(type), type, factory);
}
+ static <T extends CompositeRuntimeType> DataContainerCodecPrototype<T> from(final Item<?> bindingArg, final T type,
+ final CodecContextFactory factory) {
+ return new DataObjectCodecPrototype<>(bindingArg, createIdentifier(type), type, factory);
+ }
- @SuppressWarnings({ "rawtypes", "unchecked" })
- static DataContainerCodecPrototype<?> from(final Class<?> augClass, final AugmentationIdentifier arg,
- final AugmentationSchema schema, final CodecContextFactory factory) {
- return new DataContainerCodecPrototype(augClass, arg, schema, factory);
+ private static @NonNull NodeIdentifier createIdentifier(final CompositeRuntimeType type) {
+ final Object arg = type.statement().argument();
+ verify(arg instanceof QName, "Unexpected type %s argument %s", type, arg);
+ return NodeIdentifier.create((QName) arg);
}
- static DataContainerCodecPrototype<NotificationDefinition> from(final Class<?> augClass, final NotificationDefinition schema, final CodecContextFactory factory) {
- final PathArgument arg = NodeIdentifier.create(schema.getQName());
- return new DataContainerCodecPrototype<NotificationDefinition>(augClass,arg, schema, factory);
+ final @NonNull T getType() {
+ return type;
}
- protected T getSchema() {
- return schema;
+ final @NonNull ChildAddressabilitySummary getChildAddressabilitySummary() {
+ return childAddressabilitySummary;
}
- protected QNameModule getNamespace() {
+ final @NonNull QNameModule getNamespace() {
return namespace;
}
- protected CodecContextFactory getFactory() {
+ final @NonNull CodecContextFactory getFactory() {
return factory;
}
- protected Class<?> getBindingClass() {
- return bindingClass;
+ final @NonNull Class<?> getBindingClass() {
+ return bindingArg.getType();
}
- protected InstanceIdentifier.Item<?> getBindingArg() {
+ final @NonNull Item<?> getBindingArg() {
return bindingArg;
}
- protected YangInstanceIdentifier.PathArgument getYangArg() {
- return yangArg;
- }
+ abstract @NonNull NodeIdentifier getYangArg();
@Override
- public DataContainerCodecContext<?,T> get() {
- DataContainerCodecContext<?,T> tmp = instance;
- if (tmp == null) {
- synchronized (this) {
- tmp = instance;
- if (tmp == null) {
- tmp = createInstance();
- instance = tmp;
- }
- }
- }
-
- return tmp;
+ public final DataContainerCodecContext<?, T> get() {
+ final var existing = (DataContainerCodecContext<?, T>) INSTANCE.getAcquire(this);
+ return existing != null ? existing : loadInstance();
}
- @GuardedBy("this")
- @SuppressWarnings({ "rawtypes", "unchecked" })
- private DataContainerCodecContext<?,T> createInstance() {
- // FIXME: make protected abstract
- if (schema instanceof ContainerSchemaNode) {
- return new ContainerNodeCodecContext(this);
- } else if (schema instanceof ListSchemaNode) {
- if (Identifiable.class.isAssignableFrom(getBindingClass())) {
- return new KeyedListNodeCodecContext(this);
- } else {
- return new ListNodeCodecContext(this);
- }
- } else if (schema instanceof ChoiceSchemaNode) {
- return new ChoiceNodeCodecContext(this);
- } else if (schema instanceof AugmentationSchema) {
- return new AugmentationNodeContext(this);
- } else if (schema instanceof ChoiceCaseNode) {
- return new CaseNodeCodecContext(this);
- }
- throw new IllegalArgumentException("Unsupported type " + bindingClass + " " + schema);
+ @SuppressWarnings("unchecked")
+ final <R extends CompositeRuntimeType> DataObjectCodecContext<?, R> getDataObject() {
+ final var context = get();
+ verify(context instanceof DataObjectCodecContext, "Unexpected instance %s", context);
+ return (DataObjectCodecContext<?, R>) context;
}
- boolean isChoice() {
- return schema instanceof ChoiceSchemaNode;
+ private @NonNull DataContainerCodecContext<?, T> loadInstance() {
+ final var tmp = createInstance();
+ final var witness = (DataContainerCodecContext<?, T>) INSTANCE.compareAndExchangeRelease(this, null, tmp);
+ return witness == null ? tmp : witness;
}
+
+ // This method must allow concurrent loading, i.e. nothing in it may have effects outside of the loaded object
+ abstract @NonNull DataContainerCodecContext<?, T> createInstance();
}