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%2FChoiceNodeCodecContext.java;h=6100d4db209e08b715689758243ce6da79fef5f3;hb=11408d627adca7eb71ac956c3ad01f75b6b91596;hp=be96700de3f35bab019a0f50f1b5fb9cedee9ade;hpb=c241dcfa5322ac10810a1068ccd2eb57f6f2dbb2;p=mdsal.git diff --git a/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/ChoiceNodeCodecContext.java b/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/ChoiceNodeCodecContext.java index be96700de3..6100d4db20 100644 --- a/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/ChoiceNodeCodecContext.java +++ b/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/ChoiceNodeCodecContext.java @@ -7,7 +7,8 @@ */ package org.opendaylight.mdsal.binding.dom.codec.impl; -import com.google.common.base.Preconditions; +import static com.google.common.base.Preconditions.checkArgument; + import com.google.common.collect.ImmutableListMultimap; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap.Builder; @@ -17,6 +18,7 @@ import com.google.common.collect.Lists; import com.google.common.collect.MultimapBuilder.SetMultimapBuilder; import com.google.common.collect.Multimaps; import com.google.common.collect.SetMultimap; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.util.ArrayList; import java.util.Comparator; import java.util.HashMap; @@ -27,8 +29,12 @@ import java.util.Map.Entry; import java.util.Optional; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.eclipse.jdt.annotation.NonNull; +import org.opendaylight.mdsal.binding.model.api.JavaTypeName; +import org.opendaylight.mdsal.binding.runtime.api.CaseRuntimeType; +import org.opendaylight.mdsal.binding.runtime.api.ChoiceRuntimeType; import org.opendaylight.mdsal.binding.spec.reflect.BindingReflections; import org.opendaylight.yangtools.yang.binding.DataObject; import org.opendaylight.yangtools.yang.binding.InstanceIdentifier.PathArgument; @@ -36,82 +42,84 @@ import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier; import org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode; import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; -import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer; -import org.opendaylight.yangtools.yang.data.impl.schema.SchemaUtils; +import org.opendaylight.yangtools.yang.data.util.DataSchemaContextNode; +import org.opendaylight.yangtools.yang.data.util.NormalizedNodeSchemaUtils; 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.DataSchemaNode; +import org.opendaylight.yangtools.yang.model.api.DocumentedNode.WithStatus; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -final class ChoiceNodeCodecContext extends DataContainerCodecContext { +/** + * This is a bit tricky. DataObject addressing does not take into account choice/case statements, and hence given: + * + *
+ *   
+ *     container foo {
+ *       choice bar {
+ *         leaf baz;
+ *       }
+ *     }
+ *   
+ * 
+ * we will see {@code Baz extends ChildOf}, which is how the users would address it in InstanceIdentifier terms. + * The implicit assumption being made is that {@code Baz} identifies a particular instantiation and hence provides + * unambiguous reference to an effective schema statement. + * + *

+ * Unfortunately this does not quite work with groupings, as their generation has changed: we do not have interfaces + * that would capture grouping instantiations, hence we do not have a proper addressing point and users need to specify + * the interfaces generated in the grouping's definition. These can be very much ambiguous, as a {@code grouping} can be + * used in multiple modules independently within an {@code augment} targeting {@code choice}, as each instantiation is + * guaranteed to have a unique namespace -- but we do not have the appropriate instantiations of those nodes. + * + *

+ * To address this issue we have a two-class lookup mechanism, which relies on the interface generated for the + * {@code case} statement to act as the namespace anchor bridging the nodes inside the grouping to the namespace in + * which they are instantiated. + * + *

+ * Furthermore downstream code relies on historical mechanics, which would guess what the instantiation is, silently + * assuming the ambiguity is theoretical and does not occur in practice. + * + *

+ * This leads to three classes of addressing, in order descending performance requirements. + *

    + *
  • Direct DataObject, where we name an exact child
  • + *
  • Case DataObject + Grouping DataObject
  • + *
  • Grouping DataObject, which is ambiguous
  • + *
+ * + * {@link #byCaseChildClass} supports direct DataObject mapping and contains only unambiguous children, while + * {@link #byClass} supports indirect mapping and contains {@code case} sub-statements. + * + * {@link #ambiguousByCaseChildClass} contains ambiguous mappings, for which we end up issuing warnings. We track each + * ambiguous reference and issue warn once when they are encountered -- tracking warning information in + * {@link #ambiguousByCaseChildWarnings}. + */ +final class ChoiceNodeCodecContext extends DataContainerCodecContext { private static final Logger LOG = LoggerFactory.getLogger(ChoiceNodeCodecContext.class); - private final ImmutableMap> byYangCaseChild; - /* - * This is a bit tricky. DataObject addressing does not take into account choice/case statements, and hence - * given: - * - * container foo { - * choice bar { - * leaf baz; - * } - * } - * - * we will see {@code Baz extends ChildOf}, which is how the users would address it in InstanceIdentifier - * terms. The implicit assumption being made is that {@code Baz} identifies a particular instantiation and hence - * provides unambiguous reference to an effective schema statement. - * - *

- * Unfortunately this does not quite work with groupings, as their generation has changed: we do not have - * interfaces that would capture grouping instantiations, hence we do not have a proper addressing point and - * users need to specify the interfaces generated in the grouping's definition. These can be very much - * ambiguous, as a {@code grouping} can be used in multiple modules independently within an {@code augment} - * targeting {@code choice}, as each instantiation is guaranteed to have a unique namespace -- but we do not - * have the appropriate instantiations of those nodes. - * - *

- * To address this issue we have a two-class lookup mechanism, which relies on the interface generated for - * the {@code case} statement to act as the namespace anchor bridging the nodes inside the grouping to the - * namespace in which they are instantiated. - * - *

- * Furthermore downstream code relies on historical mechanics, which would guess what the instantiation is, - * silently assuming the ambiguity is theoretical and does not occur in practice. - * - *

- * This leads to three classes of addressing, in order descending performance requirements. - *

    - *
  • Direct DataObject, where we name an exact child
  • - *
  • Case DataObject + Grouping DataObject
  • - *
  • Grouping DataObject, which is ambiguous
  • - *
- * - * {@code byCaseChildClass} supports direct DataObject mapping and contains only unambiguous children, while - * {@code byClass} supports indirect mapping and contains {@code case} sub-statements. - * - * ambiguousByCaseChildClass contains ambiguous mappings, for which we end up issuing warnings. We track each - * ambiguous reference and issue warnings when they are encountered. - */ - private final ImmutableMap, DataContainerCodecPrototype> byClass; - private final ImmutableMap, DataContainerCodecPrototype> byCaseChildClass; + private final ImmutableMap> byYangCaseChild; private final ImmutableListMultimap, DataContainerCodecPrototype> ambiguousByCaseChildClass; + private final ImmutableMap, DataContainerCodecPrototype> byCaseChildClass; + private final ImmutableMap, DataContainerCodecPrototype> byClass; private final Set> ambiguousByCaseChildWarnings; - ChoiceNodeCodecContext(final DataContainerCodecPrototype prototype) { + ChoiceNodeCodecContext(final DataContainerCodecPrototype prototype) { super(prototype); final Map> byYangCaseChildBuilder = - new HashMap<>(); + new HashMap<>(); final Map, DataContainerCodecPrototype> byClassBuilder = new HashMap<>(); - final SetMultimap, DataContainerCodecPrototype> childToCase = SetMultimapBuilder.hashKeys() - .hashSetValues().build(); + final SetMultimap, DataContainerCodecPrototype> childToCase = + SetMultimapBuilder.hashKeys().hashSetValues().build(); final Set> potentialSubstitutions = new HashSet<>(); // Walks all cases for supplied choice in current runtime context - for (final Class caze : factory().getRuntimeContext().getCases(getBindingClass())) { + // FIXME: 9.0.0: factory short-circuits to prototype, just as getBindingClass() does + for (final Class caze : loadCaseClasses()) { // We try to load case using exact match thus name // and original schema must equals - final DataContainerCodecPrototype cazeDef = loadCase(caze); + final DataContainerCodecPrototype cazeDef = loadCase(caze); // If we have case definition, this case is instantiated // at current location and thus is valid in context of parent choice if (cazeDef != null) { @@ -123,16 +131,21 @@ final class ChoiceNodeCodecContext extends DataContainerCo childToCase.put(cazeChild, cazeDef); } // Updates collection of YANG instance identifier to case - for (final DataSchemaNode cazeChild : cazeDef.getSchema().getChildNodes()) { - if (cazeChild.isAugmenting()) { - final AugmentationSchemaNode augment = SchemaUtils.findCorrespondingAugment(cazeDef.getSchema(), - cazeChild); - if (augment != null) { - byYangCaseChildBuilder.put(SchemaUtils.getNodeIdentifierForAugmentation(augment), cazeDef); - continue; + for (var stmt : cazeDef.getType().statement().effectiveSubstatements()) { + if (stmt instanceof DataSchemaNode) { + final DataSchemaNode cazeChild = (DataSchemaNode) stmt; + if (cazeChild.isAugmenting()) { + final AugmentationSchemaNode augment = NormalizedNodeSchemaUtils.findCorrespondingAugment( + // FIXME: bad cast + (DataSchemaNode) cazeDef.getType().statement(), cazeChild); + if (augment != null) { + byYangCaseChildBuilder.put(DataSchemaContextNode.augmentationIdentifierFrom(augment), + cazeDef); + continue; + } } + byYangCaseChildBuilder.put(NodeIdentifier.create(cazeChild.getQName()), cazeDef); } - byYangCaseChildBuilder.put(NodeIdentifier.create(cazeChild.getQName()), cazeDef); } } else { /* @@ -186,6 +199,28 @@ final class ChoiceNodeCodecContext extends DataContainerCo byClass = ImmutableMap.copyOf(byClassBuilder); } + private List> loadCaseClasses() { + final var context = factory().getRuntimeContext(); + final var type = getType(); + + return Stream.concat(type.validCaseChildren().stream(), type.additionalCaseChildren().stream()) + .map(caseChild -> { + final var caseName = caseChild.getIdentifier(); + try { + return context.loadClass(caseName); + } catch (ClassNotFoundException e) { + throw new IllegalStateException("Failed to load class for " + caseName, e); + } + }) + .collect(Collectors.toUnmodifiableList()); + } + + @Override + public WithStatus getSchema() { + // FIXME: Bad cast, we should be returning an EffectiveStatement perhaps? + return (WithStatus) getType().statement(); + } + @SuppressWarnings("unchecked") @Override public DataContainerCodecContext streamChild(final Class childClass) { @@ -209,19 +244,18 @@ final class ChoiceNodeCodecContext extends DataContainerCo return Iterables.concat(byCaseChildClass.keySet(), ambiguousByCaseChildClass.keySet()); } - protected DataContainerCodecPrototype loadCase(final Class childClass) { - final Optional childSchema = factory().getRuntimeContext().getCaseSchemaDefinition(getSchema(), - childClass); - if (childSchema.isPresent()) { - return DataContainerCodecPrototype.from(childClass, childSchema.get(), factory()); + protected DataContainerCodecPrototype loadCase(final Class childClass) { + final var child = getType().bindingCaseChild(JavaTypeName.create(childClass)); + if (child == null) { + LOG.debug("Supplied class {} is not valid case in schema {}", childClass, getSchema()); + return null; } - LOG.debug("Supplied class %s is not valid case in schema %s", childClass, getSchema()); - return null; + return DataContainerCodecPrototype.from(childClass, child, factory()); } @Override - public NodeCodecContext yangPathArgumentChild(final YangInstanceIdentifier.PathArgument arg) { + public NodeCodecContext yangPathArgumentChild(final YangInstanceIdentifier.PathArgument arg) { final DataContainerCodecPrototype cazeProto; if (arg instanceof YangInstanceIdentifier.NodeIdentifierWithPredicates) { cazeProto = byYangCaseChild.get(new NodeIdentifier(arg.getNodeType())); @@ -229,20 +263,20 @@ final class ChoiceNodeCodecContext extends DataContainerCo cazeProto = byYangCaseChild.get(arg); } - return childNonNull(cazeProto, arg,"Argument %s is not valid child of %s", arg, getSchema()).get() + return childNonNull(cazeProto, arg, "Argument %s is not valid child of %s", arg, getSchema()).get() .yangPathArgumentChild(arg); } - @SuppressWarnings("unchecked") @Override - @Nullable - public D deserialize(final NormalizedNode data) { - Preconditions.checkArgument(data instanceof ChoiceNode); - final NormalizedNodeContainer> casted = - (NormalizedNodeContainer>) data; - final NormalizedNode first = Iterables.getFirst(casted.getValue(), null); + @SuppressWarnings("unchecked") + @SuppressFBWarnings(value = "NP_NONNULL_RETURN_VIOLATION", justification = "See FIXME below") + public D deserialize(final NormalizedNode data) { + checkArgument(data instanceof ChoiceNode); + final ChoiceNode casted = (ChoiceNode) data; + final NormalizedNode first = Iterables.getFirst(casted.body(), null); if (first == null) { + // FIXME: this needs to be sorted out return null; } final DataContainerCodecPrototype caze = byYangCaseChild.get(first.getIdentifier()); @@ -250,13 +284,13 @@ final class ChoiceNodeCodecContext extends DataContainerCo } @Override - protected Object deserializeObject(final NormalizedNode normalizedNode) { + protected Object deserializeObject(final NormalizedNode normalizedNode) { return deserialize(normalizedNode); } @Override public PathArgument deserializePathArgument(final YangInstanceIdentifier.PathArgument arg) { - Preconditions.checkArgument(getDomPathArgument().equals(arg)); + checkArgument(getDomPathArgument().equals(arg)); return null; } @@ -266,7 +300,7 @@ final class ChoiceNodeCodecContext extends DataContainerCo return getDomPathArgument(); } - DataContainerCodecContext getCaseByChildClass(final @Nonnull Class type) { + DataContainerCodecContext getCaseByChildClass(final @NonNull Class type) { DataContainerCodecPrototype result = byCaseChildClass.get(type); if (result == null) { // We have not found an unambiguous result, try ambiguous ones