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 java.util.Objects.requireNonNull;
13 import com.google.common.base.MoreObjects;
14 import com.google.common.base.MoreObjects.ToStringHelper;
15 import com.google.common.collect.ArrayListMultimap;
16 import com.google.common.collect.Multimap;
17 import java.util.ArrayList;
18 import java.util.Collection;
19 import java.util.Iterator;
20 import java.util.List;
21 import java.util.Map.Entry;
22 import org.eclipse.jdt.annotation.NonNull;
23 import org.opendaylight.mdsal.binding.spec.naming.BindingMapping;
24 import org.opendaylight.yangtools.yang.common.AbstractQName;
25 import org.opendaylight.yangtools.yang.common.QName;
26 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier;
28 final class CollisionDomain {
29 abstract sealed class Member {
30 private final Generator gen;
32 private List<Secondary> secondaries = List.of();
33 private String currentPackage;
34 private String currentClass;
36 Member(final Generator gen) {
37 this.gen = requireNonNull(gen);
40 final void addSecondary(final Secondary secondary) {
41 if (secondaries.isEmpty()) {
42 secondaries = new ArrayList<>();
44 secondaries.add(requireNonNull(secondary));
47 final @NonNull String currentClass() {
48 if (currentClass == null) {
49 currentClass = computeCurrentClass();
54 final @NonNull String currentPackage() {
55 if (currentPackage == null) {
56 currentPackage = computeCurrentPackage();
58 return currentPackage;
61 abstract boolean equalRoot(@NonNull Member other);
63 abstract String computeCurrentClass();
65 abstract String computeCurrentPackage();
67 boolean signalConflict() {
70 currentPackage = null;
72 for (Secondary secondary : secondaries) {
73 secondary.primaryConflict();
80 public final String toString() {
81 return addToStringAttributes(MoreObjects.toStringHelper(this).omitNullValues()).toString();
84 ToStringHelper addToStringAttributes(final ToStringHelper helper) {
85 return helper.add("gen", gen).add("class", currentClass).add("package", currentPackage);
89 private sealed class Primary extends Member {
90 private ClassNamingStrategy strategy;
92 Primary(final Generator gen, final ClassNamingStrategy strategy) {
94 this.strategy = requireNonNull(strategy);
98 final String computeCurrentClass() {
99 return strategy.simpleClassName();
103 final String computeCurrentPackage() {
104 return packageString(strategy.nodeIdentifier());
108 final boolean signalConflict() {
109 final ClassNamingStrategy newStrategy = strategy.fallback();
110 if (newStrategy == null) {
114 strategy = newStrategy;
115 return super.signalConflict();
119 final ToStringHelper addToStringAttributes(final ToStringHelper helper) {
120 return super.addToStringAttributes(helper.add("strategy", strategy));
124 boolean equalRoot(final Member other) {
125 return other instanceof Primary primary
126 && strategy.nodeIdentifier().getLocalName().equals(primary.strategy.nodeIdentifier().getLocalName());
130 private final class Prefix extends Primary {
131 Prefix(final Generator gen, final ClassNamingStrategy strategy) {
132 super(gen, strategy);
136 private abstract sealed class Secondary extends Member {
137 private final String classSuffix;
138 final @NonNull Member classPrimary;
140 Secondary(final Generator gen, final Member primary, final String classSuffix) {
142 classPrimary = requireNonNull(primary);
143 this.classSuffix = requireNonNull(classSuffix);
144 primary.addSecondary(this);
148 final String computeCurrentClass() {
149 return classPrimary.currentClass() + classSuffix;
153 final boolean signalConflict() {
154 return classPrimary.signalConflict();
157 final void primaryConflict() {
158 super.signalConflict();
162 final boolean equalRoot(final Member other) {
163 return other instanceof Secondary sec
164 && classPrimary.equalRoot(sec.classPrimary) && classSuffix.equals(sec.classSuffix);
168 private final class LeafSecondary extends Secondary {
169 LeafSecondary(final Generator gen, final Member classPrimary, final String classSuffix) {
170 super(gen, classPrimary, classSuffix);
174 String computeCurrentPackage() {
175 // This should never happen
176 throw new UnsupportedOperationException();
180 private final class SuffixSecondary extends Secondary {
181 private final AbstractQName packageSuffix;
183 SuffixSecondary(final Generator gen, final Member primaryClass, final String classSuffix,
184 final AbstractQName packageSuffix) {
185 super(gen, primaryClass, classSuffix);
186 this.packageSuffix = requireNonNull(packageSuffix);
190 String computeCurrentPackage() {
191 return classPrimary.currentPackage() + '.' + packageString(packageSuffix);
195 private final class AugmentSecondary extends Secondary {
196 private final SchemaNodeIdentifier packageSuffix;
198 AugmentSecondary(final AbstractAugmentGenerator gen, final Member primary, final String classSuffix,
199 final SchemaNodeIdentifier packageSuffix) {
200 super(gen, primary, classSuffix);
201 this.packageSuffix = requireNonNull(packageSuffix);
205 String computeCurrentPackage() {
206 final Iterator<QName> it = packageSuffix.getNodeIdentifiers().iterator();
208 final StringBuilder sb = new StringBuilder();
209 sb.append(packageString(it.next()));
210 while (it.hasNext()) {
211 sb.append('.').append(packageString(it.next()));
213 return sb.toString();
217 private final AbstractCompositeGenerator<?, ?> gen;
219 private List<Member> members = List.of();
220 private boolean solved;
222 CollisionDomain(final AbstractCompositeGenerator<?, ?> gen) {
223 this.gen = requireNonNull(gen);
226 @NonNull Member addPrefix(final Generator memberGen, final ClassNamingStrategy strategy) {
227 // Note that contrary to the method name, we are not adding the result to members
228 return new Prefix(memberGen, strategy);
231 @NonNull Member addPrimary(final Generator memberGen, final ClassNamingStrategy strategy) {
232 return addMember(new Primary(memberGen, strategy));
235 @NonNull Member addSecondary(final Generator memberGen, final Member primary, final String classSuffix) {
236 return addMember(new LeafSecondary(memberGen, primary, classSuffix));
239 @NonNull Member addSecondary(final InputGenerator memberGen, final Member primary) {
240 return addMember(new SuffixSecondary(memberGen, primary, BindingMapping.RPC_INPUT_SUFFIX,
241 memberGen.statement().argument()));
244 @NonNull Member addSecondary(final OutputGenerator memberGen, final Member primary) {
245 return addMember(new SuffixSecondary(memberGen, primary, BindingMapping.RPC_OUTPUT_SUFFIX,
246 memberGen.statement().argument()));
249 @NonNull Member addSecondary(final AbstractAugmentGenerator memberGen, final Member classPrimary,
250 final String classSuffix, final SchemaNodeIdentifier packageSuffix) {
251 return addMember(new AugmentSecondary(memberGen, classPrimary, classSuffix, packageSuffix));
255 * Naming child nodes is tricky.
257 * We map multiple YANG namespaces (see YangStatementNamespace) onto a single Java namespace
258 * (package/class names), hence we can have legal conflicts on same localName.
260 * Furthermore not all localNames are valid Java class/package identifiers, hence even non-equal localNames can
261 * conflict on their mapping.
263 * Final complication is that we allow user to control preferred name, or we generate one, and we try to come up
264 * with nice names like 'foo-bar' becoming FooBar and similar.
266 * In all cases we want to end up with cutest possible names while also never creating duplicates. For that we
267 * start with each child telling us their preferred name and we collect name->child mapping.
269 boolean findSolution() {
271 // Already solved, nothing to do
274 if (members.size() < 2) {
275 // Zero or one member: no conflict possible
280 boolean result = false;
282 // Construct mapping to discover any naming overlaps.
283 final Multimap<String, Member> toAssign = ArrayListMultimap.create();
284 for (Member member : members) {
285 toAssign.put(member.currentClass(), member);
288 // Deal with names which do not create a conflict. This is very simple and also very effective, we rarely
289 // run into conflicts.
290 final var it = toAssign.asMap().entrySet().iterator();
291 while (it.hasNext()) {
292 final Entry<String, Collection<Member>> entry = it.next();
293 final Collection<Member> assignees = entry.getValue();
294 if (assignees.size() == 1) {
299 // This looks counter-intuitive, but the idea is simple: the act of assigning a different strategy may end
300 // up creating conflicts where there were none -- including in this domain. Marking this bit allows us to
301 // react to such invalidation chains and retry the process.
303 if (!toAssign.isEmpty()) {
305 // We still have some assignments we need to resolve -- which means we need to change their strategy.
306 for (Collection<Member> conflicting : toAssign.asMap().values()) {
308 for (Member member : conflicting) {
309 if (!member.signalConflict()) {
313 checkState(remaining < 2, "Failed to solve %s due to naming conflict among %s", this, conflicting);
322 public String toString() {
323 return MoreObjects.toStringHelper(this).add("gen", gen).toString();
326 private @NonNull Member addMember(final @NonNull Member member) {
327 if (members.isEmpty()) {
328 members = new ArrayList<>();
334 private static @NonNull String packageString(final AbstractQName component) {
335 // Replace dashes with dots, as dashes are not allowed in package names
336 return component.getLocalName().replace('-', '.');