2 * Copyright (c) 2021 PANTHEON.tech, s.r.o. and others. All rights reserved.
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
8 package org.opendaylight.mdsal.binding.generator.impl.reactor;
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;
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;
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.PathExpression;
38 import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
39 import org.opendaylight.yangtools.yang.model.api.stmt.ModuleEffectiveStatement;
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;
47 * A multi-stage reactor for generating {@link GeneratedType} instances from an {@link EffectiveModelContext}.
50 * The reason for multi-stage processing is that the problem ahead of us involves:
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>
57 public final class GeneratorReactor extends GeneratorContext implements Mutable {
64 private static final Logger LOG = LoggerFactory.getLogger(GeneratorReactor.class);
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;
71 private State state = State.INITIALIZED;
73 public GeneratorReactor(final EffectiveModelContext context) {
75 inferenceStack = SchemaInferenceStack.of(context);
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()
83 verify(module instanceof ModuleEffectiveStatement, "Unexpected module %s", module);
84 return new ModuleGenerator((ModuleEffectiveStatement) module);
86 .collect(Collectors.toUnmodifiableList());
87 generators = Maps.uniqueIndex(children, gen -> gen.statement().localQNameModule());
91 * Execute the reactor. Execution follows the following steps:
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>
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}
111 public @NonNull Map<QNameModule, ModuleGenerator> execute(final TypeBuilderFactory builderFactory) {
114 state = State.EXECUTING;
119 throw new IllegalStateException("Cannot resume partial execution");
121 throw new IllegalStateException("Unhandled state" + state);
124 // Start measuring time...
125 final Stopwatch sw = Stopwatch.createStarted();
127 // Step 1a: Walk all composite generators and resolve 'uses' statements to the corresponding grouping generator,
128 // establishing implied inheritance. During this walk we maintain 'stack' to aid this process.
129 // This indirectly triggers resolution of UsesAugmentGenerators' targets by hooking a requirement
130 // on the resolved grouping's child nodes as needed.
131 linkUsesDependencies(children);
133 // Step 1b: Walk all module generators and start ModuleAugmentGenerators' target resolution by linking the first
134 // step of each 'augment' statement to its corresponding instantiated site.
135 // Then start all UsesAugmentGenerators' target resolution.
136 final var augments = new ArrayList<AugmentRequirement>();
137 for (ModuleGenerator module : children) {
138 for (Generator gen : module) {
139 if (gen instanceof ModuleAugmentGenerator moduleGen) {
140 augments.add(moduleGen.startLinkage(this));
144 for (ModuleGenerator module : children) {
145 module.startUsesAugmentLinkage(augments);
147 LOG.trace("Processing linkage of {} augment generators", augments.size());
149 // Step 1c: Establish linkage along the reverse uses/augment axis. This is needed to route generated type
150 // manifestations (isAddedByUses/isAugmenting) to their type generation sites. Since generator tree
151 // iteration order does not match dependencies, we may need to perform multiple passes.
152 for (ModuleGenerator module : children) {
153 verify(module.linkOriginalGenerator(), "Module %s failed to link", module);
156 final var unlinkedModules = new ArrayList<>(children);
158 final boolean progress =
159 progressAndClean(unlinkedModules, ModuleGenerator::linkOriginalGeneratorRecursive)
160 // not '||' because we need the side-effects, which would get short-circuited
161 | progressAndClean(augments, AugmentRequirement::resolve);
163 if (augments.isEmpty() && unlinkedModules.isEmpty()) {
168 final var ex = new VerifyException("Failed to make progress on linking of original generators");
169 for (var augment : augments) {
170 ex.addSuppressed(new IllegalStateException(augment + " is incomplete"));
172 for (var module : unlinkedModules) {
173 ex.addSuppressed(new IllegalStateException(module + " remains unlinked"));
180 * Step 2: link typedef statements, so that typedef's 'type' axis is fully established
181 * Step 3: link all identity statements, so that identity's 'base' axis is fully established
182 * Step 4: link all type statements, so that leafs and leaf-lists have restrictions established
184 * Since our implementation class hierarchy captures all four statements involved in a common superclass, we can
185 * perform this in a single pass.
187 linkDependencies(children);
189 // Step five: resolve all 'type leafref' and 'type identityref' statements, so they point to their
190 // corresponding Java type representation.
191 bindTypeDefinition(children);
193 // Step six: walk all composite generators and link ChildOf/ChoiceIn relationships with parents. We have taken
194 // care of this step during tree construction, hence this now a no-op.
197 * Step seven: assign java packages and JavaTypeNames
199 * This is a really tricky part, as we have large number of factors to consider:
200 * - we are mapping grouping, typedef, identity and schema tree namespaces into Fully Qualified Class Names,
201 * i.e. four namespaces into one
202 * - our source of class naming are YANG identifiers, which allow characters not allowed by Java
203 * - we generate class names as well as nested package hierarchy
204 * - we want to generate names which look like Java as much as possible
205 * - we need to always have an (arbitrarily-ugly) fail-safe name
207 * To deal with all that, we split this problem into multiple manageable chunks.
209 * The first chunk is here: we walk all generators and ask them to do two things:
210 * - instantiate their CollisionMembers and link them to appropriate CollisionDomains
211 * - return their collision domain
213 * Then we process we ask collision domains until all domains are resolved, driving the second chunk of the
214 * algorithm in CollisionDomain. Note that we may need to walk the domains multiple times, as the process of
215 * solving a domain may cause another domain's solution to be invalidated.
217 final List<CollisionDomain> domains = new ArrayList<>();
218 collectCollisionDomains(domains, children);
219 boolean haveUnresolved;
221 haveUnresolved = false;
222 for (CollisionDomain domain : domains) {
223 if (domain.findSolution()) {
224 haveUnresolved = true;
227 } while (haveUnresolved);
229 // Step eight: generate actual Types
231 // We have now properly cross-linked all generators and have assigned their naming roots, so from this point
232 // it looks as though we are performing a simple recursive execution. In reality, though, the actual path taken
233 // through generators is dictated by us as well as generator linkage.
234 for (ModuleGenerator module : children) {
235 module.ensureType(builderFactory);
238 LOG.debug("Processed {} modules in {}", generators.size(), sw);
239 state = State.FINISHED;
243 private void collectCollisionDomains(final List<CollisionDomain> result,
244 final Iterable<? extends Generator> parent) {
245 for (Generator gen : parent) {
247 collectCollisionDomains(result, gen);
248 if (gen instanceof AbstractCompositeGenerator<?, ?> compositeGen) {
249 result.add(compositeGen.domain());
255 <E extends EffectiveStatement<QName, ?>, G extends AbstractExplicitGenerator<E, ?>> G resolveTreeScoped(
256 final Class<G> type, final QName argument) {
257 LOG.trace("Searching for tree-scoped argument {} at {}", argument, stack);
259 // Check if the requested QName matches current module, if it does search the stack
260 final Iterable<? extends Generator> last = stack.getLast();
261 verify(last instanceof ModuleGenerator, "Unexpected last stack item %s", last);
263 if (argument.getModule().equals(((ModuleGenerator) last).statement().localQNameModule())) {
264 for (Iterable<? extends Generator> ancestor : stack) {
265 for (Generator child : ancestor) {
266 if (type.isInstance(child)) {
267 final G cast = type.cast(child);
268 if (argument.equals(cast.statement().argument())) {
269 LOG.trace("Found matching {}", child);
276 final ModuleGenerator module = generators.get(argument.getModule());
277 if (module != null) {
278 for (Generator child : module) {
279 if (type.isInstance(child)) {
280 final G cast = type.cast(child);
281 if (argument.equals(cast.statement().argument())) {
282 LOG.trace("Found matching {}", child);
290 throw new IllegalStateException("Could not find " + type + " argument " + argument + " in " + stack);
294 ModuleGenerator resolveModule(final QNameModule namespace) {
295 final ModuleGenerator module = generators.get(requireNonNull(namespace));
296 checkState(module != null, "Failed to find module for %s", namespace);
301 AbstractTypeObjectGenerator<?, ?> resolveLeafref(final PathExpression path) {
302 LOG.trace("Resolving path {}", path);
303 verify(inferenceStack.isEmpty(), "Unexpected data tree state %s", inferenceStack);
305 // Populate inferenceStack with a grouping + data tree equivalent of current stack's state.
306 final Iterator<Iterable<? extends Generator>> it = stack.descendingIterator();
307 // Skip first item, as it points to our children
308 verify(it.hasNext(), "Unexpected empty stack");
311 while (it.hasNext()) {
312 final Iterable<? extends Generator> item = it.next();
313 verify(item instanceof Generator, "Unexpected stack item %s", item);
314 ((Generator) item).pushToInference(inferenceStack);
317 return inferenceStack.inGrouping() ? lenientResolveLeafref(path) : strictResolvePath(path);
319 inferenceStack.clear();
323 private @NonNull AbstractTypeAwareGenerator<?, ?, ?> strictResolvePath(final @NonNull PathExpression path) {
325 inferenceStack.resolvePathExpression(path);
326 } catch (IllegalArgumentException e) {
327 throw new IllegalArgumentException("Failed to find leafref target " + path.getOriginalString(), e);
329 return mapToGenerator();
332 private @Nullable AbstractTypeAwareGenerator<?, ?, ?> lenientResolveLeafref(final @NonNull PathExpression path) {
334 inferenceStack.resolvePathExpression(path);
335 } catch (IllegalArgumentException e) {
336 LOG.debug("Ignoring unresolved path {}", path, e);
339 return mapToGenerator();
342 // Map a statement to the corresponding generator
343 private @NonNull AbstractTypeAwareGenerator<?, ?, ?> mapToGenerator() {
344 // Some preliminaries first: we need to be in the correct module to walk the path
345 final ModuleEffectiveStatement module = inferenceStack.currentModule();
346 final ModuleGenerator gen = verifyNotNull(generators.get(module.localQNameModule()),
347 "Cannot find generator for %s", module);
349 // Now kick of the search
350 final List<EffectiveStatement<?, ?>> stmtPath = inferenceStack.toInference().statementPath();
351 final AbstractExplicitGenerator<?, ?> found = gen.findGenerator(stmtPath);
352 if (found instanceof AbstractTypeAwareGenerator<?, ?, ?> typeAware) {
355 throw new VerifyException("Statements " + stmtPath + " resulted in unexpected " + found);
358 // Note: unlike other methods, this method pushes matching child to the stack
359 private void linkUsesDependencies(final Iterable<? extends Generator> parent) {
360 for (Generator child : parent) {
361 if (child instanceof AbstractCompositeGenerator<?, ?> composite) {
362 LOG.trace("Visiting composite {}", composite);
363 stack.push(composite);
364 composite.linkUsesDependencies(this);
365 linkUsesDependencies(composite);
371 private static <T> boolean progressAndClean(final List<T> items, final Function<T, LinkageProgress> function) {
372 boolean progress = false;
374 final var it = items.iterator();
375 while (it.hasNext()) {
376 final var item = it.next();
377 final var tmp = function.apply(item);
378 if (tmp == LinkageProgress.NONE) {
379 LOG.debug("No progress made linking {}", item);
384 if (tmp == LinkageProgress.DONE) {
385 LOG.debug("Finished linking {}", item);
388 LOG.debug("Progress made linking {}", item);
395 private void linkDependencies(final Iterable<? extends Generator> parent) {
396 for (Generator child : parent) {
397 if (child instanceof AbstractDependentGenerator<?, ?> dependent) {
398 dependent.linkDependencies(this);
399 } else if (child instanceof AbstractCompositeGenerator) {
401 linkDependencies(child);
407 private void bindTypeDefinition(final Iterable<? extends Generator> parent) {
408 for (Generator child : parent) {
410 if (child instanceof AbstractTypeObjectGenerator<?, ?> typeObject) {
411 typeObject.bindTypeDefinition(this);
412 } else if (child instanceof AbstractCompositeGenerator) {
413 bindTypeDefinition(child);