Use YANG-derived prefixes
[yangtools.git] / codec / yang-data-codec-xml / src / main / java / org / opendaylight / yangtools / yang / data / codec / xml / NamespacePrefixes.java
1 /*
2  * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
3  *
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
7  */
8 package org.opendaylight.yangtools.yang.data.codec.xml;
9
10 import static java.util.Objects.requireNonNull;
11
12 import com.google.common.annotations.VisibleForTesting;
13 import com.google.common.collect.BiMap;
14 import com.google.common.collect.HashBiMap;
15 import java.util.Comparator;
16 import java.util.List;
17 import java.util.Map.Entry;
18 import java.util.stream.Collectors;
19 import javax.xml.XMLConstants;
20 import javax.xml.namespace.NamespaceContext;
21 import org.eclipse.jdt.annotation.NonNull;
22 import org.opendaylight.yangtools.concepts.Mutable;
23 import org.opendaylight.yangtools.yang.common.XMLNamespace;
24
25 /**
26  * Interface to prefix assignment based on a {@link NamespaceContext} and advice from {@link PreferredPrefixes}.
27  */
28 final class NamespacePrefixes implements Mutable {
29     // 32 characters, carefully chosen
30     @VisibleForTesting
31     static final String LOOKUP = "abcdefghiknoprstABCDEFGHIKNOPRST";
32     @VisibleForTesting
33     static final int SHIFT = 5;
34     private static final int MASK = 0x1f;
35
36     private int counter = 0;
37
38     // BiMap to make values lookup faster
39     private final BiMap<XMLNamespace, String> emittedPrefixes = HashBiMap.create();
40     private final PreferredPrefixes pref;
41     private final NamespaceContext context;
42
43     NamespacePrefixes(final PreferredPrefixes pref, final NamespaceContext context) {
44         this.pref = requireNonNull(pref);
45         this.context = context;
46     }
47
48     List<Entry<XMLNamespace, String>> emittedPrefixes() {
49         return emittedPrefixes.entrySet().stream()
50             // Order by prefix
51             .sorted(Comparator.comparing(Entry::getValue))
52             .collect(Collectors.toList());
53     }
54
55     @NonNull String encodePrefix(final XMLNamespace namespace) {
56         var prefix = emittedPrefixes.get(namespace);
57         if (prefix != null) {
58             return prefix;
59         }
60
61         if (context != null) {
62             prefix = context.getPrefix(namespace.toString());
63             if (prefix != null) {
64                 return prefix;
65             }
66         }
67
68         prefix = pref.prefixForNamespace(namespace);
69         if (prefix == null) {
70             prefix = encode(counter++);
71         }
72
73         while (alreadyUsedPrefix(prefix)) {
74             prefix = encode(counter++);
75         }
76
77         emittedPrefixes.put(namespace, prefix);
78         return prefix;
79     }
80
81     private boolean alreadyUsedPrefix(final String prefix) {
82         if (context == null) {
83             return false;
84         }
85
86         // It seems JDK8 is violating the API contract of NamespaceContext by returning null for unbound prefixes,
87         // rather than specified NULL_NS_URI. Work this around by checking explicitly for null.
88         final var str = context.getNamespaceURI(prefix);
89         return str != null && !XMLConstants.NULL_NS_URI.equals(str);
90     }
91
92     @VisibleForTesting
93     static @NonNull String encode(int num) {
94         final var sb = new StringBuilder();
95
96         do {
97             sb.append(LOOKUP.charAt(num & MASK));
98             num >>>= SHIFT;
99         } while (num != 0);
100
101         return sb.reverse().toString();
102     }
103 }