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 edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
18 import java.util.ArrayList;
19 import java.util.Collection;
20 import java.util.Iterator;
21 import java.util.List;
22 import java.util.Map.Entry;
23 import org.eclipse.jdt.annotation.NonNull;
24 import org.opendaylight.mdsal.binding.spec.naming.BindingMapping;
25 import org.opendaylight.yangtools.yang.common.AbstractQName;
26 import org.opendaylight.yangtools.yang.common.QName;
27 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier;
29 final class CollisionDomain {
30 abstract class Member {
31 private final Generator gen;
33 private List<Secondary> secondaries = List.of();
34 private String currentPackage;
35 private String currentClass;
37 Member(final Generator gen) {
38 this.gen = requireNonNull(gen);
41 final void addSecondary(final Secondary secondary) {
42 if (secondaries.isEmpty()) {
43 secondaries = new ArrayList<>();
45 secondaries.add(requireNonNull(secondary));
48 final @NonNull String currentClass() {
49 if (currentClass == null) {
50 currentClass = computeCurrentClass();
55 final @NonNull String currentPackage() {
56 if (currentPackage == null) {
57 currentPackage = computeCurrentPackage();
59 return currentPackage;
62 abstract boolean equalRoot(@NonNull Member other);
64 abstract String computeCurrentClass();
66 abstract String computeCurrentPackage();
68 boolean signalConflict() {
71 currentPackage = null;
73 for (Secondary secondary : secondaries) {
74 secondary.primaryConflict();
81 public final String toString() {
82 return addToStringAttributes(MoreObjects.toStringHelper(this).omitNullValues()).toString();
85 ToStringHelper addToStringAttributes(final ToStringHelper helper) {
86 return helper.add("gen", gen).add("class", currentClass).add("package", currentPackage);
90 private class Primary extends Member {
91 private ClassNamingStrategy strategy;
93 Primary(final Generator gen, final ClassNamingStrategy strategy) {
95 this.strategy = requireNonNull(strategy);
99 final String computeCurrentClass() {
100 return strategy.simpleClassName();
104 final String computeCurrentPackage() {
105 return packageString(strategy.nodeIdentifier());
109 final boolean signalConflict() {
110 final ClassNamingStrategy newStrategy = strategy.fallback();
111 if (newStrategy == null) {
115 strategy = newStrategy;
116 return super.signalConflict();
120 final ToStringHelper addToStringAttributes(final ToStringHelper helper) {
121 return super.addToStringAttributes(helper.add("strategy", strategy));
125 boolean equalRoot(final Member other) {
126 return other instanceof Primary && strategy.nodeIdentifier().getLocalName().equals(
127 ((Primary) other).strategy.nodeIdentifier().getLocalName());
131 private final class Prefix extends Primary {
132 Prefix(final Generator gen, final ClassNamingStrategy strategy) {
133 super(gen, strategy);
137 private abstract class Secondary extends Member {
138 private final String classSuffix;
139 final Member classPrimary;
141 Secondary(final Generator gen, final Member primary, final String classSuffix) {
143 classPrimary = requireNonNull(primary);
144 this.classSuffix = requireNonNull(classSuffix);
145 primary.addSecondary(this);
149 final String computeCurrentClass() {
150 return classPrimary.currentClass() + classSuffix;
154 final boolean signalConflict() {
155 return classPrimary.signalConflict();
158 final void primaryConflict() {
159 super.signalConflict();
163 final boolean equalRoot(final Member other) {
164 if (other instanceof Secondary) {
165 final Secondary sec = (Secondary) other;
166 return classPrimary.equalRoot(sec.classPrimary) && classSuffix.equals(sec.classSuffix);
172 private final class LeafSecondary extends Secondary {
173 LeafSecondary(final Generator gen, final Member classPrimary, final String classSuffix) {
174 super(gen, classPrimary, classSuffix);
178 String computeCurrentPackage() {
179 // This should never happen
180 throw new UnsupportedOperationException();
184 private final class SuffixSecondary extends Secondary {
185 private final AbstractQName packageSuffix;
187 SuffixSecondary(final Generator gen, final Member primaryClass, final String classSuffix,
188 final AbstractQName packageSuffix) {
189 super(gen, primaryClass, classSuffix);
190 this.packageSuffix = requireNonNull(packageSuffix);
194 String computeCurrentPackage() {
195 return classPrimary.currentPackage() + '.' + packageString(packageSuffix);
199 private final class AugmentSecondary extends Secondary {
200 private final SchemaNodeIdentifier packageSuffix;
202 AugmentSecondary(final AbstractAugmentGenerator gen, final Member primary, final String classSuffix,
203 final SchemaNodeIdentifier packageSuffix) {
204 super(gen, primary, classSuffix);
205 this.packageSuffix = requireNonNull(packageSuffix);
209 String computeCurrentPackage() {
210 final Iterator<QName> it = packageSuffix.getNodeIdentifiers().iterator();
212 final StringBuilder sb = new StringBuilder();
213 sb.append(packageString(it.next()));
214 while (it.hasNext()) {
215 sb.append('.').append(packageString(it.next()));
217 return sb.toString();
221 private final AbstractCompositeGenerator<?, ?> gen;
223 private List<Member> members = List.of();
224 private boolean solved;
226 CollisionDomain(final AbstractCompositeGenerator<?, ?> gen) {
227 this.gen = requireNonNull(gen);
230 @NonNull Member addPrefix(final Generator memberGen, final ClassNamingStrategy strategy) {
231 // Note that contrary to the method name, we are not adding the result to members
232 return new Prefix(memberGen, strategy);
235 @NonNull Member addPrimary(final Generator memberGen, final ClassNamingStrategy strategy) {
236 return addMember(new Primary(memberGen, strategy));
239 @NonNull Member addSecondary(final Generator memberGen, final Member primary, final String classSuffix) {
240 return addMember(new LeafSecondary(memberGen, primary, classSuffix));
243 @NonNull Member addSecondary(final RpcInputGenerator memberGen, final Member primary) {
244 return addMember(new SuffixSecondary(memberGen, primary, BindingMapping.RPC_INPUT_SUFFIX,
245 memberGen.statement().argument()));
248 @NonNull Member addSecondary(final RpcOutputGenerator memberGen, final Member primary) {
249 return addMember(new SuffixSecondary(memberGen, primary, BindingMapping.RPC_OUTPUT_SUFFIX,
250 memberGen.statement().argument()));
253 @NonNull Member addSecondary(final AbstractAugmentGenerator memberGen, final Member classPrimary,
254 final String classSuffix, final SchemaNodeIdentifier packageSuffix) {
255 return addMember(new AugmentSecondary(memberGen, classPrimary, classSuffix, packageSuffix));
259 * Naming child nodes is tricky.
261 * We map multiple YANG namespaces (see YangStatementNamespace) onto a single Java namespace
262 * (package/class names), hence we can have legal conflicts on same localName.
264 * Furthermore not all localNames are valid Java class/package identifiers, hence even non-equal localNames can
265 * conflict on their mapping.
267 * Final complication is that we allow user to control preferred name, or we generate one, and we try to come up
268 * with nice names like 'foo-bar' becoming FooBar and similar.
270 * In all cases we want to end up with cutest possible names while also never creating duplicates. For that we
271 * start with each child telling us their preferred name and we collect name->child mapping.
273 boolean findSolution() {
275 // Already solved, nothing to do
278 if (members.size() < 2) {
279 // Zero or one member: no conflict possible
284 boolean result = false;
286 // Construct mapping to discover any naming overlaps.
287 final Multimap<String, Member> toAssign = ArrayListMultimap.create();
288 for (Member member : members) {
289 toAssign.put(member.currentClass(), member);
292 // Deal with names which do not create a conflict. This is very simple and also very effective, we rarely
293 // run into conflicts.
294 final var it = toAssign.asMap().entrySet().iterator();
295 while (it.hasNext()) {
296 final Entry<String, Collection<Member>> entry = it.next();
297 final Collection<Member> assignees = entry.getValue();
298 if (assignees.size() == 1) {
303 // This looks counter-intuitive, but the idea is simple: the act of assigning a different strategy may end
304 // up creating conflicts where there were none -- including in this domain. Marking this bit allows us to
305 // react to such invalidation chains and retry the process.
307 if (!toAssign.isEmpty()) {
309 // We still have some assignments we need to resolve -- which means we need to change their strategy.
310 for (Collection<Member> conflicting : toAssign.asMap().values()) {
312 for (Member member : conflicting) {
313 if (!member.signalConflict()) {
317 checkState(remaining < 2, "Failed to solve %s due to naming conflict among %s", this, conflicting);
326 public String toString() {
327 return MoreObjects.toStringHelper(this).add("gen", gen).toString();
330 @SuppressFBWarnings(value = "UPM_UNCALLED_PRIVATE_METHOD",
331 justification = "https://github.com/spotbugs/spotbugs/issues/811")
332 private @NonNull Member addMember(final @NonNull Member member) {
333 if (members.isEmpty()) {
334 members = new ArrayList<>();
340 @SuppressFBWarnings(value = "UPM_UNCALLED_PRIVATE_METHOD",
341 justification = "https://github.com/spotbugs/spotbugs/issues/811")
342 private static @NonNull String packageString(final AbstractQName component) {
343 // Replace dashes with dots, as dashes are not allowed in package names
344 return component.getLocalName().replace('-', '.');