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 }