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.util.ObjectUtils;
10  
11  import java.net.URI;
12  import java.util.Optional;
13  
14  import javax.xml.XMLConstants;
15  import javax.xml.namespace.QName;
16  
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 {
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    @SuppressWarnings("PMD.ShortMethodName")
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    @SuppressWarnings("PMD.ShortMethodName")
84    @NonNull
85    static IEnhancedQName of(@NonNull QName qname) {
86      return of(
87          ObjectUtils.notNull(qname.getNamespaceURI()),
88          ObjectUtils.notNull(qname.getLocalPart()));
89    }
90  
91    /**
92     * Get a qualified name using the provided local name value with no namespace.
93     *
94     * @param localName
95     *          the qualified name local part
96     * @return the qualified name
97     */
98    @SuppressWarnings("PMD.ShortMethodName")
99    @NonNull
100   static IEnhancedQName of(@NonNull String localName) {
101     return of("", localName);
102   }
103 
104   /**
105    * Get a qualified name using the provided namespace and local name.
106    *
107    * @param namespace
108    *          the qualified name namespace part
109    * @param localName
110    *          the qualified name local part
111    * @return the qualified name
112    */
113   @SuppressWarnings("PMD.ShortMethodName")
114   @NonNull
115   static IEnhancedQName of(@NonNull URI namespace, @NonNull String localName) {
116     return of(ObjectUtils.notNull(namespace.toASCIIString()), localName);
117   }
118 
119   /**
120    * Get a qualified name using the provided namespace and local name.
121    *
122    * @param namespace
123    *          the qualified name namespace part
124    * @param localName
125    *          the qualified name local part
126    * @return the qualified name
127    */
128   @SuppressWarnings("PMD.ShortMethodName")
129   @NonNull
130   static IEnhancedQName of(@NonNull String namespace, @NonNull String localName) {
131     return EQNameFactory.instance().newQName(namespace, localName);
132   }
133 
134   @NonNull
135   default String toEQName() {
136     return toEQName((NamespaceToPrefixResolver) null);
137   }
138 
139   /**
140    * Generate a qualified name for this QName, use a prefix provided by the
141    * resolver, or by prepending the namespace if no prefix can be resolved.
142    *
143    * @param resolver
144    *          the resolver to use to lookup the prefix
145    * @return the extended qualified-name
146    */
147   @NonNull
148   default String toEQName(@Nullable NamespaceToPrefixResolver resolver) {
149     String namespace = getNamespace();
150     String prefix = namespace.isEmpty() ? null : StaticContext.getWellKnownPrefixForUri(namespace);
151     if (prefix == null && resolver != null) {
152       prefix = resolver.resolve(namespace);
153     }
154     return toEQName(namespace, getLocalName(), prefix);
155   }
156 
157   @NonNull
158   default String toEQName(@NonNull StaticContext staticContext) {
159     String namespace = getNamespace();
160     String prefix = namespace.isEmpty() ? null : staticContext.lookupPrefixForNamespace(namespace);
161     return toEQName(namespace, getLocalName(), prefix);
162   }
163 
164   @NonNull
165   private static String toEQName(
166       @NonNull String namespace,
167       @NonNull String localName,
168       @Nullable String prefix) {
169 
170     StringBuilder builder = new StringBuilder();
171     if (prefix == null) {
172       if (!namespace.isEmpty()) {
173         builder.append("Q{")
174             .append(namespace)
175             .append('}');
176       }
177     } else {
178       builder.append(prefix)
179           .append(':');
180     }
181     return ObjectUtils.notNull(builder.append(localName)
182         .toString());
183   }
184 
185   /**
186    * Generate a {@link QName} without a namespace prefix.
187    *
188    * @return the name
189    */
190   @NonNull
191   default QName toQName() {
192     return toQName(XMLConstants.DEFAULT_NS_PREFIX);
193   }
194 
195   /**
196    * Generate a {@link QName} using the provided namespace prefix.
197    *
198    * @param prefix
199    *          the prefix to use
200    * @return the name
201    */
202   @NonNull
203   default QName toQName(@NonNull String prefix) {
204     return new QName(getNamespace(), getLocalName(), prefix);
205   }
206 
207   /**
208    * Provides a callback for resolving namespace prefixes.
209    */
210   @FunctionalInterface
211   interface NamespaceToPrefixResolver {
212     /**
213      * Get the URI string for the provided namespace prefix.
214      *
215      * @param namespace
216      *          the namespace URI
217      * @return the associated prefix or {@code null} if no prefix is associated
218      */
219     @Nullable
220     String resolve(@NonNull String namespace);
221   }
222 }