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.Maps;
15 import java.util.Optional;
16 import java.util.concurrent.ConcurrentHashMap;
17 import java.util.concurrent.ConcurrentMap;
18 import org.eclipse.jdt.annotation.NonNull;
19 import org.eclipse.jdt.annotation.Nullable;
20 import org.opendaylight.yangtools.yang.common.XMLNamespace;
21 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
24 * Prefixes preferred by an {@link EffectiveModelContext}. This acts as an advisory to {@link NamespacePrefixes} for
25 * picking namespace prefixes. This works with IETF guidelines, which prefer XML prefix names coming from {@code prefix}
26 * statement's argument. This, unfortunately, is not sufficient, as these are not guaranteed to be unique, but we deal
27 * with those ambiguities.
29 abstract sealed class PreferredPrefixes {
30 private static final class Precomputed extends PreferredPrefixes {
31 static final @NonNull Precomputed EMPTY = new Precomputed(Map.of());
33 private final Map<XMLNamespace, String> mappings;
35 Precomputed(final Map<XMLNamespace, String> mappings) {
36 this.mappings = requireNonNull(mappings);
40 String prefixForNamespace(final XMLNamespace namespace) {
41 return mappings.get(namespace);
45 Map<XMLNamespace, ?> mappings() {
50 static final class Shared extends PreferredPrefixes {
51 private final ConcurrentMap<XMLNamespace, Optional<String>> mappings = new ConcurrentHashMap<>();
52 private final EffectiveModelContext modelContext;
54 Shared(final EffectiveModelContext modelContext) {
55 this.modelContext = requireNonNull(modelContext);
59 String prefixForNamespace(final XMLNamespace namespace) {
60 final var existing = mappings.get(namespace);
61 if (existing != null) {
62 return existing.orElse(null);
65 final var modules = modelContext.findModuleStatements(namespace).iterator();
66 // Note: we are not caching anything if we do not find the module
67 return modules.hasNext() ? loadPrefix(namespace, modules.next().prefix().argument()) : null;
71 * Completely populate known mappings and return an optimized version equivalent of this object.
73 * @return A pre-computed {@link PreferredPrefixes} instance
75 @NonNull PreferredPrefixes toPrecomputed() {
76 for (var module : modelContext.getModuleStatements().values()) {
77 prefixForNamespace(module.namespace().argument());
79 return new Precomputed(Map.copyOf(
80 Maps.transformValues(Maps.filterValues(mappings, Optional::isPresent), Optional::orElseThrow)));
84 Map<XMLNamespace, ?> mappings() {
88 private @Nullable String loadPrefix(final XMLNamespace namespace, final String prefix) {
89 final var mapping = isValidMapping(namespace, prefix) ? Optional.of(prefix) : Optional.<String>empty();
90 final var raced = mappings.putIfAbsent(namespace, mapping);
91 return (raced != null ? raced : mapping).orElse(null);
94 // Validate that all modules which have the same prefix have also the name namespace
95 private boolean isValidMapping(final XMLNamespace namespace, final String prefix) {
96 if (startsWithXml(prefix)) {
99 for (var module : modelContext.getModuleStatements().values()) {
100 if (prefix.equals(module.prefix().argument()) && !namespace.equals(module.namespace().argument())) {
107 // https://www.w3.org/TR/xml-names/#xmlReserved
108 private static boolean startsWithXml(final String prefix) {
109 if (prefix.length() < 3) {
112 final var first = prefix.charAt(0);
113 if (first != 'x' && first != 'X') {
116 final var second = prefix.charAt(1);
117 if (second != 'm' && second != 'M') {
120 final var third = prefix.charAt(2);
121 return third == 'l' || third == 'L';
125 private PreferredPrefixes() {
129 static @NonNull PreferredPrefixes empty() {
130 return Precomputed.EMPTY;
133 abstract @Nullable String prefixForNamespace(@NonNull XMLNamespace namespace);
136 public final String toString() {
137 return MoreObjects.toStringHelper(this).add("mappings", mappings()).toString();
140 abstract Map<XMLNamespace, ?> mappings();