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.Verify.verify;
11 import static com.google.common.base.Verify.verifyNotNull;
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;
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;
45 * A multi-stage reactor for generating {@link GeneratedType} instances from an {@link EffectiveModelContext}.
48 * The reason for multi-stage processing is that the problem ahead of us involves:
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>
55 public final class GeneratorReactor extends GeneratorContext implements Mutable {
62 private static final Logger LOG = LoggerFactory.getLogger(GeneratorReactor.class);
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;
69 private Map<?, AbstractTypeAwareGenerator<?>> leafGenerators;
70 private State state = State.INITIALIZED;
72 public GeneratorReactor(final EffectiveModelContext context) {
73 inferenceStack = SchemaInferenceStack.of(context);
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()
81 verify(module instanceof ModuleEffectiveStatement, "Unexpected module %s", module);
82 return new ModuleGenerator((ModuleEffectiveStatement) module);
84 .collect(Collectors.toUnmodifiableList());
85 generators = Maps.uniqueIndex(children, gen -> gen.statement().localQNameModule());
89 * Execute the reactor. Execution follows the following steps:
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>
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}
109 public @NonNull Map<QNameModule, ModuleGenerator> execute(final TypeBuilderFactory builderFactory) {
112 state = State.EXECUTING;
117 throw new IllegalStateException("Cannot resume partial execution");
119 throw new IllegalStateException("Unhandled state" + state);
122 // Step 1a: walk all composite generators and resolve 'uses' statements to the corresponding grouping node,
123 // establishing implied inheritance ...
124 linkUsesDependencies(children);
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);
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);
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
145 * Since our implementation class hierarchy captures all four statements involved in a common superclass, we can
146 * perform this in a single pass.
148 final Stopwatch sw = Stopwatch.createStarted();
149 linkDependencies(children);
151 // Step five: resolve all 'type leafref' and 'type identityref' statements, so they point to their
152 // corresponding Java type representation.
153 bindTypeDefinition(children);
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.
159 * Step seven: assign java packages and JavaTypeNames
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
169 * To deal with all that, we split this problem into multiple manageable chunks.
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
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.
179 final List<CollisionDomain> domains = new ArrayList<>();
180 collectCollisionDomains(domains, children);
181 boolean haveUnresolved;
183 haveUnresolved = false;
184 for (CollisionDomain domain : domains) {
185 if (domain.findSolution()) {
186 haveUnresolved = true;
189 } while (haveUnresolved);
191 // Step eight: generate actual Types
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);
200 LOG.debug("Processed {} modules in {}", generators.size(), sw);
201 state = State.FINISHED;
205 private void collectCollisionDomains(final List<CollisionDomain> result,
206 final Iterable<? extends Generator> parent) {
207 for (Generator gen : parent) {
209 collectCollisionDomains(result, gen);
210 if (gen instanceof AbstractCompositeGenerator) {
211 result.add(((AbstractCompositeGenerator<?>) gen).domain());
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);
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);
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);
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);
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);
259 throw new IllegalStateException("Could not find " + type + " argument " + argument + " in " + stack);
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())) {
275 throw new IllegalStateException("Failed to find identity " + name);
279 AbstractTypeObjectGenerator<?> resolveLeafref(final PathExpression path) {
280 LOG.trace("Resolving path {}", path);
281 verify(inferenceStack.isEmpty(), "Unexpected data tree state %s", inferenceStack);
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");
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);
295 return inferenceStack.inGrouping() ? lenientResolveLeafref(path) : strictResolvePath(path);
297 inferenceStack.clear();
301 private @NonNull AbstractTypeAwareGenerator<?> strictResolvePath(final @NonNull PathExpression path) {
303 inferenceStack.resolvePathExpression(path);
304 } catch (IllegalArgumentException e) {
305 throw new IllegalArgumentException("Failed to find leafref target " + path.getOriginalString(), e);
307 return mapToGenerator();
310 private @Nullable AbstractTypeAwareGenerator<?> lenientResolveLeafref(final @NonNull PathExpression path) {
312 inferenceStack.resolvePathExpression(path);
313 } catch (IllegalArgumentException e) {
314 LOG.debug("Ignoring unresolved path {}", path, e);
317 return mapToGenerator();
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);
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;
333 throw new VerifyException("Statements " + stmtPath + " resulted in unexpected " + found);
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);
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) {
356 linkDependencies(child);
362 private void linkOriginalGenerator(final Iterable<? extends Generator> parent) {
363 for (Generator child : parent) {
364 if (child instanceof AbstractExplicitGenerator) {
365 ((AbstractExplicitGenerator<?>) child).linkOriginalGenerator(this);
367 if (child instanceof AbstractCompositeGenerator) {
369 linkOriginalGenerator(child);
375 private void bindTypeDefinition(final Iterable<? extends Generator> parent) {
376 for (Generator child : parent) {
378 if (child instanceof AbstractTypeObjectGenerator) {
379 ((AbstractTypeObjectGenerator<?>) child).bindTypeDefinition(this);
380 } else if (child instanceof AbstractCompositeGenerator) {
381 bindTypeDefinition(child);