import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
+import java.util.Set;
import java.util.stream.Collectors;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
groupings = List.copyOf(tmp);
}
+ // Iterate over a some generators recursively, linking them to the GroupingGenerators they use. GroupingGenerators
+ // are skipped and added to unprocessedGroupings for later processing.
+ final void linkUsedGroupings(final Set<GroupingGenerator> skippedChildren) {
+ // Link to used groupings IFF we have a corresponding generated Java class
+ switch (classPlacement()) {
+ case NONE:
+ case PHANTOM:
+ break;
+ default:
+ for (var grouping : groupings()) {
+ grouping.addUser(this);
+ }
+ }
+
+ for (var child : childGenerators) {
+ if (child instanceof GroupingGenerator grouping) {
+ skippedChildren.add(grouping);
+ } else if (child instanceof AbstractCompositeGenerator<?, ?> composite) {
+ composite.linkUsedGroupings(skippedChildren);
+ }
+ }
+ }
+
final void startUsesAugmentLinkage(final List<AugmentRequirement> requirements) {
for (var child : childGenerators) {
if (child instanceof UsesAugmentGenerator uses) {
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
*/
linkDependencies(children);
- // Step five: resolve all 'type leafref' and 'type identityref' statements, so they point to their
- // corresponding Java type representation.
+ // Step 5: resolve grouping usage, so that each GroupingGenerator has links to their instantiation sites and
+ // any unused
+ resolveGroupingUsers();
+ freezeGroupingUsers(children);
+
+ // Step 6: resolve all 'type leafref' and 'type identityref' statements, so they point to their corresponding
+ // Java type representation.
bindTypeDefinition(children);
- // Step six: walk all composite generators and link ChildOf/ChoiceIn relationships with parents. We have taken
- // care of this step during tree construction, hence this now a no-op.
+ // Step 7: walk all composite generators and link ChildOf/ChoiceIn relationships with parents. We have taken
+ // care of this step during tree construction, hence this now a no-op.
/*
- * Step seven: assign java packages and JavaTypeNames
+ * Step 8: assign java packages and JavaTypeNames
*
* This is a really tricky part, as we have large number of factors to consider:
* - we are mapping grouping, typedef, identity and schema tree namespaces into Fully Qualified Class Names,
}
} while (haveUnresolved);
- // Step eight: generate actual Types
- //
- // We have now properly cross-linked all generators and have assigned their naming roots, so from this point
- // it looks as though we are performing a simple recursive execution. In reality, though, the actual path taken
- // through generators is dictated by us as well as generator linkage.
+ /*
+ * Step 9: generate actual Types
+ *
+ * We have now properly cross-linked all generators and have assigned their naming roots, so from this point
+ * it looks as though we are performing a simple recursive execution. In reality, though, the actual path taken
+ * through generators is dictated by us as well as generator linkage.
+ */
for (var module : children) {
module.ensureType(builderFactory);
}
stack.pop();
}
}
+
+ private void resolveGroupingUsers() {
+ // Primary pass on modules, collecting all groupings which were left unprocessed
+ // TODO: use a plain List
+ final var remaining = new HashSet<GroupingGenerator>();
+ for (var module : children) {
+ module.linkUsedGroupings(remaining);
+ }
+ LOG.debug("Grouping pass 1 found {} groupings", remaining.size());
+
+ // Secondary passes: if any unprocessed groupings have been marked as used, process their children, potentially
+ // adding more work
+ int passes = 2;
+ int processed;
+ do {
+ // Do not process groupings again unless we make some progress
+ processed = 0;
+
+ final var found = new HashSet<GroupingGenerator>();
+ final var it = remaining.iterator();
+ while (it.hasNext()) {
+ final var next = it.next();
+ if (next.hasUser()) {
+ // Process this grouping and remember we need to iterate again, as groupings we have already visited
+ // may become used as a side-effect.
+ it.remove();
+ next.linkUsedGroupings(found);
+ processed++;
+ }
+ }
+
+ final var foundSize = found.size();
+ LOG.debug("Grouping pass {} processed {} and found {} grouping(s)", passes, processed, foundSize);
+ if (foundSize != 0) {
+ // we have some more groupings to process, shove them into the next iteration
+ remaining.addAll(found);
+ }
+
+ passes++;
+ } while (processed != 0);
+
+ LOG.debug("Grouping usage completed after {} pass(es) with unused {} grouping(s)", passes, remaining.size());
+ }
+
+ private static void freezeGroupingUsers(final Iterable<? extends Generator> parent) {
+ for (var child : parent) {
+ if (child instanceof AbstractCompositeGenerator<?, ?> composite) {
+ if (composite instanceof GroupingGenerator grouping) {
+ grouping.freezeUsers();
+ }
+ freezeGroupingUsers(composite);
+ }
+ }
+ }
}
*/
package org.opendaylight.mdsal.binding.generator.impl.reactor;
-import static com.google.common.base.Verify.verify;
-
+import com.google.common.base.VerifyException;
+import java.util.ArrayList;
import java.util.List;
+import java.util.stream.Collectors;
import org.opendaylight.mdsal.binding.generator.impl.rt.DefaultGroupingRuntimeType;
import org.opendaylight.mdsal.binding.model.api.GeneratedType;
-import org.opendaylight.mdsal.binding.model.api.type.builder.GeneratedTypeBuilder;
import org.opendaylight.mdsal.binding.model.api.type.builder.GeneratedTypeBuilderBase;
import org.opendaylight.mdsal.binding.model.ri.BindingTypes;
import org.opendaylight.mdsal.binding.runtime.api.AugmentRuntimeType;
* Generator corresponding to a {@code grouping} statement.
*/
final class GroupingGenerator extends AbstractCompositeGenerator<GroupingEffectiveStatement, GroupingRuntimeType> {
+ // Linkage towards concrete data tree instantiations of this grouping. This can contain two different kinds of
+ // generators:
+ // - GroupingGenerators which provide next step in the linkage
+ // - other composite generators, which are the actual instantiations
+ private List<AbstractCompositeGenerator<?, ?>> users;
+
GroupingGenerator(final GroupingEffectiveStatement statement, final AbstractCompositeGenerator<?, ?> parent) {
super(statement, parent);
}
+ void addUser(final AbstractCompositeGenerator<?, ?> user) {
+ if (users == null) {
+ // We are adding the first user: allocate a small set and notify the groupings we use that we are a user
+ users = new ArrayList<>();
+ for (var grouping : groupings()) {
+ grouping.addUser(this);
+ }
+ }
+ users.add(user);
+ }
+
+ boolean hasUser() {
+ return users != null;
+ }
+
+ void freezeUsers() {
+ users = users == null ? List.of() : users.stream().distinct().collect(Collectors.toUnmodifiableList());
+ }
+
@Override
StatementNamespace namespace() {
return StatementNamespace.GROUPING;
@Override
GeneratedType createTypeImpl(final TypeBuilderFactory builderFactory) {
- final GeneratedTypeBuilder builder = builderFactory.newGeneratedTypeBuilder(typeName());
+ final var builder = builderFactory.newGeneratedTypeBuilder(typeName());
builder.addImplementsType(BindingTypes.DATA_OBJECT);
narrowImplementedInterface(builder);
addUsesInterfaces(builder, builderFactory);
addGetterMethods(builder, builderFactory);
- final ModuleGenerator module = currentModule();
+ final var module = currentModule();
module.addQNameConstant(builder, statement().argument());
annotateDeprecatedIfNecessary(builder);
@Override
CompositeRuntimeTypeBuilder<GroupingEffectiveStatement, GroupingRuntimeType> createBuilder(
final GroupingEffectiveStatement statement) {
+ final var local = users;
+ if (local == null) {
+ throw new VerifyException(this + " has unresolved users");
+ }
+
+ final var vectors = local.stream()
+ .map(AbstractCompositeGenerator::getRuntimeType)
+ .distinct()
+ .collect(Collectors.toUnmodifiableList());
+
return new CompositeRuntimeTypeBuilder<>(statement) {
@Override
GroupingRuntimeType build(final GeneratedType type, final GroupingEffectiveStatement statement,
final List<RuntimeType> children, final List<AugmentRuntimeType> augments) {
// Groupings cannot be targeted by 'augment'
- verify(augments.isEmpty(), "Unexpected augments %s", augments);
- return new DefaultGroupingRuntimeType(type, statement, children);
+ if (augments.isEmpty()) {
+ return new DefaultGroupingRuntimeType(type, statement, children, vectors);
+ }
+ throw new VerifyException("Unexpected augments " + augments);
}
};
}
-
}
*/
package org.opendaylight.mdsal.binding.generator.impl.rt;
+import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
+import java.util.Objects;
+import org.eclipse.jdt.annotation.Nullable;
import org.opendaylight.mdsal.binding.model.api.GeneratedType;
+import org.opendaylight.mdsal.binding.runtime.api.CompositeRuntimeType;
import org.opendaylight.mdsal.binding.runtime.api.GroupingRuntimeType;
import org.opendaylight.mdsal.binding.runtime.api.RuntimeType;
import org.opendaylight.yangtools.yang.model.api.stmt.GroupingEffectiveStatement;
public final class DefaultGroupingRuntimeType extends AbstractCompositeRuntimeType<GroupingEffectiveStatement>
implements GroupingRuntimeType {
+ /**
+ * These are vectors towards concrete instantiations of this type -- i.e. the manifestation in the effective data
+ * tree. Each item in this list represents either:
+ * <ul>
+ * <li>a concrete instantiation, or<li>
+ * <li>another {@link GroupingRuntimeType}</li>
+ * </ul>
+ * We use these vectors to create {@link #instantiations()}.
+ */
+ private final @Nullable Object instantiationVectors;
+
public DefaultGroupingRuntimeType(final GeneratedType bindingType, final GroupingEffectiveStatement statement,
- final List<RuntimeType> children) {
+ final List<RuntimeType> children, final List<? extends CompositeRuntimeType> instantiationVectors) {
super(bindingType, statement, children);
+ this.instantiationVectors = switch (instantiationVectors.size()) {
+ case 0 -> null;
+ case 1 -> Objects.requireNonNull(instantiationVectors.get(0));
+ default -> instantiationVectors.stream().map(Objects::requireNonNull).toArray(CompositeRuntimeType[]::new);
+ };
+ }
+
+ @Override
+ public List<CompositeRuntimeType> directUsers() {
+ final var local = instantiationVectors;
+ if (local == null) {
+ return List.of();
+ } else if (local instanceof CompositeRuntimeType[] array) {
+ return Collections.unmodifiableList(Arrays.asList(array));
+ } else {
+ return List.of((CompositeRuntimeType) local);
+ }
}
}
--- /dev/null
+/*
+ * Copyright (c) 2023 PANTHEON.tech, s.r.o. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.mdsal.binding.generator.impl;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+
+import java.util.Arrays;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.junit.jupiter.api.Test;
+import org.opendaylight.mdsal.binding.model.api.JavaTypeName;
+import org.opendaylight.mdsal.binding.runtime.api.BindingRuntimeTypes;
+import org.opendaylight.mdsal.binding.runtime.api.GroupingRuntimeType;
+import org.opendaylight.mdsal.binding.runtime.api.RuntimeType;
+import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils;
+
+class Mdsal669Test {
+ private static final BindingRuntimeTypes RUNTIME_TYPES = new DefaultBindingRuntimeGenerator()
+ .generateTypeMapping(YangParserTestUtils.parseYangResource("/mdsal669.yang"));
+
+ @Test
+ void barIsUsed() {
+ assertInstances(JavaTypeName.create("org.opendaylight.yang.gen.v1.mdsal669.norev", "Bar"),
+ JavaTypeName.create("org.opendaylight.yang.gen.v1.mdsal669.norev", "Foo"),
+ JavaTypeName.create("org.opendaylight.yang.gen.v1.mdsal669.norev", "Target1"),
+ JavaTypeName.create("org.opendaylight.yang.gen.v1.mdsal669.norev.used.augmented", "ToBeAugmented1"),
+ JavaTypeName.create("org.opendaylight.yang.gen.v1.mdsal669.norev.used.augmented.indirect",
+ "ToBeAugmented1"));
+ }
+
+ @Test
+ void bazIsUsedByOneAndTwo() {
+ assertInstances(JavaTypeName.create("org.opendaylight.yang.gen.v1.mdsal669.norev", "Baz"),
+ JavaTypeName.create("org.opendaylight.yang.gen.v1.mdsal669.norev", "One"),
+ JavaTypeName.create("org.opendaylight.yang.gen.v1.mdsal669.norev", "Two"));
+ }
+
+ @Test
+ void unusedIsNotUsed() {
+ assertInstances(JavaTypeName.create("org.opendaylight.yang.gen.v1.mdsal669.norev", "Unused"));
+ }
+
+ @Test
+ void fooAsStringIsNotUsed() {
+ assertInstances(JavaTypeName.create("org.opendaylight.yang.gen.v1.mdsal669.norev", "FooAsString"));
+ }
+
+ @Test
+ void unusedBarIsNotUsed() {
+ assertInstances(JavaTypeName.create("org.opendaylight.yang.gen.v1.mdsal669.norev", "UnusedBar"));
+ }
+
+ @Test
+ void unusedAugmendIsNotUsed() {
+ assertInstances(JavaTypeName.create("org.opendaylight.yang.gen.v1.mdsal669.norev", "UnusedAugmented"));
+ }
+
+ @Test
+ void unusedIntermediateAugmentedIsNotUsed() {
+ assertInstances(
+ JavaTypeName.create("org.opendaylight.yang.gen.v1.mdsal669.norev", "UnusedIntermediateAugmentedUser"));
+ assertInstances(
+ JavaTypeName.create("org.opendaylight.yang.gen.v1.mdsal669.norev", "UnusedIntermediateAugmented"));
+ }
+
+ @Test
+ void usedAugmentedIndirectIsUsed() {
+ assertInstances(JavaTypeName.create("org.opendaylight.yang.gen.v1.mdsal669.norev", "UsedAugmentedIndirectGrp"),
+ JavaTypeName.create("org.opendaylight.yang.gen.v1.mdsal669.norev", "UsedAugmentedIndirectUser"));
+ assertInstances(JavaTypeName.create("org.opendaylight.yang.gen.v1.mdsal669.norev", "UsedAugmentedIndirect"),
+ JavaTypeName.create("org.opendaylight.yang.gen.v1.mdsal669.norev", "UsedAugmentedIndirectUser"));
+ }
+
+ @Test
+ void usedAugmentedIsUsed() {
+ assertInstances(JavaTypeName.create("org.opendaylight.yang.gen.v1.mdsal669.norev", "UsedAugmented"),
+ JavaTypeName.create("org.opendaylight.yang.gen.v1.mdsal669.norev", "UsedAugmentedUser"));
+ }
+
+ @Test
+ void toBeAugmentedIsUsed() {
+ assertInstances(JavaTypeName.create("org.opendaylight.yang.gen.v1.mdsal669.norev", "ToBeAugmented"),
+ JavaTypeName.create("org.opendaylight.yang.gen.v1.mdsal669.norev", "UsedAugmentedIndirectUser"),
+ JavaTypeName.create("org.opendaylight.yang.gen.v1.mdsal669.norev", "UsedAugmentedUser"));
+ }
+
+ private static void assertInstances(final JavaTypeName groupingTypeName, final JavaTypeName... instanceTypeNames) {
+ assertEquals(
+ Arrays.stream(instanceTypeNames).map(Mdsal669Test::assertType).collect(Collectors.toSet()),
+ Set.copyOf(assertGrouping(groupingTypeName).instantiations()));
+ }
+
+ private static GroupingRuntimeType assertGrouping(final JavaTypeName typeName) {
+ return assertInstanceOf(GroupingRuntimeType.class, assertType(typeName));
+ }
+
+ private static RuntimeType assertType(final JavaTypeName typeName) {
+ return RUNTIME_TYPES.findSchema(typeName).orElseThrow();
+ }
+}
--- /dev/null
+module mdsal669 {
+ namespace mdsal669;
+ prefix mdsal669;
+
+ grouping bar {
+ container bar {
+ leaf-list bar {
+ type leafref {
+ path ../../foo;
+ }
+ }
+ }
+ }
+
+ container foo {
+ leaf foo {
+ type instance-identifier;
+ }
+
+ uses bar;
+ }
+
+ grouping baz {
+ leaf baz {
+ type leafref {
+ path "../bar";
+ }
+ }
+ }
+
+ container one {
+ leaf bar {
+ type string;
+ }
+ uses baz;
+ }
+
+ container two {
+ leaf bar {
+ type uint16;
+ }
+ uses baz;
+ }
+
+ grouping unused {
+ leaf foo {
+ type leafref {
+ path "../bar";
+ }
+ }
+ }
+
+ // this grouping is used ...
+ grouping foo-as-string {
+ leaf foo {
+ type string;
+ }
+
+ uses bar;
+ }
+
+ // ... but this grouping is not ...
+ grouping unused-bar {
+ container qux {
+ // ... and hence this is not count as an instantiation
+ uses foo-as-string;
+ }
+ }
+
+ // Direct use via augment
+ container target;
+
+ augment /target {
+ leaf foo {
+ type uint32;
+ }
+
+ uses bar;
+ }
+
+ // Multiple use cases for uses/augment: this is the base grouping
+ grouping to-be-augmented {
+ container to-be-augmented;
+ }
+
+ // This grouping is not used
+ grouping unused-augmented {
+ uses to-be-augmented {
+ augment to-be-augmented {
+ leaf foo {
+ type boolean;
+ }
+
+ uses bar;
+ }
+ }
+ }
+
+ // This grouping is used only ...
+ grouping unused-intermediate-augmented {
+ uses to-be-augmented {
+ augment to-be-augmented {
+ leaf foo {
+ type uint64;
+ }
+
+ uses bar;
+ }
+ }
+ }
+
+ // ... by this grouping, which itself is not used
+ grouping unused-intermediate-augmented-user {
+ uses unused-intermediate-augmented;
+ }
+
+ // This grouping is used directly ...
+ grouping used-augmented {
+ uses to-be-augmented {
+ augment to-be-augmented {
+ leaf foo {
+ type uint8;
+ }
+
+ uses bar;
+ }
+ }
+ }
+
+ // ... by this container
+ container used-augmented-user {
+ uses used-augmented;
+ }
+
+ // ... this grouping is used ...
+ grouping used-augmented-indirect {
+ uses to-be-augmented {
+ augment to-be-augmented {
+ leaf foo {
+ type empty;
+ }
+
+ uses bar;
+ }
+ }
+ }
+
+ // ... by another grouping, which itself is used ...
+ grouping used-augmented-indirect-grp {
+ uses used-augmented-indirect;
+ }
+
+ // ... by this container
+ container used-augmented-indirect-user {
+ uses used-augmented-indirect-grp;
+ }
+}
*/
package org.opendaylight.mdsal.binding.runtime.api;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.eclipse.jdt.annotation.NonNull;
import org.opendaylight.yangtools.yang.model.api.stmt.GroupingEffectiveStatement;
/**
public interface GroupingRuntimeType extends CompositeRuntimeType {
@Override
GroupingEffectiveStatement statement();
+
+ /**
+ * Return the set of all concrete data tree instantiations of this {@code grouping}. This is necessary to completely
+ * resolve type information for {@code leafref}s.
+ *
+ * <p>
+ * As an example, consider {@link GroupingRuntimeType} of {@code grouping baz} and it's instantiations roots
+ * {@code container one} and {@code container two} define in these three models:
+ * <pre>{@code
+ * module baz {
+ * namespace baz;
+ * prefix baz;
+ *
+ * grouping baz {
+ * leaf baz {
+ * type leafref {
+ * path "../bar";
+ * }
+ * }
+ * }
+ * }
+ *
+ * module one {
+ * namespace one;
+ * prefix one;
+ * import baz { prefix baz; }
+ *
+ * container one {
+ * leaf bar {
+ * type string;
+ * }
+ * uses baz:baz;
+ * }
+ * }
+ *
+ * module two {
+ * namespace two;
+ * prefix two;
+ * import baz { prefix baz; }
+ *
+ * container two {
+ * leaf bar {
+ * type uint16;
+ * }
+ * uses baz:baz;
+ * }
+ * }
+ * }</pre>
+ *
+ * <p>
+ * Since these are separate modules, each of them can be part of its own compilation unit and therefore
+ * {@code grouping baz} compile-time analysis cannot definitely determine the return type of {@code getBaz()} and
+ * must fall back to {@code Object}.
+ *
+ * <p>
+ * At run-time, though, we have a closed world, and therefore we can provide accurate information about
+ * instantiation sites: this method will return the {@link CompositeRuntimeType}s for {@code one} and {@code two}.
+ * We can then use this information to know that {@code getBaz()} can either be a {@code String} or an
+ * {@code Uint32} and which type is appropriate at a particular point in YANG data tree.
+ *
+ * @return The set instantiated {@link CompositeRuntimeType}s which use this grouping
+ */
+ default @NonNull List<CompositeRuntimeType> instantiations() {
+ final var users = directUsers();
+ return switch (users.size()) {
+ case 0 -> List.of();
+ case 1 -> {
+ final var user = users.get(0);
+ yield user instanceof GroupingRuntimeType grouping ? grouping.instantiations() : List.of(user);
+ }
+ default -> users.stream()
+ .flatMap(user -> user instanceof GroupingRuntimeType grouping ? grouping.instantiations().stream()
+ : Stream.of(user))
+ .distinct()
+ .collect(Collectors.toUnmodifiableList());
+ };
+ }
+
+ /**
+ * Support method for {@link #instantiations()}. This method's return, unlike {@link #instantiations()} can contain
+ * other {@link GroupingRuntimeType}s.
+ *
+ * @return Direct users of this grouping
+ */
+ @NonNull List<CompositeRuntimeType> directUsers();
}