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 }