Enforce explicit generator linkage
[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.stream.Collectors;
25 import org.eclipse.jdt.annotation.NonNull;
26 import org.eclipse.jdt.annotation.Nullable;
27 import org.opendaylight.mdsal.binding.model.api.GeneratedType;
28 import org.opendaylight.mdsal.binding.model.api.JavaTypeName;
29 import org.opendaylight.mdsal.binding.model.api.Type;
30 import org.opendaylight.yangtools.concepts.Mutable;
31 import org.opendaylight.yangtools.yang.binding.ChildOf;
32 import org.opendaylight.yangtools.yang.binding.ChoiceIn;
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.PathExpression;
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.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         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         // Start measuring time...
123         final Stopwatch sw = Stopwatch.createStarted();
124
125         // Step 1a: walk all composite generators and resolve 'uses' statements to the corresponding grouping node,
126         //          establishing implied inheritance ...
127         linkUsesDependencies(children);
128
129         // Step 1b: ... and also link augments and their targets in a separate pass, as we need groupings fully resolved
130         //          before we attempt augmentation lookups ...
131         for (ModuleGenerator module : children) {
132             for (Generator child : module) {
133                 if (child instanceof ModuleAugmentGenerator) {
134                     ((ModuleAugmentGenerator) child).linkAugmentationTarget(this);
135                 }
136             }
137         }
138
139         // Step 1c: ... finally establish linkage along the reverse uses/augment axis. This is needed to route generated
140         //          type manifestations (isAddedByUses/isAugmenting) to their type generation sites. Since generator
141         //          tree iteration order does not match dependencies, we may need to perform multiple passes.
142         long unlinkedOriginals = Long.MAX_VALUE;
143         do {
144             long remaining = 0;
145             for (ModuleGenerator module : children) {
146                 remaining += module.linkOriginalGenerator();
147             }
148             verify(remaining < unlinkedOriginals, "Failed to make progress on linking of remaining %s originals",
149                 remaining);
150             unlinkedOriginals = remaining;
151         } while (unlinkedOriginals != 0);
152
153         /*
154          * Step 2: link typedef statements, so that typedef's 'type' axis is fully established
155          * Step 3: link all identity statements, so that identity's 'base' axis is fully established
156          * Step 4: link all type statements, so that leafs and leaf-lists have restrictions established
157          *
158          * Since our implementation class hierarchy captures all four statements involved in a common superclass, we can
159          * perform this in a single pass.
160          */
161         linkDependencies(children);
162
163         // Step five: resolve all 'type leafref' and 'type identityref' statements, so they point to their
164         //            corresponding Java type representation.
165         bindTypeDefinition(children);
166
167         // Step six: walk all composite generators and link ChildOf/ChoiceIn relationships with parents. We have taken
168         //           care of this step during tree construction, hence this now a no-op.
169
170         /*
171          * Step seven: assign java packages and JavaTypeNames
172          *
173          * This is a really tricky part, as we have large number of factors to consider:
174          * - we are mapping grouping, typedef, identity and schema tree namespaces into Fully Qualified Class Names,
175          *   i.e. four namespaces into one
176          * - our source of class naming are YANG identifiers, which allow characters not allowed by Java
177          * - we generate class names as well as nested package hierarchy
178          * - we want to generate names which look like Java as much as possible
179          * - we need to always have an (arbitrarily-ugly) fail-safe name
180          *
181          * To deal with all that, we split this problem into multiple manageable chunks.
182          *
183          * The first chunk is here: we walk all generators and ask them to do two things:
184          * - instantiate their CollisionMembers and link them to appropriate CollisionDomains
185          * - return their collision domain
186          *
187          * Then we process we ask collision domains until all domains are resolved, driving the second chunk of the
188          * algorithm in CollisionDomain. Note that we may need to walk the domains multiple times, as the process of
189          * solving a domain may cause another domain's solution to be invalidated.
190          */
191         final List<CollisionDomain> domains = new ArrayList<>();
192         collectCollisionDomains(domains, children);
193         boolean haveUnresolved;
194         do {
195             haveUnresolved = false;
196             for (CollisionDomain domain : domains) {
197                 if (domain.findSolution()) {
198                     haveUnresolved = true;
199                 }
200             }
201         } while (haveUnresolved);
202
203         // Step eight: generate actual Types
204         //
205         // We have now properly cross-linked all generators and have assigned their naming roots, so from this point
206         // it looks as though we are performing a simple recursive execution. In reality, though, the actual path taken
207         // through generators is dictated by us as well as generator linkage.
208         for (ModuleGenerator module : children) {
209             module.ensureType(builderFactory);
210         }
211
212         LOG.debug("Processed {} modules in {}", generators.size(), sw);
213         state = State.FINISHED;
214         return generators;
215     }
216
217     private void collectCollisionDomains(final List<CollisionDomain> result,
218             final Iterable<? extends Generator> parent) {
219         for (Generator gen : parent) {
220             gen.ensureMember();
221             collectCollisionDomains(result, gen);
222             if (gen instanceof AbstractCompositeGenerator) {
223                 result.add(((AbstractCompositeGenerator<?>) gen).domain());
224             }
225         }
226     }
227
228     @Override
229     <E extends EffectiveStatement<QName, ?>, G extends AbstractExplicitGenerator<E>> G resolveTreeScoped(
230             final Class<G> type, final QName argument) {
231         LOG.trace("Searching for tree-scoped argument {} at {}", argument, stack);
232
233         // Check if the requested QName matches current module, if it does search the stack
234         final Iterable<? extends Generator> last = stack.getLast();
235         verify(last instanceof ModuleGenerator, "Unexpected last stack item %s", last);
236
237         if (argument.getModule().equals(((ModuleGenerator) last).statement().localQNameModule())) {
238             for (Iterable<? extends Generator> ancestor : stack) {
239                 for (Generator child : ancestor) {
240                     if (type.isInstance(child)) {
241                         final G cast = type.cast(child);
242                         if (argument.equals(cast.statement().argument())) {
243                             LOG.trace("Found matching {}", child);
244                             return cast;
245                         }
246                     }
247                 }
248             }
249         } else {
250             final ModuleGenerator module = generators.get(argument.getModule());
251             if (module != null) {
252                 for (Generator child : module) {
253                     if (type.isInstance(child)) {
254                         final G cast = type.cast(child);
255                         if (argument.equals(cast.statement().argument())) {
256                             LOG.trace("Found matching {}", child);
257                             return cast;
258                         }
259                     }
260                 }
261             }
262         }
263
264         throw new IllegalStateException("Could not find " + type + " argument " + argument + " in " + stack);
265     }
266
267     @Override
268     ModuleGenerator resolveModule(final QNameModule namespace) {
269         final ModuleGenerator module = generators.get(requireNonNull(namespace));
270         checkState(module != null, "Failed to find module for %s", namespace);
271         return module;
272     }
273
274     @Override
275     AbstractTypeObjectGenerator<?> resolveLeafref(final PathExpression path) {
276         LOG.trace("Resolving path {}", path);
277         verify(inferenceStack.isEmpty(), "Unexpected data tree state %s", inferenceStack);
278         try {
279             // Populate inferenceStack with a grouping + data tree equivalent of current stack's state.
280             final Iterator<Iterable<? extends Generator>> it = stack.descendingIterator();
281             // Skip first item, as it points to our children
282             verify(it.hasNext(), "Unexpected empty stack");
283             it.next();
284
285             while (it.hasNext()) {
286                 final Iterable<? extends Generator> item = it.next();
287                 verify(item instanceof Generator, "Unexpected stack item %s", item);
288                 ((Generator) item).pushToInference(inferenceStack);
289             }
290
291             return inferenceStack.inGrouping() ? lenientResolveLeafref(path) : strictResolvePath(path);
292         } finally {
293             inferenceStack.clear();
294         }
295     }
296
297     private @NonNull AbstractTypeAwareGenerator<?> strictResolvePath(final @NonNull PathExpression path) {
298         try {
299             inferenceStack.resolvePathExpression(path);
300         } catch (IllegalArgumentException e) {
301             throw new IllegalArgumentException("Failed to find leafref target " + path.getOriginalString(), e);
302         }
303         return mapToGenerator();
304     }
305
306     private @Nullable AbstractTypeAwareGenerator<?> lenientResolveLeafref(final @NonNull PathExpression path) {
307         try {
308             inferenceStack.resolvePathExpression(path);
309         } catch (IllegalArgumentException e) {
310             LOG.debug("Ignoring unresolved path {}", path, e);
311             return null;
312         }
313         return mapToGenerator();
314     }
315
316     // Map a statement to the corresponding generator
317     private @NonNull AbstractTypeAwareGenerator<?> mapToGenerator() {
318         // Some preliminaries first: we need to be in the correct module to walk the path
319         final ModuleEffectiveStatement module = inferenceStack.currentModule();
320         final ModuleGenerator gen = verifyNotNull(generators.get(module.localQNameModule()),
321             "Cannot find generator for %s", module);
322
323         // Now kick of the search
324         final List<EffectiveStatement<?, ?>> stmtPath = inferenceStack.toInference().statementPath();
325         final AbstractExplicitGenerator<?> found = gen.findGenerator(stmtPath);
326         if (found instanceof AbstractTypeAwareGenerator) {
327             return (AbstractTypeAwareGenerator<?>) found;
328         }
329         throw new VerifyException("Statements " + stmtPath + " resulted in unexpected " + found);
330     }
331
332     // Note: unlike other methods, this method pushes matching child to the stack
333     private void linkUsesDependencies(final Iterable<? extends Generator> parent) {
334         for (Generator child : parent) {
335             if (child instanceof AbstractCompositeGenerator) {
336                 LOG.trace("Visiting composite {}", child);
337                 final AbstractCompositeGenerator<?> composite = (AbstractCompositeGenerator<?>) child;
338                 stack.push(composite);
339                 composite.linkUsesDependencies(this);
340                 linkUsesDependencies(composite);
341                 stack.pop();
342             }
343         }
344     }
345
346     private void linkDependencies(final Iterable<? extends Generator> parent) {
347         for (Generator child : parent) {
348             if (child instanceof AbstractDependentGenerator) {
349                 ((AbstractDependentGenerator<?>) child).linkDependencies(this);
350             } else if (child instanceof AbstractCompositeGenerator) {
351                 stack.push(child);
352                 linkDependencies(child);
353                 stack.pop();
354             }
355         }
356     }
357
358     private void bindTypeDefinition(final Iterable<? extends Generator> parent) {
359         for (Generator child : parent) {
360             stack.push(child);
361             if (child instanceof AbstractTypeObjectGenerator) {
362                 ((AbstractTypeObjectGenerator<?>) child).bindTypeDefinition(this);
363             } else if (child instanceof AbstractCompositeGenerator) {
364                 bindTypeDefinition(child);
365             }
366             stack.pop();
367         }
368     }
369 }