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 java.util.Objects.requireNonNull;
14 import com.google.common.base.MoreObjects;
15 import com.google.common.base.MoreObjects.ToStringHelper;
16 import com.google.common.collect.ArrayListMultimap;
17 import com.google.common.collect.Multimap;
18 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
19 import java.util.ArrayList;
20 import java.util.Collection;
21 import java.util.Iterator;
22 import java.util.List;
23 import java.util.Map.Entry;
24 import org.eclipse.jdt.annotation.NonNull;
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 String currentPackage;
32 private String currentClass;
34 final @NonNull String currentClass() {
35 if (currentClass == null) {
36 currentClass = computeCurrentClass();
41 final @NonNull String currentPackage() {
42 if (currentPackage == null) {
43 currentPackage = computeCurrentPackage();
45 return currentPackage;
48 abstract String computeCurrentClass();
50 abstract String computeCurrentPackage();
52 boolean signalConflict() {
55 currentPackage = null;
60 public final String toString() {
61 return addToStringAttributes(MoreObjects.toStringHelper(this).omitNullValues()).toString();
64 ToStringHelper addToStringAttributes(final ToStringHelper helper) {
65 return helper.add("class", currentClass).add("package", currentPackage);
69 private class Primary extends Member {
70 private ClassNamingStrategy strategy;
71 private List<Secondary> secondaries = List.of();
73 Primary(final ClassNamingStrategy strategy) {
74 this.strategy = requireNonNull(strategy);
78 final String computeCurrentClass() {
79 return strategy.simpleClassName();
83 final String computeCurrentPackage() {
84 return packageString(strategy.nodeIdentifier());
87 final void addSecondary(final Secondary secondary) {
88 if (secondaries.isEmpty()) {
89 secondaries = new ArrayList<>();
91 secondaries.add(requireNonNull(secondary));
95 final boolean signalConflict() {
96 final ClassNamingStrategy newStrategy = strategy.fallback();
97 if (newStrategy == null) {
101 strategy = newStrategy;
102 super.signalConflict();
103 for (Secondary secondary : secondaries) {
104 secondary.primaryConflict();
110 final ToStringHelper addToStringAttributes(final ToStringHelper helper) {
111 return super.addToStringAttributes(helper.add("strategy", strategy));
115 private final class Prefix extends Primary {
116 Prefix(final ClassNamingStrategy strategy) {
121 private abstract class Secondary extends Member {
122 private final String classSuffix;
123 final Primary classPrimary;
125 Secondary(final Primary primary, final String classSuffix) {
126 this.classPrimary = requireNonNull(primary);
127 this.classSuffix = requireNonNull(classSuffix);
128 primary.addSecondary(this);
132 final String computeCurrentClass() {
133 return classPrimary.currentClass() + classSuffix;
137 final boolean signalConflict() {
138 return classPrimary.signalConflict();
141 final void primaryConflict() {
142 super.signalConflict();
146 private final class LeafSecondary extends Secondary {
147 LeafSecondary(final Primary classPrimary, final String classSuffix) {
148 super(classPrimary, classSuffix);
152 String computeCurrentPackage() {
153 // This should never happen
154 throw new UnsupportedOperationException();
158 private final class SuffixSecondary extends Secondary {
159 private final AbstractQName packageSuffix;
161 SuffixSecondary(final Primary primaryClass, final String classSuffix, final AbstractQName packageSuffix) {
162 super(primaryClass, classSuffix);
163 this.packageSuffix = requireNonNull(packageSuffix);
167 String computeCurrentPackage() {
168 return classPrimary.currentPackage() + '.' + packageString(packageSuffix);
172 private final class AugmentSecondary extends Secondary {
173 private final SchemaNodeIdentifier packageSuffix;
175 AugmentSecondary(final Primary primary, final String classSuffix, final SchemaNodeIdentifier packageSuffix) {
176 super(primary, classSuffix);
177 this.packageSuffix = requireNonNull(packageSuffix);
181 String computeCurrentPackage() {
182 final Iterator<QName> it = packageSuffix.getNodeIdentifiers().iterator();
184 final StringBuilder sb = new StringBuilder();
185 sb.append(packageString(it.next()));
186 while (it.hasNext()) {
187 sb.append('.').append(packageString(it.next()));
189 return sb.toString();
193 private List<Member> members = List.of();
194 private boolean solved;
196 @NonNull Member addPrefix(final ClassNamingStrategy strategy) {
197 // Note that contrary to the method name, we are not adding the result to members
198 return new Prefix(strategy);
201 @NonNull Member addPrimary(final ClassNamingStrategy strategy) {
202 return addMember(new Primary(strategy));
205 @NonNull Member addSecondary(final Member primary, final String classSuffix) {
206 return addMember(new LeafSecondary(castPrimary(primary), classSuffix));
209 @NonNull Member addSecondary(final Member primary, final String classSuffix, final AbstractQName packageSuffix) {
210 return addMember(new SuffixSecondary(castPrimary(primary), classSuffix, packageSuffix));
213 @NonNull Member addSecondary(final Member classPrimary, final String classSuffix,
214 final SchemaNodeIdentifier packageSuffix) {
215 return addMember(new AugmentSecondary(castPrimary(classPrimary), classSuffix, packageSuffix));
218 private static @NonNull Primary castPrimary(final Member primary) {
219 verify(primary instanceof Primary, "Unexpected primary %s", primary);
220 return (Primary) primary;
224 * Naming child nodes is tricky.
226 * We map multiple YANG namespaces (see YangStatementNamespace) onto a single Java namespace
227 * (package/class names), hence we can have legal conflicts on same localName.
229 * Furthermore not all localNames are valid Java class/package identifiers, hence even non-equal localNames can
230 * conflict on their mapping.
232 * Final complication is that we allow user to control preferred name, or we generate one, and we try to come up
233 * with nice names like 'foo-bar' becoming FooBar and similar.
235 * In all cases we want to end up with cutest possible names while also never creating duplicates. For that we
236 * start with each child telling us their preferred name and we collect name->child mapping.
238 boolean findSolution() {
240 // Already solved, nothing to do
243 if (members.size() < 2) {
244 // Zero or one member: no conflict possible
249 boolean result = false;
251 // Construct mapping to discover any naming overlaps.
252 final Multimap<String, Member> toAssign = ArrayListMultimap.create();
253 for (Member member : members) {
254 toAssign.put(member.currentClass(), member);
257 // Deal with names which do not create a conflict. This is very simple and also very effective, we rarely
258 // run into conflicts.
259 final var it = toAssign.asMap().entrySet().iterator();
260 while (it.hasNext()) {
261 final Entry<String, Collection<Member>> entry = it.next();
262 final Collection<Member> assignees = entry.getValue();
263 if (assignees.size() == 1) {
268 // This looks counter-intuitive, but the idea is simple: the act of assigning a different strategy may end
269 // up creating conflicts where there were none -- including in this domain. Marking this bit allows us to
270 // react to such invalidation chains and retry the process.
272 if (!toAssign.isEmpty()) {
274 // We still have some assignments we need to resolve -- which means we need to change their strategy.
275 for (Collection<Member> conflicting : toAssign.asMap().values()) {
277 for (Member member : conflicting) {
278 if (!member.signalConflict()) {
282 checkState(remaining < 2, "Failed to resolve members %s", conflicting);
290 @SuppressFBWarnings(value = "UPM_UNCALLED_PRIVATE_METHOD",
291 justification = "https://github.com/spotbugs/spotbugs/issues/811")
292 private @NonNull Member addMember(final @NonNull Member member) {
293 if (members.isEmpty()) {
294 members = new ArrayList<>();
300 @SuppressFBWarnings(value = "UPM_UNCALLED_PRIVATE_METHOD",
301 justification = "https://github.com/spotbugs/spotbugs/issues/811")
302 private static @NonNull String packageString(final AbstractQName component) {
303 // Replace dashes with dots, as dashes are not allowed in package names
304 return component.getLocalName().replace('-', '.');