2 * Copyright (c) 2023 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.yangtools.yang.data.codec.xml;
10 import static java.util.Objects.requireNonNull;
12 import com.google.common.base.MoreObjects;
13 import com.google.common.collect.ImmutableBiMap;
14 import com.google.common.collect.Maps;
16 import java.util.Optional;
17 import java.util.concurrent.ConcurrentHashMap;
18 import java.util.concurrent.ConcurrentMap;
19 import java.util.stream.Stream;
20 import org.eclipse.jdt.annotation.NonNull;
21 import org.eclipse.jdt.annotation.Nullable;
22 import org.opendaylight.yangtools.yang.common.XMLNamespace;
23 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
24 import org.opendaylight.yangtools.yang.model.api.stmt.ModuleEffectiveStatement;
27 * Prefixes preferred by an {@link EffectiveModelContext}. This acts as an advisory to {@link NamespacePrefixes} for
28 * picking namespace prefixes. This works with IETF guidelines, which prefer XML prefix names coming from {@code prefix}
29 * statement's argument. This, unfortunately, is not sufficient, as these are not guaranteed to be unique, but we deal
30 * with those ambiguities.
32 abstract sealed class PreferredPrefixes {
33 static final class Precomputed extends PreferredPrefixes {
34 private final ImmutableBiMap<XMLNamespace, String> mappings;
36 Precomputed(final Map<XMLNamespace, String> mappings) {
37 this.mappings = ImmutableBiMap.copyOf(mappings);
41 String prefixForNamespace(final XMLNamespace namespace) {
42 return mappings.get(namespace);
46 boolean isUsed(final String prefix) {
47 return mappings.inverse().containsKey(prefix);
51 Map<XMLNamespace, ?> mappings() {
56 static final class Shared extends PreferredPrefixes {
57 private final ConcurrentMap<XMLNamespace, Optional<String>> mappings = new ConcurrentHashMap<>();
58 private final EffectiveModelContext modelContext;
60 Shared(final EffectiveModelContext modelContext) {
61 this.modelContext = requireNonNull(modelContext);
65 String prefixForNamespace(final XMLNamespace namespace) {
66 final var existing = mappings.get(namespace);
67 if (existing != null) {
68 return existing.orElse(null);
71 final var modules = modelContext.findModuleStatements(namespace).iterator();
72 // Note: we are not caching anything if we do not find the module
73 return modules.hasNext() ? loadPrefix(namespace, modules.next().prefix().argument()) : null;
77 boolean isUsed(final String prefix) {
78 return modulesForPrefix(prefix).findAny().isPresent();
82 * Completely populate known mappings and return an optimized version equivalent of this object.
84 * @return A pre-computed {@link PreferredPrefixes} instance
86 @NonNull PreferredPrefixes toPrecomputed() {
87 for (var module : modelContext.getModuleStatements().values()) {
88 prefixForNamespace(module.namespace().argument());
90 return new Precomputed(
91 Maps.transformValues(Maps.filterValues(mappings, Optional::isPresent), Optional::orElseThrow));
95 Map<XMLNamespace, ?> mappings() {
99 private @Nullable String loadPrefix(final XMLNamespace namespace, final String prefix) {
100 final var mapping = isValidMapping(namespace, prefix) ? Optional.of(prefix) : Optional.<String>empty();
101 final var raced = mappings.putIfAbsent(namespace, mapping);
102 return (raced != null ? raced : mapping).orElse(null);
105 // Validate that all modules which have the same prefix have also the name namespace
106 private boolean isValidMapping(final XMLNamespace namespace, final String prefix) {
107 return !startsWithXml(prefix) && modulesForPrefix(prefix)
108 .allMatch(module -> namespace.equals(module.namespace().argument()));
111 private Stream<ModuleEffectiveStatement> modulesForPrefix(final String prefix) {
112 return modelContext.getModuleStatements().values().stream()
113 .filter(module -> prefix.equals(module.prefix().argument()));
116 // https://www.w3.org/TR/xml-names/#xmlReserved
117 private static boolean startsWithXml(final String prefix) {
118 if (prefix.length() < 3) {
121 final var first = prefix.charAt(0);
122 if (first != 'x' && first != 'X') {
125 final var second = prefix.charAt(1);
126 if (second != 'm' && second != 'M') {
129 final var third = prefix.charAt(2);
130 return third == 'l' || third == 'L';
134 private PreferredPrefixes() {
138 abstract @Nullable String prefixForNamespace(@NonNull XMLNamespace namespace);
140 abstract boolean isUsed(String prefix);
143 public final String toString() {
144 return MoreObjects.toStringHelper(this).add("mappings", mappings()).toString();
147 abstract Map<XMLNamespace, ?> mappings();