Fix uses/augment linkage
[mdsal.git] / binding / mdsal-binding-generator / src / main / java / org / opendaylight / mdsal / binding / generator / impl / reactor / AugmentRequirement.java
1 /*
2  * Copyright (c) 2022 PANTHEON.tech, s.r.o. 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.generator.impl.reactor;
9
10 import static com.google.common.base.Verify.verify;
11 import static com.google.common.base.Verify.verifyNotNull;
12 import static java.util.Objects.requireNonNull;
13
14 import java.util.HashSet;
15 import java.util.Iterator;
16 import java.util.Set;
17 import org.eclipse.jdt.annotation.NonNull;
18 import org.opendaylight.yangtools.concepts.Mutable;
19 import org.opendaylight.yangtools.yang.common.QName;
20 import org.opendaylight.yangtools.yang.common.QNameModule;
21
22 /**
23  * Class tracking state of resolution of an {@code augment} statement's target generator.
24  *
25  * <p>
26  * This is not quite straightforward. 'path' works on top of schema tree, which is instantiated view. Since we
27  * do not generate duplicate instantiations along 'uses' path, findSchemaTreeGenerator() would satisfy our
28  * request by returning a child of the source 'grouping'.
29  *
30  * <p>
31  * When that happens, our subsequent lookups need to adjust the namespace being looked up to the grouping's
32  * namespace... except for the case when the step is actually an augmentation, in which case we must not make
33  * that adjustment.
34  *
35  * <p>
36  * Hence we deal with this lookup recursively, dropping namespace hints when we cross into groupings. Note we
37  * take an initial hint -- which UsesAugmentGenerator provides, but ModuleAugmentGenerator does not -- and that
38  * accounts for the difference.
39  */
40 final class AugmentRequirement implements Mutable {
41     private final @NonNull Set<QNameModule> squashNamespaces = new HashSet<>(4);
42     private final @NonNull AbstractAugmentGenerator augment;
43     private final @NonNull Iterator<QName> remaining;
44
45     private @NonNull AbstractCompositeGenerator<?> target;
46     private QNameModule localNamespace;
47     private QName qname;
48
49     private AugmentRequirement(final AbstractAugmentGenerator augment, final AbstractCompositeGenerator<?> target) {
50         this.augment = requireNonNull(augment);
51         this.target = requireNonNull(target);
52         remaining = augment.statement().argument().getNodeIdentifiers().iterator();
53         qname = remaining.next();
54     }
55
56     AugmentRequirement(final ModuleAugmentGenerator augment, final ModuleGenerator module) {
57         this((AbstractAugmentGenerator) augment, module);
58     }
59
60     AugmentRequirement(final UsesAugmentGenerator augment, final GroupingGenerator grouping) {
61         this((AbstractAugmentGenerator) augment, grouping);
62         // Starting in a grouping: squash namespace references to the grouping's namespace
63         localNamespace = grouping.getQName().getModule();
64         squashNamespaces.add(qname.getModule());
65     }
66
67     @NonNull LinkageProgress resolve() {
68         return qname == null ? resolveAsTarget() : resolveAsChild();
69     }
70
71     private @NonNull LinkageProgress resolveAsTarget() {
72         // Resolved requirement, if we also have original we end resolution here and now
73         final var original = target.tryOriginal();
74         if (original != null) {
75             augment.setTargetGenerator(original);
76             original.addAugment(augment);
77             return LinkageProgress.DONE;
78         }
79         return LinkageProgress.NONE;
80     }
81
82     private @NonNull LinkageProgress resolveAsChild() {
83         // First try local statements without adjustment
84         var gen = target.findLocalSchemaTreeGenerator(qname);
85         if (gen != null) {
86             return progressTo(gen);
87         }
88
89         // Second try local augments, as those are guaranteed to match namespace exactly
90         final var aug = target.findAugmentForGenerator(qname);
91         if (aug != null) {
92             return moveTo(aug);
93         }
94
95         // Third try local groupings, as those perform their own adjustment
96         final var grp = target.findGroupingForGenerator(qname);
97         if (grp != null) {
98             squashNamespaces.add(qname.getModule());
99             localNamespace = grp.getQName().getModule();
100             return moveTo(grp);
101         }
102
103         // Lastly try local statements adjusted with namespace, if applicable
104         gen = target.findLocalSchemaTreeGenerator(squashNamespaces.contains(qname.getModule())
105             ? qname.bindTo(verifyNotNull(localNamespace)) : qname);
106         if (gen != null) {
107             return progressTo(gen);
108         }
109         return LinkageProgress.NONE;
110     }
111
112     private @NonNull LinkageProgress moveTo(final @NonNull AbstractCompositeGenerator<?> newTarget) {
113         target = newTarget;
114         return tryProgress();
115     }
116
117     private @NonNull LinkageProgress progressTo(final @NonNull AbstractExplicitGenerator<?> newTarget) {
118         verify(newTarget instanceof AbstractCompositeGenerator, "Unexpected generator %s", newTarget);
119         target = (AbstractCompositeGenerator<?>) newTarget;
120         qname = remaining.hasNext() ? remaining.next() : null;
121         return tryProgress();
122     }
123
124     private @NonNull LinkageProgress tryProgress() {
125         final var progress = resolve();
126         return progress != LinkageProgress.NONE ? progress : LinkageProgress.SOME;
127     }
128 }