1 /*
2 * SPDX-FileCopyrightText: none
3 * SPDX-License-Identifier: CC0-1.0
4 */
5
6 package gov.nist.secauto.metaschema.core.qname;
7
8 import gov.nist.secauto.metaschema.core.metapath.StaticContext;
9 import gov.nist.secauto.metaschema.core.metapath.StaticMetapathException;
10 import gov.nist.secauto.metaschema.core.util.ObjectUtils;
11
12 import java.net.URI;
13 import java.util.Optional;
14
15 import javax.xml.XMLConstants;
16 import javax.xml.namespace.QName;
17
18 import edu.umd.cs.findbugs.annotations.NonNull;
19 import edu.umd.cs.findbugs.annotations.Nullable;
20
21 /**
22 * An efficient cache-backed representation of a qualified name.
23 * <p>
24 * This implementation uses an underlying integer-based cache to reduce the
25 * memory footprint of qualified names and namespaces by reusing instances with
26 * the same namespace and local name.
27 */
28 public interface IEnhancedQName extends Comparable<IEnhancedQName> {
29 /**
30 * Get the index position of the qualified name.
31 * <p>
32 * This value can be used in place of this object. The object can be retrieved
33 * using this index with the {@link #of(int)} method.
34 *
35 * @return the index position
36 */
37 int getIndexPosition();
38
39 /**
40 * Get the namespace part of the qualified name.
41 *
42 * @return the namespace
43 */
44 @NonNull
45 String getNamespace();
46
47 /**
48 * Get the namespace part of the qualified name.
49 *
50 * @return the namespace as a URI
51 */
52 @NonNull
53 URI getNamespaceAsUri();
54
55 /**
56 * Get the local part of the qualified name.
57 *
58 * @return the local name
59 */
60 @NonNull
61 String getLocalName();
62
63 /**
64 * Get an existing qualified name by looking up the cached entry using the
65 * provided index value.
66 *
67 * @param index
68 * the index value to lookup
69 * @return an optional containing the qualified name, if it exists
70 */
71 @SuppressWarnings("PMD.ShortMethodName")
72 @NonNull
73 static Optional<IEnhancedQName> of(int index) {
74 return EQNameFactory.instance().get(index);
75 }
76
77 /**
78 * Get a qualified name using the provided {@link QName} value.
79 *
80 * @param qname
81 * the qualified name to get
82 * @return the qualified name
83 */
84 @SuppressWarnings("PMD.ShortMethodName")
85 @NonNull
86 static IEnhancedQName of(@NonNull QName qname) {
87 return of(
88 ObjectUtils.notNull(qname.getNamespaceURI()),
89 ObjectUtils.notNull(qname.getLocalPart()));
90 }
91
92 /**
93 * Get a qualified name using the provided local name value with no namespace.
94 *
95 * @param localName
96 * the qualified name local part
97 * @return the qualified name
98 */
99 @SuppressWarnings("PMD.ShortMethodName")
100 @NonNull
101 static IEnhancedQName of(@NonNull String localName) {
102 return of("", localName);
103 }
104
105 /**
106 * Get a qualified name using the provided namespace and local name.
107 *
108 * @param namespace
109 * the qualified name namespace part
110 * @param localName
111 * the qualified name local part
112 * @return the qualified name
113 */
114 @SuppressWarnings("PMD.ShortMethodName")
115 @NonNull
116 static IEnhancedQName of(@NonNull URI namespace, @NonNull String localName) {
117 return of(ObjectUtils.notNull(namespace.toASCIIString()), localName);
118 }
119
120 /**
121 * Get a qualified name using the provided namespace and local name.
122 *
123 * @param namespace
124 * the qualified name namespace part
125 * @param localName
126 * the qualified name local part
127 * @return the qualified name
128 */
129 @SuppressWarnings("PMD.ShortMethodName")
130 @NonNull
131 static IEnhancedQName of(@NonNull String namespace, @NonNull String localName) {
132 return EQNameFactory.instance().newQName(namespace, localName);
133 }
134
135 /**
136 * Generate a qualified name for this QName.
137 * <p>
138 * This method uses prefixes associated with well-known namespaces, or will
139 * prepend the namespace if no prefix can be resolved.
140 *
141 * @return the extended qualified-name
142 */
143 @NonNull
144 default String toEQName() {
145 return toEQName((NamespaceToPrefixResolver) null);
146 }
147
148 /**
149 * Generate a qualified name for this QName, use a prefix provided by the
150 * resolver, or by prepending the namespace if no prefix can be resolved.
151 *
152 * @param resolver
153 * the resolver to use to lookup the prefix
154 * @return the extended qualified-name
155 */
156 @NonNull
157 default String toEQName(@Nullable NamespaceToPrefixResolver resolver) {
158 String namespace = getNamespace();
159 String prefix = namespace.isEmpty() ? null : WellKnown.getWellKnownPrefixForUri(namespace);
160 if (prefix == null && resolver != null) {
161 prefix = resolver.resolve(namespace);
162 }
163 return toEQName(namespace, getLocalName(), prefix);
164 }
165
166 /**
167 * Generate a qualified name for this QName. Use a prefix resolved from the
168 * provided static context, or prepend the namespace if no prefix can be
169 * resolved.
170 *
171 * @param staticContext
172 * the static context to use to lookup the prefix
173 * @return the extended qualified-name
174 */
175 @NonNull
176 default String toEQName(@NonNull StaticContext staticContext) {
177 String namespace = getNamespace();
178 String prefix = namespace.isEmpty() ? null : staticContext.lookupPrefixForNamespace(namespace);
179 return toEQName(namespace, getLocalName(), prefix);
180 }
181
182 @NonNull
183 private static String toEQName(
184 @NonNull String namespace,
185 @NonNull String localName,
186 @Nullable String prefix) {
187
188 StringBuilder builder = new StringBuilder();
189 if (prefix == null) {
190 if (!namespace.isEmpty()) {
191 builder.append("Q{")
192 .append(namespace)
193 .append('}');
194 }
195 } else {
196 builder.append(prefix)
197 .append(':');
198 }
199 return ObjectUtils.notNull(builder.append(localName)
200 .toString());
201 }
202
203 /**
204 * Generate a {@link QName} without a namespace prefix.
205 *
206 * @return the name
207 */
208 @NonNull
209 default QName toQName() {
210 return toQName(XMLConstants.DEFAULT_NS_PREFIX);
211 }
212
213 /**
214 * Generate a {@link QName} using the provided namespace prefix.
215 *
216 * @param prefix
217 * the prefix to use
218 * @return the name
219 */
220 @NonNull
221 default QName toQName(@NonNull String prefix) {
222 return new QName(getNamespace(), getLocalName(), prefix);
223 }
224
225 /**
226 * Provides a callback for resolving namespace prefixes.
227 */
228 @FunctionalInterface
229 interface NamespaceToPrefixResolver {
230 /**
231 * Get the URI string for the provided namespace prefix.
232 *
233 * @param namespace
234 * the namespace URI
235 * @return the associated prefix or {@code null} if no prefix is associated
236 */
237 @Nullable
238 String resolve(@NonNull String namespace);
239 }
240
241 /**
242 * Provides a callback for resolving namespace prefixes.
243 */
244 @FunctionalInterface
245 interface PrefixToNamespaceResolver {
246 /**
247 * Get the URI string for the provided namespace prefix.
248 *
249 * @param name
250 * the name to resolve
251 * @return the URI string or {@code null} if the prefix is unbound
252 * @throws StaticMetapathException
253 * with the code {@link StaticMetapathException#PREFIX_NOT_EXPANDABLE}
254 * if a non-empty prefix is provided
255 */
256 @NonNull
257 IEnhancedQName resolve(@NonNull String name);
258 }
259
260 }