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 @NonNull
72 static Optional<IEnhancedQName> of(int index) {
73 return EQNameFactory.instance().get(index);
74 }
75
76 /**
77 * Get a qualified name using the provided {@link QName} value.
78 *
79 * @param qname
80 * the qualified name to get
81 * @return the qualified name
82 */
83 @NonNull
84 static IEnhancedQName of(@NonNull QName qname) {
85 return of(
86 ObjectUtils.notNull(qname.getNamespaceURI()),
87 ObjectUtils.notNull(qname.getLocalPart()));
88 }
89
90 /**
91 * Get a qualified name using the provided local name value with no namespace.
92 *
93 * @param localName
94 * the qualified name local part
95 * @return the qualified name
96 */
97 @NonNull
98 static IEnhancedQName of(@NonNull String localName) {
99 return of("", localName);
100 }
101
102 /**
103 * Get a qualified name using the provided namespace and local name.
104 *
105 * @param namespace
106 * the qualified name namespace part
107 * @param localName
108 * the qualified name local part
109 * @return the qualified name
110 */
111 @NonNull
112 static IEnhancedQName of(@NonNull URI namespace, @NonNull String localName) {
113 return of(ObjectUtils.notNull(namespace.toASCIIString()), localName);
114 }
115
116 /**
117 * Get a qualified name using the provided namespace and local name.
118 *
119 * @param namespace
120 * the qualified name namespace part
121 * @param localName
122 * the qualified name local part
123 * @return the qualified name
124 */
125 @NonNull
126 static IEnhancedQName of(@NonNull String namespace, @NonNull String localName) {
127 return EQNameFactory.instance().newQName(namespace, localName);
128 }
129
130 /**
131 * Generate a qualified name for this QName.
132 * <p>
133 * This method uses prefixes associated with well-known namespaces, or will
134 * prepend the namespace if no prefix can be resolved.
135 *
136 * @return the extended qualified-name
137 */
138 @NonNull
139 default String toEQName() {
140 return toEQName((NamespaceToPrefixResolver) null);
141 }
142
143 /**
144 * Generate a qualified name for this QName, use a prefix provided by the
145 * resolver, or by prepending the namespace if no prefix can be resolved.
146 *
147 * @param resolver
148 * the resolver to use to lookup the prefix
149 * @return the extended qualified-name
150 */
151 @NonNull
152 default String toEQName(@Nullable NamespaceToPrefixResolver resolver) {
153 String namespace = getNamespace();
154 String prefix = namespace.isEmpty() ? null : WellKnown.getWellKnownPrefixForUri(namespace);
155 if (prefix == null && resolver != null) {
156 prefix = resolver.resolve(namespace);
157 }
158 return toEQName(namespace, getLocalName(), prefix);
159 }
160
161 /**
162 * Generate a qualified name for this QName. Use a prefix resolved from the
163 * provided static context, or prepend the namespace if no prefix can be
164 * resolved.
165 *
166 * @param staticContext
167 * the static context to use to lookup the prefix
168 * @return the extended qualified-name
169 */
170 @NonNull
171 default String toEQName(@NonNull StaticContext staticContext) {
172 String namespace = getNamespace();
173 String prefix = namespace.isEmpty() ? null : staticContext.lookupPrefixForNamespace(namespace);
174 return toEQName(namespace, getLocalName(), prefix);
175 }
176
177 @NonNull
178 private static String toEQName(
179 @NonNull String namespace,
180 @NonNull String localName,
181 @Nullable String prefix) {
182
183 StringBuilder builder = new StringBuilder();
184 if (prefix == null) {
185 if (!namespace.isEmpty()) {
186 builder.append("Q{")
187 .append(namespace)
188 .append('}');
189 }
190 } else {
191 builder.append(prefix)
192 .append(':');
193 }
194 return ObjectUtils.notNull(builder.append(localName)
195 .toString());
196 }
197
198 /**
199 * Generate a {@link QName} without a namespace prefix.
200 *
201 * @return the name
202 */
203 @NonNull
204 default QName toQName() {
205 return toQName(XMLConstants.DEFAULT_NS_PREFIX);
206 }
207
208 /**
209 * Generate a {@link QName} using the provided namespace prefix.
210 *
211 * @param prefix
212 * the prefix to use
213 * @return the name
214 */
215 @NonNull
216 default QName toQName(@NonNull String prefix) {
217 return new QName(getNamespace(), getLocalName(), prefix);
218 }
219
220 /**
221 * Provides a callback for resolving namespace prefixes.
222 */
223 @FunctionalInterface
224 interface NamespaceToPrefixResolver {
225 /**
226 * Get the URI string for the provided namespace prefix.
227 *
228 * @param namespace
229 * the namespace URI
230 * @return the associated prefix or {@code null} if no prefix is associated
231 */
232 @Nullable
233 String resolve(@NonNull String namespace);
234 }
235
236 /**
237 * Provides a callback for resolving namespace prefixes.
238 */
239 @FunctionalInterface
240 interface PrefixToNamespaceResolver {
241 /**
242 * Get the URI string for the provided namespace prefix.
243 *
244 * @param name
245 * the name to resolve
246 * @return the URI string or {@code null} if the prefix is unbound
247 * @throws StaticMetapathException
248 * with the code {@link StaticMetapathException#PREFIX_NOT_EXPANDABLE}
249 * if a non-empty prefix is provided
250 */
251 @NonNull
252 IEnhancedQName resolve(@NonNull String name);
253 }
254
255 }