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