Modernize GeneratorReactor
[mdsal.git] / binding / mdsal-binding-generator / src / main / java / org / opendaylight / mdsal / binding / generator / impl / reactor / GeneratorReactor.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.base.Stopwatch;
15 import com.google.common.base.VerifyException;
16 import com.google.common.collect.Maps;
17 import java.util.ArrayDeque;
18 import java.util.ArrayList;
19 import java.util.Deque;
20 import java.util.List;
21 import java.util.Map;
22 import java.util.function.Function;
23 import java.util.stream.Collectors;
24 import org.eclipse.jdt.annotation.NonNull;
25 import org.eclipse.jdt.annotation.Nullable;
26 import org.opendaylight.mdsal.binding.model.api.GeneratedType;
27 import org.opendaylight.mdsal.binding.model.api.JavaTypeName;
28 import org.opendaylight.mdsal.binding.model.api.Type;
29 import org.opendaylight.yangtools.concepts.Mutable;
30 import org.opendaylight.yangtools.yang.binding.ChildOf;
31 import org.opendaylight.yangtools.yang.binding.ChoiceIn;
32 import org.opendaylight.yangtools.yang.common.QName;
33 import org.opendaylight.yangtools.yang.common.QNameModule;
34 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
35 import org.opendaylight.yangtools.yang.model.api.Module;
36 import org.opendaylight.yangtools.yang.model.api.PathExpression;
37 import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
38 import org.opendaylight.yangtools.yang.model.ri.type.TypeBuilder;
39 import org.opendaylight.yangtools.yang.model.spi.ModuleDependencySort;
40 import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
41 import org.slf4j.Logger;
42 import org.slf4j.LoggerFactory;
43
44 /**
45  * A multi-stage reactor for generating {@link GeneratedType} instances from an {@link EffectiveModelContext}.
46  *
47  * <p>
48  * The reason for multi-stage processing is that the problem ahead of us involves:
49  * <ul>
50  *   <li>mapping {@code typedef} and restricted {@code type} statements onto Java classes</li>
51  *   <li>mapping a number of schema tree nodes into Java interfaces with properties</li>
52  *   <li>taking advantage of Java composition to provide {@code grouping} mobility</li>
53  * </ul>
54  */
55 public final class GeneratorReactor extends GeneratorContext implements Mutable {
56     private enum State {
57         INITIALIZED,
58         EXECUTING,
59         FINISHED
60     }
61
62     private static final Logger LOG = LoggerFactory.getLogger(GeneratorReactor.class);
63
64     private final Deque<Iterable<? extends Generator>> stack = new ArrayDeque<>();
65     private final @NonNull Map<QNameModule, ModuleGenerator> generators;
66     private final @NonNull List<ModuleGenerator> children;
67     private final @NonNull SchemaInferenceStack inferenceStack;
68
69     private State state = State.INITIALIZED;
70
71     public GeneratorReactor(final EffectiveModelContext context) {
72         super(context);
73         inferenceStack = SchemaInferenceStack.of(context);
74
75         // Construct modules and their subtrees. Dependency sort is very much needed here, as it establishes order of
76         // module evaluation, and that (along with the sort in AbstractCompositeGenerator) ensures we visit
77         // AugmentGenerators without having forward references.
78         // FIXME: migrate to new ModuleDependencySort when it is available, which streamline things here
79         children = ModuleDependencySort.sort(context.getModules()).stream()
80             .map(Module::asEffectiveStatement)
81             .map(ModuleGenerator::new)
82             .collect(Collectors.toUnmodifiableList());
83         generators = Maps.uniqueIndex(children, gen -> gen.statement().localQNameModule());
84     }
85
86     /**
87      * Execute the reactor. Execution follows the following steps:
88      * <ol>
89      *   <li>link the statement inheritance graph along {@code uses}/{@code grouping} statements</li>
90      *   <li>link the {@code typedef} inheritance hierarchy by visiting all {@link TypedefGenerator}s and memoizing the
91      *       {@code type} lookup</li>
92      *   <li>link the {@code identity} inheritance hierarchy by visiting all {@link IdentityGenerator}s and memoizing
93      *       the {@code base} lookup</li>
94      *   <li>link the {@code type} statements and resolve type restriction hierarchy, determining the set of Java
95              classes required for Java equivalent of effective YANG type definitions</li>
96      *   <li>bind {@code leafref} and {@code identityref} references to their Java class roots</li>
97      *   <li>resolve {@link ChoiceIn}/{@link ChildOf} hierarchy</li>
98      *   <li>assign Java package names and {@link JavaTypeName}s to all generated classes</li>
99      *   <li>create {@link Type} instances</li>
100      * </ol>
101      *
102      * @param builderFactory factory for creating {@link TypeBuilder}s for resulting types
103      * @return Resolved generators
104      * @throws IllegalStateException if the reactor has failed execution
105      * @throws NullPointerException if {@code builderFactory} is {@code null}
106      */
107     public @NonNull Map<QNameModule, ModuleGenerator> execute(final TypeBuilderFactory builderFactory) {
108         switch (state) {
109             case INITIALIZED:
110                 state = State.EXECUTING;
111                 break;
112             case FINISHED:
113                 return generators;
114             case EXECUTING:
115                 throw new IllegalStateException("Cannot resume partial execution");
116             default:
117                 throw new IllegalStateException("Unhandled state" + state);
118         }
119
120         // Start measuring time...
121         final var sw = Stopwatch.createStarted();
122
123         // Step 1a: Walk all composite generators and resolve 'uses' statements to the corresponding grouping generator,
124         //          establishing implied inheritance. During this walk we maintain 'stack' to aid this process.
125         //          This indirectly triggers resolution of UsesAugmentGenerators' targets by hooking a requirement
126         //          on the resolved grouping's child nodes as needed.
127         linkUsesDependencies(children);
128
129         // Step 1b: Walk all module generators and start ModuleAugmentGenerators' target resolution by linking the first
130         //          step of each 'augment' statement to its corresponding instantiated site.
131         //          Then start all UsesAugmentGenerators' target resolution.
132         final var augments = new ArrayList<AugmentRequirement>();
133         for (var module : children) {
134             for (var gen : module) {
135                 if (gen instanceof ModuleAugmentGenerator moduleGen) {
136                     augments.add(moduleGen.startLinkage(this));
137                 }
138             }
139         }
140         for (var module : children) {
141             module.startUsesAugmentLinkage(augments);
142         }
143         LOG.trace("Processing linkage of {} augment generators", augments.size());
144
145         // Step 1c: Establish linkage along the reverse uses/augment axis. This is needed to route generated type
146         //          manifestations (isAddedByUses/isAugmenting) to their type generation sites. Since generator tree
147         //          iteration order does not match dependencies, we may need to perform multiple passes.
148         for (var module : children) {
149             verify(module.linkOriginalGenerator(), "Module %s failed to link", module);
150         }
151
152         final var unlinkedModules = new ArrayList<>(children);
153         while (true) {
154             final boolean progress =
155                 progressAndClean(unlinkedModules, ModuleGenerator::linkOriginalGeneratorRecursive)
156                 // not '||' because we need the side-effects, which would get short-circuited
157                 | progressAndClean(augments, AugmentRequirement::resolve);
158
159             if (augments.isEmpty() && unlinkedModules.isEmpty()) {
160                 break;
161             }
162
163             if (!progress) {
164                 final var ex = new VerifyException("Failed to make progress on linking of original generators");
165                 for (var augment : augments) {
166                     ex.addSuppressed(new IllegalStateException(augment + " is incomplete"));
167                 }
168                 for (var module : unlinkedModules) {
169                     ex.addSuppressed(new IllegalStateException(module + " remains unlinked"));
170                 }
171                 throw ex;
172             }
173         }
174
175         /*
176          * Step 2: link typedef statements, so that typedef's 'type' axis is fully established
177          * Step 3: link all identity statements, so that identity's 'base' axis is fully established
178          * Step 4: link all type statements, so that leafs and leaf-lists have restrictions established
179          *
180          * Since our implementation class hierarchy captures all four statements involved in a common superclass, we can
181          * perform this in a single pass.
182          */
183         linkDependencies(children);
184
185         // Step five: resolve all 'type leafref' and 'type identityref' statements, so they point to their
186         //            corresponding Java type representation.
187         bindTypeDefinition(children);
188
189         // Step six: walk all composite generators and link ChildOf/ChoiceIn relationships with parents. We have taken
190         //           care of this step during tree construction, hence this now a no-op.
191
192         /*
193          * Step seven: assign java packages and JavaTypeNames
194          *
195          * This is a really tricky part, as we have large number of factors to consider:
196          * - we are mapping grouping, typedef, identity and schema tree namespaces into Fully Qualified Class Names,
197          *   i.e. four namespaces into one
198          * - our source of class naming are YANG identifiers, which allow characters not allowed by Java
199          * - we generate class names as well as nested package hierarchy
200          * - we want to generate names which look like Java as much as possible
201          * - we need to always have an (arbitrarily-ugly) fail-safe name
202          *
203          * To deal with all that, we split this problem into multiple manageable chunks.
204          *
205          * The first chunk is here: we walk all generators and ask them to do two things:
206          * - instantiate their CollisionMembers and link them to appropriate CollisionDomains
207          * - return their collision domain
208          *
209          * Then we process we ask collision domains until all domains are resolved, driving the second chunk of the
210          * algorithm in CollisionDomain. Note that we may need to walk the domains multiple times, as the process of
211          * solving a domain may cause another domain's solution to be invalidated.
212          */
213         final var domains = new ArrayList<CollisionDomain>();
214         collectCollisionDomains(domains, children);
215         boolean haveUnresolved;
216         do {
217             haveUnresolved = false;
218             for (var domain : domains) {
219                 if (domain.findSolution()) {
220                     haveUnresolved = true;
221                 }
222             }
223         } while (haveUnresolved);
224
225         // Step eight: generate actual Types
226         //
227         // We have now properly cross-linked all generators and have assigned their naming roots, so from this point
228         // it looks as though we are performing a simple recursive execution. In reality, though, the actual path taken
229         // through generators is dictated by us as well as generator linkage.
230         for (var module : children) {
231             module.ensureType(builderFactory);
232         }
233
234         LOG.debug("Processed {} modules in {}", generators.size(), sw);
235         state = State.FINISHED;
236         return generators;
237     }
238
239     private void collectCollisionDomains(final List<CollisionDomain> result,
240             final Iterable<? extends Generator> parent) {
241         for (var gen : parent) {
242             gen.ensureMember();
243             collectCollisionDomains(result, gen);
244             if (gen instanceof AbstractCompositeGenerator<?, ?> compositeGen) {
245                 result.add(compositeGen.domain());
246             }
247         }
248     }
249
250     @Override
251     <E extends EffectiveStatement<QName, ?>, G extends AbstractExplicitGenerator<E, ?>> G resolveTreeScoped(
252             final Class<G> type, final QName argument) {
253         LOG.trace("Searching for tree-scoped argument {} at {}", argument, stack);
254
255         // Check if the requested QName matches current module, if it does search the stack
256         final var last = stack.getLast();
257         if (!(last instanceof ModuleGenerator lastModule)) {
258             throw new VerifyException("Unexpected last stack item " + last);
259         }
260
261         if (argument.getModule().equals(lastModule.statement().localQNameModule())) {
262             for (var ancestor : stack) {
263                 for (var child : ancestor) {
264                     if (type.isInstance(child)) {
265                         final var cast = type.cast(child);
266                         if (argument.equals(cast.statement().argument())) {
267                             LOG.trace("Found matching {}", child);
268                             return cast;
269                         }
270                     }
271                 }
272             }
273         } else {
274             final var module = generators.get(argument.getModule());
275             if (module != null) {
276                 for (var child : module) {
277                     if (type.isInstance(child)) {
278                         final var cast = type.cast(child);
279                         if (argument.equals(cast.statement().argument())) {
280                             LOG.trace("Found matching {}", child);
281                             return cast;
282                         }
283                     }
284                 }
285             }
286         }
287
288         throw new IllegalStateException("Could not find " + type + " argument " + argument + " in " + stack);
289     }
290
291     @Override
292     ModuleGenerator resolveModule(final QNameModule namespace) {
293         final var module = generators.get(requireNonNull(namespace));
294         if (module == null) {
295             throw new IllegalStateException("Failed to find module for " + namespace);
296         }
297         return module;
298     }
299
300     @Override
301     AbstractTypeObjectGenerator<?, ?> resolveLeafref(final PathExpression path) {
302         LOG.trace("Resolving path {}", path);
303         verify(inferenceStack.isEmpty(), "Unexpected data tree state %s", inferenceStack);
304         try {
305             // Populate inferenceStack with a grouping + data tree equivalent of current stack's state.
306             final var it = stack.descendingIterator();
307             // Skip first item, as it points to our children
308             verify(it.hasNext(), "Unexpected empty stack");
309             it.next();
310
311             while (it.hasNext()) {
312                 final var item = it.next();
313                 if (item instanceof Generator generator) {
314                     generator.pushToInference(inferenceStack);
315                 } else {
316                     throw new VerifyException("Unexpected stack item " + item);
317                 }
318             }
319
320             return inferenceStack.inGrouping() ? lenientResolveLeafref(path) : strictResolvePath(path);
321         } finally {
322             inferenceStack.clear();
323         }
324     }
325
326     private @NonNull AbstractTypeAwareGenerator<?, ?, ?> strictResolvePath(final @NonNull PathExpression path) {
327         try {
328             inferenceStack.resolvePathExpression(path);
329         } catch (IllegalArgumentException e) {
330             throw new IllegalArgumentException("Failed to find leafref target " + path.getOriginalString(), e);
331         }
332         return mapToGenerator();
333     }
334
335     private @Nullable AbstractTypeAwareGenerator<?, ?, ?> lenientResolveLeafref(final @NonNull PathExpression path) {
336         try {
337             inferenceStack.resolvePathExpression(path);
338         } catch (IllegalArgumentException e) {
339             LOG.debug("Ignoring unresolved path {}", path, e);
340             return null;
341         }
342         return mapToGenerator();
343     }
344
345     // Map a statement to the corresponding generator
346     private @NonNull AbstractTypeAwareGenerator<?, ?, ?> mapToGenerator() {
347         // Some preliminaries first: we need to be in the correct module to walk the path
348         final var module = inferenceStack.currentModule();
349         final var gen = verifyNotNull(generators.get(module.localQNameModule()),
350             "Cannot find generator for %s", module);
351
352         // Now kick of the search
353         final var stmtPath = inferenceStack.toInference().statementPath();
354         final var found = gen.findGenerator(stmtPath);
355         if (found instanceof AbstractTypeAwareGenerator<?, ?, ?> typeAware) {
356             return typeAware;
357         }
358         throw new VerifyException("Statements " + stmtPath + " resulted in unexpected " + found);
359     }
360
361     // Note: unlike other methods, this method pushes matching child to the stack
362     private void linkUsesDependencies(final Iterable<? extends Generator> parent) {
363         for (var child : parent) {
364             if (child instanceof AbstractCompositeGenerator<?, ?> composite) {
365                 LOG.trace("Visiting composite {}", composite);
366                 stack.push(composite);
367                 composite.linkUsesDependencies(this);
368                 linkUsesDependencies(composite);
369                 stack.pop();
370             }
371         }
372     }
373
374     private static <T> boolean progressAndClean(final List<T> items, final Function<T, LinkageProgress> function) {
375         boolean progress = false;
376
377         final var it = items.iterator();
378         while (it.hasNext()) {
379             final var item = it.next();
380             final var tmp = function.apply(item);
381             if (tmp == LinkageProgress.NONE) {
382                 LOG.debug("No progress made linking {}", item);
383                 continue;
384             }
385
386             progress = true;
387             if (tmp == LinkageProgress.DONE) {
388                 LOG.debug("Finished linking {}", item);
389                 it.remove();
390             } else {
391                 LOG.debug("Progress made linking {}", item);
392             }
393         }
394
395         return progress;
396     }
397
398     private void linkDependencies(final Iterable<? extends Generator> parent) {
399         for (var child : parent) {
400             if (child instanceof AbstractDependentGenerator<?, ?> dependent) {
401                 dependent.linkDependencies(this);
402             } else if (child instanceof AbstractCompositeGenerator) {
403                 stack.push(child);
404                 linkDependencies(child);
405                 stack.pop();
406             }
407         }
408     }
409
410     private void bindTypeDefinition(final Iterable<? extends Generator> parent) {
411         for (var child : parent) {
412             stack.push(child);
413             if (child instanceof AbstractTypeObjectGenerator<?, ?> typeObject) {
414                 typeObject.bindTypeDefinition(this);
415             } else if (child instanceof AbstractCompositeGenerator) {
416                 bindTypeDefinition(child);
417             }
418             stack.pop();
419         }
420     }
421 }