Fix GeneratorReactor.mapToGenerator()
[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
13 import com.google.common.base.Stopwatch;
14 import com.google.common.base.VerifyException;
15 import com.google.common.collect.Maps;
16 import java.util.ArrayDeque;
17 import java.util.ArrayList;
18 import java.util.Deque;
19 import java.util.Iterator;
20 import java.util.List;
21 import java.util.Map;
22 import java.util.stream.Collectors;
23 import org.eclipse.jdt.annotation.NonNull;
24 import org.eclipse.jdt.annotation.Nullable;
25 import org.opendaylight.mdsal.binding.model.api.GeneratedType;
26 import org.opendaylight.mdsal.binding.model.api.JavaTypeName;
27 import org.opendaylight.mdsal.binding.model.api.Type;
28 import org.opendaylight.yangtools.concepts.Mutable;
29 import org.opendaylight.yangtools.yang.binding.ChildOf;
30 import org.opendaylight.yangtools.yang.binding.ChoiceIn;
31 import org.opendaylight.yangtools.yang.common.QName;
32 import org.opendaylight.yangtools.yang.common.QNameModule;
33 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
34 import org.opendaylight.yangtools.yang.model.api.PathExpression;
35 import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
36 import org.opendaylight.yangtools.yang.model.api.stmt.ModuleEffectiveStatement;
37 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier;
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 Map<?, AbstractTypeAwareGenerator<?>> leafGenerators;
70     private State state = State.INITIALIZED;
71
72     public GeneratorReactor(final EffectiveModelContext 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 -> {
81                 verify(module instanceof ModuleEffectiveStatement, "Unexpected module %s", module);
82                 return new ModuleGenerator((ModuleEffectiveStatement) module);
83             })
84             .collect(Collectors.toUnmodifiableList());
85         generators = Maps.uniqueIndex(children, gen -> gen.statement().localQNameModule());
86     }
87
88     /**
89      * Execute the reactor. Execution follows the following steps:
90      * <ol>
91      *   <li>link the statement inheritance graph along {@code uses}/{@code grouping} statements</li>
92      *   <li>link the {@code typedef} inheritance hierarchy by visiting all {@link TypedefGenerator}s and memoizing the
93      *       {@code type} lookup</li>
94      *   <li>link the {@code identity} inheritance hierarchy by visiting all {@link IdentityGenerator}s and memoizing
95      *       the {@code base} lookup</li>
96      *   <li>link the {@code type} statements and resolve type restriction hierarchy, determining the set of Java
97              classes required for Java equivalent of effective YANG type definitions</li>
98      *   <li>bind {@code leafref} and {@code identityref} references to their Java class roots</li>
99      *   <li>resolve {@link ChoiceIn}/{@link ChildOf} hierarchy</li>
100      *   <li>assign Java package names and {@link JavaTypeName}s to all generated classes</li>
101      *   <li>create {@link Type} instances</li>
102      * </ol>
103      *
104      * @param builderFactory factory for creating {@link TypeBuilder}s for resulting types
105      * @return Resolved generators
106      * @throws IllegalStateException if the reactor has failed execution
107      * @throws NullPointerException if {@code builderFactory} is {@code null}
108      */
109     public @NonNull Map<QNameModule, ModuleGenerator> execute(final TypeBuilderFactory builderFactory) {
110         switch (state) {
111             case INITIALIZED:
112                 state = State.EXECUTING;
113                 break;
114             case FINISHED:
115                 return generators;
116             case EXECUTING:
117                 throw new IllegalStateException("Cannot resume partial execution");
118             default:
119                 throw new IllegalStateException("Unhandled state" + state);
120         }
121
122         // Step 1a: walk all composite generators and resolve 'uses' statements to the corresponding grouping node,
123         //          establishing implied inheritance ...
124         linkUsesDependencies(children);
125
126         // Step 1b: ... and also link augments and their targets in a separate pass, as we need groupings fully resolved
127         //          before we attempt augmentation lookups ...
128         for (ModuleGenerator module : children) {
129             for (Generator child : module) {
130                 if (child instanceof ModuleAugmentGenerator) {
131                     ((ModuleAugmentGenerator) child).linkAugmentationTarget(this);
132                 }
133             }
134         }
135
136         // Step 1c: ... finally establish linkage along the reverse uses/augment axis. This is needed to route generated
137         //          type manifestations (isAddedByUses/isAugmenting) to their type generation sites.
138         linkOriginalGenerator(children);
139
140         /*
141          * Step 2: link typedef statements, so that typedef's 'type' axis is fully established
142          * Step 3: link all identity statements, so that identity's 'base' axis is fully established
143          * Step 4: link all type statements, so that leafs and leaf-lists have restrictions established
144          *
145          * Since our implementation class hierarchy captures all four statements involved in a common superclass, we can
146          * perform this in a single pass.
147          */
148         final Stopwatch sw = Stopwatch.createStarted();
149         linkDependencies(children);
150
151         // Step five: resolve all 'type leafref' and 'type identityref' statements, so they point to their
152         //            corresponding Java type representation.
153         bindTypeDefinition(children);
154
155         // Step six: walk all composite generators and link ChildOf/ChoiceIn relationships with parents. We have taken
156         //           care of this step during tree construction, hence this now a no-op.
157
158         /*
159          * Step seven: assign java packages and JavaTypeNames
160          *
161          * This is a really tricky part, as we have large number of factors to consider:
162          * - we are mapping grouping, typedef, identity and schema tree namespaces into Fully Qualified Class Names,
163          *   i.e. four namespaces into one
164          * - our source of class naming are YANG identifiers, which allow characters not allowed by Java
165          * - we generate class names as well as nested package hierarchy
166          * - we want to generate names which look like Java as much as possible
167          * - we need to always have an (arbitrarily-ugly) fail-safe name
168          *
169          * To deal with all that, we split this problem into multiple manageable chunks.
170          *
171          * The first chunk is here: we walk all generators and ask them to do two things:
172          * - instantiate their CollisionMembers and link them to appropriate CollisionDomains
173          * - return their collision domain
174          *
175          * Then we process we ask collision domains until all domains are resolved, driving the second chunk of the
176          * algorithm in CollisionDomain. Note that we may need to walk the domains multiple times, as the process of
177          * solving a domain may cause another domain's solution to be invalidated.
178          */
179         final List<CollisionDomain> domains = new ArrayList<>();
180         collectCollisionDomains(domains, children);
181         boolean haveUnresolved;
182         do {
183             haveUnresolved = false;
184             for (CollisionDomain domain : domains) {
185                 if (domain.findSolution()) {
186                     haveUnresolved = true;
187                 }
188             }
189         } while (haveUnresolved);
190
191         // Step eight: generate actual Types
192         //
193         // We have now properly cross-linked all generators and have assigned their naming roots, so from this point
194         // it looks as though we are performing a simple recursive execution. In reality, though, the actual path taken
195         // through generators is dictated by us as well as generator linkage.
196         for (ModuleGenerator module : children) {
197             module.ensureType(builderFactory);
198         }
199
200         LOG.debug("Processed {} modules in {}", generators.size(), sw);
201         state = State.FINISHED;
202         return generators;
203     }
204
205     private void collectCollisionDomains(final List<CollisionDomain> result,
206             final Iterable<? extends Generator> parent) {
207         for (Generator gen : parent) {
208             gen.ensureMember();
209             collectCollisionDomains(result, gen);
210             if (gen instanceof AbstractCompositeGenerator) {
211                 result.add(((AbstractCompositeGenerator<?>) gen).domain());
212             }
213         }
214     }
215
216     @Override
217     AbstractExplicitGenerator<?> resolveSchemaNode(final SchemaNodeIdentifier path) {
218         verify(path instanceof SchemaNodeIdentifier.Absolute, "Unexpected path %s", path);
219         return verifyNotNull(generators.get(path.firstNodeIdentifier().getModule()), "Cannot find module for %s", path)
220             .resolveSchemaNode(path, null);
221     }
222
223     @Override
224     <E extends EffectiveStatement<QName, ?>, G extends AbstractExplicitGenerator<E>> G resolveTreeScoped(
225             final Class<G> type, final QName argument) {
226         LOG.trace("Searching for tree-scoped argument {} at {}", argument, stack);
227
228         // Check if the requested QName matches current module, if it does search the stack
229         final Iterable<? extends Generator> last = stack.getLast();
230         verify(last instanceof ModuleGenerator, "Unexpected last stack item %s", last);
231
232         if (argument.getModule().equals(((ModuleGenerator) last).statement().localQNameModule())) {
233             for (Iterable<? extends Generator> ancestor : stack) {
234                 for (Generator child : ancestor) {
235                     if (type.isInstance(child)) {
236                         final G cast = type.cast(child);
237                         if (argument.equals(cast.statement().argument())) {
238                             LOG.trace("Found matching {}", child);
239                             return cast;
240                         }
241                     }
242                 }
243             }
244         } else {
245             final ModuleGenerator module = generators.get(argument.getModule());
246             if (module != null) {
247                 for (Generator child : module) {
248                     if (type.isInstance(child)) {
249                         final G cast = type.cast(child);
250                         if (argument.equals(cast.statement().argument())) {
251                             LOG.trace("Found matching {}", child);
252                             return cast;
253                         }
254                     }
255                 }
256             }
257         }
258
259         throw new IllegalStateException("Could not find " + type + " argument " + argument + " in " + stack);
260     }
261
262     @Override
263     IdentityGenerator resolveIdentity(final QName name) {
264         final ModuleGenerator module = generators.get(name.getModule());
265         if (module != null) {
266             for (Generator gen : module) {
267                 if (gen instanceof IdentityGenerator) {
268                     final IdentityGenerator idgen = (IdentityGenerator) gen;
269                     if (name.equals(idgen.statement().argument())) {
270                         return idgen;
271                     }
272                 }
273             }
274         }
275         throw new IllegalStateException("Failed to find identity " + name);
276     }
277
278     @Override
279     AbstractTypeObjectGenerator<?> resolveLeafref(final PathExpression path) {
280         LOG.trace("Resolving path {}", path);
281         verify(inferenceStack.isEmpty(), "Unexpected data tree state %s", inferenceStack);
282         try {
283             // Populate inferenceStack with a grouping + data tree equivalent of current stack's state.
284             final Iterator<Iterable<? extends Generator>> it = stack.descendingIterator();
285             // Skip first item, as it points to our children
286             verify(it.hasNext(), "Unexpected empty stack");
287             it.next();
288
289             while (it.hasNext()) {
290                 final Iterable<? extends Generator> item = it.next();
291                 verify(item instanceof Generator, "Unexpected stack item %s", item);
292                 ((Generator) item).pushToInference(inferenceStack);
293             }
294
295             return inferenceStack.inGrouping() ? lenientResolveLeafref(path) : strictResolvePath(path);
296         } finally {
297             inferenceStack.clear();
298         }
299     }
300
301     private @NonNull AbstractTypeAwareGenerator<?> strictResolvePath(final @NonNull PathExpression path) {
302         try {
303             inferenceStack.resolvePathExpression(path);
304         } catch (IllegalArgumentException e) {
305             throw new IllegalArgumentException("Failed to find leafref target " + path.getOriginalString(), e);
306         }
307         return mapToGenerator();
308     }
309
310     private @Nullable AbstractTypeAwareGenerator<?> lenientResolveLeafref(final @NonNull PathExpression path) {
311         try {
312             inferenceStack.resolvePathExpression(path);
313         } catch (IllegalArgumentException e) {
314             LOG.debug("Ignoring unresolved path {}", path, e);
315             return null;
316         }
317         return mapToGenerator();
318     }
319
320     // Map a statement to the corresponding generator
321     private @NonNull AbstractTypeAwareGenerator<?> mapToGenerator() {
322         // Some preliminaries first: we need to be in the correct module to walk the path
323         final ModuleEffectiveStatement module = inferenceStack.currentModule();
324         final ModuleGenerator gen = verifyNotNull(generators.get(module.localQNameModule()),
325             "Cannot find generator for %s", module);
326
327         // Now kick of the search
328         final List<EffectiveStatement<?, ?>> stmtPath = inferenceStack.toInference().statementPath();
329         final AbstractExplicitGenerator<?> found = gen.findGenerator(stmtPath);
330         if (found instanceof AbstractTypeAwareGenerator) {
331             return (AbstractTypeAwareGenerator<?>) found;
332         }
333         throw new VerifyException("Statements " + stmtPath + " resulted in unexpected " + found);
334     }
335
336     // Note: unlike other methods, this method pushes matching child to the stack
337     private void linkUsesDependencies(final Iterable<? extends Generator> parent) {
338         for (Generator child : parent) {
339             if (child instanceof AbstractCompositeGenerator) {
340                 LOG.trace("Visiting composite {}", child);
341                 final AbstractCompositeGenerator<?> composite = (AbstractCompositeGenerator<?>) child;
342                 stack.push(composite);
343                 composite.linkUsesDependencies(this);
344                 linkUsesDependencies(composite);
345                 stack.pop();
346             }
347         }
348     }
349
350     private void linkDependencies(final Iterable<? extends Generator> parent) {
351         for (Generator child : parent) {
352             if (child instanceof AbstractDependentGenerator) {
353                 ((AbstractDependentGenerator<?>) child).linkDependencies(this);
354             } else if (child instanceof AbstractCompositeGenerator) {
355                 stack.push(child);
356                 linkDependencies(child);
357                 stack.pop();
358             }
359         }
360     }
361
362     private void linkOriginalGenerator(final Iterable<? extends Generator> parent) {
363         for (Generator child : parent) {
364             if (child instanceof AbstractExplicitGenerator) {
365                 ((AbstractExplicitGenerator<?>) child).linkOriginalGenerator(this);
366             }
367             if (child instanceof AbstractCompositeGenerator) {
368                 stack.push(child);
369                 linkOriginalGenerator(child);
370                 stack.pop();
371             }
372         }
373     }
374
375     private void bindTypeDefinition(final Iterable<? extends Generator> parent) {
376         for (Generator child : parent) {
377             stack.push(child);
378             if (child instanceof AbstractTypeObjectGenerator) {
379                 ((AbstractTypeObjectGenerator<?>) child).bindTypeDefinition(this);
380             } else if (child instanceof AbstractCompositeGenerator) {
381                 bindTypeDefinition(child);
382             }
383             stack.pop();
384         }
385     }
386 }