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