X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=blobdiff_plain;f=binding%2Fmdsal-binding-dom-codec%2Fsrc%2Fmain%2Fjava%2Forg%2Fopendaylight%2Fmdsal%2Fbinding%2Fdom%2Fcodec%2Fimpl%2FDataContainerCodecContext.java;h=ebd628c0ff1c5c4fcdb41cf482a2c4d1df95a573;hb=f3be50ed801e5de305eb46b824e6bc3c6075e4bc;hp=2c37161b9a6aaba157418b57ea39206214978517;hpb=26e5eec596aee0c2c23769df2805f354c53bf079;p=mdsal.git diff --git a/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/DataContainerCodecContext.java b/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/DataContainerCodecContext.java index 2c37161b9a..ebd628c0ff 100644 --- a/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/DataContainerCodecContext.java +++ b/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/DataContainerCodecContext.java @@ -12,24 +12,22 @@ import static java.util.Objects.requireNonNull; import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableSet; import edu.umd.cs.findbugs.annotations.CheckReturnValue; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.io.IOException; import java.lang.invoke.MethodHandles; import java.lang.invoke.VarHandle; import java.lang.reflect.Method; import java.lang.reflect.Modifier; -import java.lang.reflect.ParameterizedType; import java.util.Arrays; import java.util.HashSet; import java.util.List; -import java.util.Map; import java.util.Optional; import java.util.Set; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; +import org.opendaylight.mdsal.binding.dom.codec.api.BindingDataContainerCodecTreeNode; import org.opendaylight.mdsal.binding.dom.codec.api.BindingNormalizedNodeCachingCodec; import org.opendaylight.mdsal.binding.dom.codec.api.BindingNormalizedNodeCodec; -import org.opendaylight.mdsal.binding.dom.codec.api.BindingStreamEventWriter; -import org.opendaylight.mdsal.binding.dom.codec.api.CommonDataObjectCodecTreeNode; import org.opendaylight.mdsal.binding.dom.codec.api.IncorrectNestingException; import org.opendaylight.mdsal.binding.dom.codec.api.MissingClassInLoadingStrategyException; import org.opendaylight.mdsal.binding.dom.codec.api.MissingSchemaException; @@ -37,96 +35,102 @@ import org.opendaylight.mdsal.binding.dom.codec.api.MissingSchemaForClassExcepti import org.opendaylight.mdsal.binding.model.api.Type; import org.opendaylight.mdsal.binding.runtime.api.BindingRuntimeContext; import org.opendaylight.mdsal.binding.runtime.api.CompositeRuntimeType; -import org.opendaylight.mdsal.binding.runtime.api.RuntimeTypeContainer; -import org.opendaylight.mdsal.binding.spec.reflect.BindingReflections; import org.opendaylight.yangtools.util.ClassLoaderUtils; +import org.opendaylight.yangtools.yang.binding.Augmentable; import org.opendaylight.yangtools.yang.binding.Augmentation; import org.opendaylight.yangtools.yang.binding.BindingObject; import org.opendaylight.yangtools.yang.binding.DataContainer; import org.opendaylight.yangtools.yang.binding.DataObject; import org.opendaylight.yangtools.yang.binding.InstanceIdentifier.PathArgument; 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.NodeIdentifier; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates; import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNormalizedNodeStreamWriter; import org.opendaylight.yangtools.yang.data.impl.schema.NormalizationResultHolder; +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.TypedDataSchemaNode; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -abstract sealed class DataContainerCodecContext - extends CodecContext implements CommonDataObjectCodecTreeNode - permits AbstractDataObjectCodecContext, ChoiceCodecContext, RootCodecContext { +abstract sealed class DataContainerCodecContext> + extends CodecContext implements BindingDataContainerCodecTreeNode + permits ChoiceCodecContext, CommonDataObjectCodecContext { private static final Logger LOG = LoggerFactory.getLogger(DataContainerCodecContext.class); private static final VarHandle EVENT_STREAM_SERIALIZER; static { try { EVENT_STREAM_SERIALIZER = MethodHandles.lookup().findVarHandle(DataContainerCodecContext.class, - "eventStreamSerializer", DataObjectSerializer.class); + "eventStreamSerializer", DataContainerSerializer.class); } catch (NoSuchFieldException | IllegalAccessException e) { throw new ExceptionInInitializerError(e); } } - final @NonNull DataContainerCodecPrototype prototype; + private final @NonNull P prototype; + private final @NonNull ChildAddressabilitySummary childAddressabilitySummary; // Accessed via a VarHandle @SuppressWarnings("unused") - private volatile DataObjectSerializer eventStreamSerializer; + @SuppressFBWarnings(value = "UUF_UNUSED_FIELD", justification = "https://github.com/spotbugs/spotbugs/issues/2749") + private volatile DataContainerSerializer eventStreamSerializer; - DataContainerCodecContext(final DataContainerCodecPrototype prototype) { + DataContainerCodecContext(final P prototype) { this.prototype = requireNonNull(prototype); + childAddressabilitySummary = computeChildAddressabilitySummary(prototype.runtimeType().statement()); } - @Override - public final ChildAddressabilitySummary getChildAddressabilitySummary() { - return prototype.getChildAddressabilitySummary(); + final @NonNull P prototype() { + return prototype; } - protected final QNameModule namespace() { - return prototype.getNamespace(); + @Override + @SuppressWarnings("unchecked") + public final Class getBindingClass() { + return (Class) prototype().javaClass(); } - protected final CodecContextFactory factory() { - return prototype.getFactory(); + // overridden in AugmentationCodecContext + @Override + protected NodeIdentifier getDomPathArgument() { + return prototype.yangArg(); } - protected final @NonNull T type() { - return prototype.getType(); + @Override + public final ChildAddressabilitySummary getChildAddressabilitySummary() { + return childAddressabilitySummary; } + // Non-final for ChoiceCodecContext @Override - protected NodeIdentifier getDomPathArgument() { - return prototype.getYangArg(); + public CodecContext yangPathArgumentChild(final YangInstanceIdentifier.PathArgument arg) { + CodecContextSupplier supplier; + if (arg instanceof NodeIdentifier nodeId) { + supplier = yangChildSupplier(nodeId); + } else if (arg instanceof NodeIdentifierWithPredicates nip) { + supplier = yangChildSupplier(new NodeIdentifier(nip.getNodeType())); + } else { + supplier = null; + } + return childNonNull(supplier, arg, "Argument %s is not valid child of %s", arg, getSchema()).getCodecContext(); } - /** - * Returns nested node context using supplied YANG Instance Identifier. - * - * @param arg Yang Instance Identifier Argument - * @return Context of child - * @throws IllegalArgumentException If supplied argument does not represent valid child. - */ - @Override - public abstract CodecContext yangPathArgumentChild(YangInstanceIdentifier.PathArgument arg); + abstract @Nullable CodecContextSupplier yangChildSupplier(@NonNull NodeIdentifier arg); - /** - * Returns nested node context using supplied Binding Instance Identifier - * and adds YANG instance identifiers to supplied list. - * - * @param arg Binding Instance Identifier Argument - * @return Context of child or null if supplied {@code arg} does not represent valid child. - * @throws IllegalArgumentException If supplied argument does not represent valid child. - */ @Override - public DataContainerCodecContext bindingPathArgumentChild(final PathArgument arg, - final List builder) { - final var child = getStreamChild(arg.getType()); - child.addYangPathArgument(arg, builder); - return child; - } + public abstract CommonDataObjectCodecContext bindingPathArgumentChild(PathArgument arg, + List builder); /** * Serializes supplied Binding Path Argument and adds all necessary YANG instance identifiers to supplied list. @@ -147,42 +151,27 @@ abstract sealed class DataContainerCodecContext DataContainerCodecContext getStreamChild(final Class childClass) { + return childNonNull(streamChild(childClass), childClass, + "Child %s is not valid child of %s", getBindingClass(), childClass); } @SuppressWarnings("unchecked") @Override - public final Class getBindingClass() { - return Class.class.cast(prototype.getBindingClass()); + public final DataContainerCodecContext streamChild(final Class childClass) { + final var childProto = streamChildPrototype(requireNonNull(childClass)); + return childProto == null ? null : (DataContainerCodecContext) childProto.getCodecContext(); } - @Override - public abstract DataContainerCodecContext getStreamChild(Class childClass); - - /** - * Returns child context as if it was walked by {@link BindingStreamEventWriter}. This means that to enter case, one - * must issue getChild(ChoiceClass).getChild(CaseClass). - * - * @param childClass child class - * @return Context of child or Optional.empty is supplied class is not applicable in context. - */ - @Override - public abstract DataContainerCodecContext streamChild(Class childClass); + abstract @Nullable DataContainerPrototype streamChildPrototype(@NonNull Class childClass); @Override public String toString() { - return getClass().getSimpleName() + " [" + prototype.getBindingClass() + "]"; + return getClass().getSimpleName() + " [" + getBindingClass() + "]"; } - static final & BindingNormalizedNodeCodec> + static final & BindingNormalizedNodeCodec> @NonNull BindingNormalizedNodeCachingCodec createCachingCodec(final C context, final ImmutableCollection> cacheSpecifier) { return cacheSpecifier.isEmpty() ? new NonCachingCodec<>(context) @@ -215,8 +204,8 @@ abstract sealed class DataContainerCodecContext childClass, final String message, final Object... args) { - return childNullException(factory().getRuntimeContext(), childClass, message, args); + return childNullException(prototype().contextFactory().getRuntimeContext(), childClass, message, args); } @CheckReturnValue @@ -251,16 +240,16 @@ abstract sealed class DataContainerCodecContext> getYangModeledReturnType(final Method method, - final String prefix) { - final String methodName = method.getName(); - if ("getClass".equals(methodName) || !methodName.startsWith(prefix) || method.getParameterCount() > 0) { - return Optional.empty(); - } - - final Class returnType = method.getReturnType(); - if (DataContainer.class.isAssignableFrom(returnType)) { - return optionalDataContainer(returnType); - } else if (List.class.isAssignableFrom(returnType)) { - return getYangModeledReturnType(method, 0); - } else if (Map.class.isAssignableFrom(returnType)) { - return getYangModeledReturnType(method, 1); - } - return Optional.empty(); - } - - @SuppressWarnings("checkstyle:illegalCatch") - private static Optional> getYangModeledReturnType(final Method method, - final int parameterOffset) { - try { - return ClassLoaderUtils.callWithClassLoader(method.getDeclaringClass().getClassLoader(), - () -> genericParameter(method.getGenericReturnType(), parameterOffset) - .flatMap(result -> result instanceof Class ? optionalCast((Class) result) : Optional.empty())); - } catch (Exception e) { - /* - * It is safe to log this this exception on debug, since this - * method should not fail. Only failures are possible if the - * runtime / backing. - */ - LOG.debug("Unable to find YANG modeled return type for {}", method, e); - } - return Optional.empty(); - } - - private static Optional genericParameter(final java.lang.reflect.Type type, - final int offset) { - if (type instanceof ParameterizedType parameterized) { - final var parameters = parameterized.getActualTypeArguments(); - if (parameters.length > offset) { - return Optional.of(parameters[offset]); - } - } - return Optional.empty(); - } - - private static Optional> optionalCast(final Class type) { - return DataContainer.class.isAssignableFrom(type) ? optionalDataContainer(type) : Optional.empty(); - } - - // FIXME: MDSAL-780: remove this method - static final Optional> optionalDataContainer(final Class type) { - return Optional.of(type.asSubclass(DataContainer.class)); - } - /** * Determines if two augmentation classes or case classes represents same data. * @@ -378,8 +310,7 @@ abstract sealed class DataContainerCodecContext> findAugmentationTarget( + final Class> augmentation) { + final Optional>> opt = ClassLoaderUtils.findFirstGenericArgument(augmentation, + Augmentation.class); + return opt.orElse(null); + } + + 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; + } + + 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; + } }