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