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 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 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 && strategy.nodeIdentifier().getLocalName().equals(
126 ((Primary) other).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 class Secondary extends Member {
137 private final String classSuffix;
138 final 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 if (other instanceof Secondary) {
164 final Secondary sec = (Secondary) other;
165 return classPrimary.equalRoot(sec.classPrimary) && classSuffix.equals(sec.classSuffix);
171 private final class LeafSecondary extends Secondary {
172 LeafSecondary(final Generator gen, final Member classPrimary, final String classSuffix) {
173 super(gen, classPrimary, classSuffix);
177 String computeCurrentPackage() {
178 // This should never happen
179 throw new UnsupportedOperationException();
183 private final class SuffixSecondary extends Secondary {
184 private final AbstractQName packageSuffix;
186 SuffixSecondary(final Generator gen, final Member primaryClass, final String classSuffix,
187 final AbstractQName packageSuffix) {
188 super(gen, primaryClass, classSuffix);
189 this.packageSuffix = requireNonNull(packageSuffix);
193 String computeCurrentPackage() {
194 return classPrimary.currentPackage() + '.' + packageString(packageSuffix);
198 private final class AugmentSecondary extends Secondary {
199 private final SchemaNodeIdentifier packageSuffix;
201 AugmentSecondary(final AbstractAugmentGenerator gen, final Member primary, final String classSuffix,
202 final SchemaNodeIdentifier packageSuffix) {
203 super(gen, primary, classSuffix);
204 this.packageSuffix = requireNonNull(packageSuffix);
208 String computeCurrentPackage() {
209 final Iterator<QName> it = packageSuffix.getNodeIdentifiers().iterator();
211 final StringBuilder sb = new StringBuilder();
212 sb.append(packageString(it.next()));
213 while (it.hasNext()) {
214 sb.append('.').append(packageString(it.next()));
216 return sb.toString();
220 private final AbstractCompositeGenerator<?, ?> gen;
222 private List<Member> members = List.of();
223 private boolean solved;
225 CollisionDomain(final AbstractCompositeGenerator<?, ?> gen) {
226 this.gen = requireNonNull(gen);
229 @NonNull Member addPrefix(final Generator memberGen, final ClassNamingStrategy strategy) {
230 // Note that contrary to the method name, we are not adding the result to members
231 return new Prefix(memberGen, strategy);
234 @NonNull Member addPrimary(final Generator memberGen, final ClassNamingStrategy strategy) {
235 return addMember(new Primary(memberGen, strategy));
238 @NonNull Member addSecondary(final Generator memberGen, final Member primary, final String classSuffix) {
239 return addMember(new LeafSecondary(memberGen, primary, classSuffix));
242 @NonNull Member addSecondary(final RpcInputGenerator memberGen, final Member primary) {
243 return addMember(new SuffixSecondary(memberGen, primary, BindingMapping.RPC_INPUT_SUFFIX,
244 memberGen.statement().argument()));
247 @NonNull Member addSecondary(final RpcOutputGenerator memberGen, final Member primary) {
248 return addMember(new SuffixSecondary(memberGen, primary, BindingMapping.RPC_OUTPUT_SUFFIX,
249 memberGen.statement().argument()));
252 @NonNull Member addSecondary(final AbstractAugmentGenerator memberGen, final Member classPrimary,
253 final String classSuffix, final SchemaNodeIdentifier packageSuffix) {
254 return addMember(new AugmentSecondary(memberGen, classPrimary, classSuffix, packageSuffix));
258 * Naming child nodes is tricky.
260 * We map multiple YANG namespaces (see YangStatementNamespace) onto a single Java namespace
261 * (package/class names), hence we can have legal conflicts on same localName.
263 * Furthermore not all localNames are valid Java class/package identifiers, hence even non-equal localNames can
264 * conflict on their mapping.
266 * Final complication is that we allow user to control preferred name, or we generate one, and we try to come up
267 * with nice names like 'foo-bar' becoming FooBar and similar.
269 * In all cases we want to end up with cutest possible names while also never creating duplicates. For that we
270 * start with each child telling us their preferred name and we collect name->child mapping.
272 boolean findSolution() {
274 // Already solved, nothing to do
277 if (members.size() < 2) {
278 // Zero or one member: no conflict possible
283 boolean result = false;
285 // Construct mapping to discover any naming overlaps.
286 final Multimap<String, Member> toAssign = ArrayListMultimap.create();
287 for (Member member : members) {
288 toAssign.put(member.currentClass(), member);
291 // Deal with names which do not create a conflict. This is very simple and also very effective, we rarely
292 // run into conflicts.
293 final var it = toAssign.asMap().entrySet().iterator();
294 while (it.hasNext()) {
295 final Entry<String, Collection<Member>> entry = it.next();
296 final Collection<Member> assignees = entry.getValue();
297 if (assignees.size() == 1) {
302 // This looks counter-intuitive, but the idea is simple: the act of assigning a different strategy may end
303 // up creating conflicts where there were none -- including in this domain. Marking this bit allows us to
304 // react to such invalidation chains and retry the process.
306 if (!toAssign.isEmpty()) {
308 // We still have some assignments we need to resolve -- which means we need to change their strategy.
309 for (Collection<Member> conflicting : toAssign.asMap().values()) {
311 for (Member member : conflicting) {
312 if (!member.signalConflict()) {
316 checkState(remaining < 2, "Failed to solve %s due to naming conflict among %s", this, conflicting);
325 public String toString() {
326 return MoreObjects.toStringHelper(this).add("gen", gen).toString();
329 private @NonNull Member addMember(final @NonNull Member member) {
330 if (members.isEmpty()) {
331 members = new ArrayList<>();
337 private static @NonNull String packageString(final AbstractQName component) {
338 // Replace dashes with dots, as dashes are not allowed in package names
339 return component.getLocalName().replace('-', '.');