Rework AugmentRuntimeType and Choice/Case linkage
[mdsal.git] / binding / mdsal-binding-generator / src / main / java / org / opendaylight / mdsal / binding / generator / impl / reactor / AbstractAugmentGenerator.java
1 /*
2  * Copyright (c) 2021 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 com.google.common.collect.ImmutableList;
15 import java.util.Comparator;
16 import java.util.Iterator;
17 import java.util.List;
18 import java.util.Optional;
19 import org.eclipse.jdt.annotation.NonNull;
20 import org.eclipse.jdt.annotation.Nullable;
21 import org.opendaylight.mdsal.binding.generator.impl.reactor.CollisionDomain.Member;
22 import org.opendaylight.mdsal.binding.generator.impl.rt.DefaultAugmentRuntimeType;
23 import org.opendaylight.mdsal.binding.model.api.GeneratedType;
24 import org.opendaylight.mdsal.binding.model.api.type.builder.GeneratedTypeBuilder;
25 import org.opendaylight.mdsal.binding.model.api.type.builder.GeneratedTypeBuilderBase;
26 import org.opendaylight.mdsal.binding.model.ri.BindingTypes;
27 import org.opendaylight.mdsal.binding.runtime.api.AugmentRuntimeType;
28 import org.opendaylight.mdsal.binding.runtime.api.CaseRuntimeType;
29 import org.opendaylight.mdsal.binding.runtime.api.RuntimeType;
30 import org.opendaylight.yangtools.odlext.model.api.AugmentIdentifierEffectiveStatement;
31 import org.opendaylight.yangtools.yang.common.AbstractQName;
32 import org.opendaylight.yangtools.yang.common.QName;
33 import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
34 import org.opendaylight.yangtools.yang.model.api.stmt.AugmentEffectiveStatement;
35 import org.opendaylight.yangtools.yang.model.api.stmt.ChoiceEffectiveStatement;
36 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaTreeAwareEffectiveStatement;
37 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaTreeAwareEffectiveStatement.SchemaTreeNamespace;
38 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaTreeEffectiveStatement;
39 import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
40
41 /**
42  * A generator corresponding to a {@code augment} statement. This class is further specialized for the two distinct uses
43  * an augment is used.
44  */
45 abstract class AbstractAugmentGenerator
46         extends AbstractCompositeGenerator<AugmentEffectiveStatement, AugmentRuntimeType> {
47     /**
48      * Comparator comparing target path length. This is useful for quickly determining order the order in which two
49      * (or more) {@link AbstractAugmentGenerator}s need to be evaluated. This is necessary when augments are layered on
50      * top of each other:
51      *
52      * <p>
53      * <pre>
54      *   <code>
55      *     container foo;
56      *
57      *     augment /foo/bar {
58      *       container baz;
59      *     }
60      *
61      *     augment /foo {
62      *       container bar;
63      *     }
64      *   </code>
65      * </pre>
66      *
67      * <p>
68      * Evaluating these in the order of increasing argument component count solves this without having to perform a full
69      * analysis.
70      *
71      * <p>
72      * Another problem we are solving here is augmentation target stability, as the declared order in YANG text may
73      * change, which does not really change the semantics. If we only relied on length of argument, such a move would
74      * result in changing the results of {@link #createMember(CollisionDomain)} and make upgrades rather unpredictable.
75      * We solve this by using {@link QName#compareTo(QName)} to determine order.
76      */
77     static final Comparator<? super AbstractAugmentGenerator> COMPARATOR = (o1, o2) -> {
78         final Iterator<QName> thisIt = o1.statement().argument().getNodeIdentifiers().iterator();
79         final Iterator<QName> otherIt = o2.statement().argument().getNodeIdentifiers().iterator();
80
81         while (thisIt.hasNext()) {
82             if (!otherIt.hasNext()) {
83                 return 1;
84             }
85
86             final int comp = thisIt.next().compareTo(otherIt.next());
87             if (comp != 0) {
88                 return comp;
89             }
90         }
91
92         return otherIt.hasNext() ? -1 : 0;
93     };
94
95     private SchemaTreeAwareEffectiveStatement<?, ?> targetStatement;
96     private AbstractCompositeGenerator<?, ?> targetGen;
97     private Optional<AugmentRuntimeType> internalRuntimeType;
98
99     AbstractAugmentGenerator(final AugmentEffectiveStatement statement, final AbstractCompositeGenerator<?, ?> parent) {
100         super(statement, parent);
101     }
102
103     @Override
104     final void pushToInference(final SchemaInferenceStack dataTree) {
105         dataTree.enterSchemaTree(statement().argument());
106     }
107
108     @Override
109     final AbstractQName localName() {
110         throw new UnsupportedOperationException();
111     }
112
113     @Override
114     ClassPlacement classPlacement() {
115         // if the target is a choice we are NOT creating an explicit augmentation, but we still need a phantom to
116         // reserve the appropriate package name
117         final AbstractCompositeGenerator<?, ?> target = targetGenerator();
118         return target instanceof ChoiceGenerator ? ClassPlacement.PHANTOM : super.classPlacement();
119     }
120
121     @Override
122     final Member createMember(final CollisionDomain domain) {
123         final AbstractQName explicitIdentifier = statement()
124             .findFirstEffectiveSubstatementArgument(AugmentIdentifierEffectiveStatement.class).orElse(null);
125         if (explicitIdentifier != null) {
126             return domain.addPrimary(this, new CamelCaseNamingStrategy(StatementNamespace.DEFAULT, explicitIdentifier));
127         }
128
129         final Member target = targetGenerator().getMember();
130         int offset = 1;
131         for (Generator gen : getParent()) {
132             if (gen == this) {
133                 break;
134             }
135             if (gen instanceof AbstractAugmentGenerator
136                 && target.equalRoot(((AbstractAugmentGenerator) gen).targetGenerator().getMember())) {
137                 offset++;
138             }
139         }
140
141         return domain.addSecondary(this, target, String.valueOf(offset), statement().argument());
142     }
143
144     @Override
145     final GeneratedType createTypeImpl(final TypeBuilderFactory builderFactory) {
146         final GeneratedTypeBuilder builder = builderFactory.newGeneratedTypeBuilder(typeName());
147
148         builder.addImplementsType(BindingTypes.augmentation(targetGenerator().getGeneratedType(builderFactory)));
149         addUsesInterfaces(builder, builderFactory);
150         addConcreteInterfaceMethods(builder);
151
152         addGetterMethods(builder, builderFactory);
153         annotateDeprecatedIfNecessary(builder);
154
155         return builder.build();
156     }
157
158     @NonNull List<CaseRuntimeType> augmentedCasesIn(final ChildLookup lookup, final ChoiceEffectiveStatement stmt) {
159         final var target = verifyNotNull(targetStatement);
160         if (!stmt.equals(target)) {
161             return List.of();
162         }
163
164         final var result = createBuilder(effectiveStatement(statement(), target))
165             .fillTypes(ChildLookup.of(target), this)
166             .getCaseChilden();
167         internalRuntimeType = Optional.empty();
168         return result;
169     }
170
171     @Nullable AugmentRuntimeType runtimeTypeIn(final ChildLookup lookup, final EffectiveStatement<?, ?> stmt) {
172         final var target = verifyNotNull(targetStatement);
173         if (!stmt.equals(target)) {
174             return null;
175         }
176         if (internalRuntimeType != null) {
177             return internalRuntimeType.orElseThrow();
178         }
179
180         final var result = verifyNotNull(createInternalRuntimeType(ChildLookup.of(target),
181             effectiveStatement(statement(), target)));
182         internalRuntimeType = Optional.of(result);
183         return result;
184     }
185
186     private static @NonNull AugmentEffectiveStatement effectiveStatement(final AugmentEffectiveStatement augment,
187             final SchemaTreeAwareEffectiveStatement<?, ?> target) {
188         final var stmts = augment.effectiveSubstatements();
189         final var builder = ImmutableList.<EffectiveStatement<?, ?>>builderWithExpectedSize(stmts.size());
190         for (var child : stmts) {
191             if (child instanceof SchemaTreeEffectiveStatement) {
192                 final var qname = ((SchemaTreeEffectiveStatement<?>) child).getIdentifier();
193                 // FIXME: orElseThrow()?
194                 target.get(SchemaTreeNamespace.class, qname).ifPresent(builder::add);
195             } else {
196                 builder.add(child);
197             }
198         }
199         return new TargetAugmentEffectiveStatement(augment, target, builder.build());
200     }
201
202     final @Nullable AugmentRuntimeType getInternalRuntimeType() {
203         return verifyNotNull(internalRuntimeType, "Internal runtime not resolved in %s", this).orElse(null);
204     }
205
206     @Override
207     final void addAsGetterMethod(final GeneratedTypeBuilderBase<?> builder, final TypeBuilderFactory builderFactory) {
208         // Augments are never added as getters, as they are handled via Augmentable mechanics
209     }
210
211     @Override
212     CompositeRuntimeTypeBuilder<AugmentEffectiveStatement, AugmentRuntimeType> createBuilder(
213             final AugmentEffectiveStatement statement) {
214         return new CompositeRuntimeTypeBuilder<>(statement) {
215             @Override
216             AugmentRuntimeType build(final GeneratedType type, final AugmentEffectiveStatement statement,
217                     final List<RuntimeType> children, final List<AugmentRuntimeType> augments) {
218                 // 'augment' cannot be targeted by augment
219                 verify(augments.isEmpty(), "Unexpected augments %s", augments);
220                 return new DefaultAugmentRuntimeType(type, statement, children);
221             }
222         };
223     }
224
225     final void setTargetGenerator(final AbstractCompositeGenerator<?, ?> targetGenerator) {
226         verify(targetGen == null, "Attempted to relink %s, already have target %s", this, targetGen);
227         targetGen = requireNonNull(targetGenerator);
228     }
229
230     final @NonNull AbstractCompositeGenerator<?, ?> targetGenerator() {
231         return verifyNotNull(targetGen, "No target for %s", this);
232     }
233
234     final void setTargetStatement(final EffectiveStatement<?, ?> targetStatement) {
235         verify(targetStatement instanceof SchemaTreeAwareEffectiveStatement, "Unexpected target statement %s",
236             targetStatement);
237         this.targetStatement = (SchemaTreeAwareEffectiveStatement<?, ?>) targetStatement;
238     }
239 }