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