001/* 002 * SPDX-FileCopyrightText: none 003 * SPDX-License-Identifier: CC0-1.0 004 */ 005 006package dev.metaschema.core.qname; 007 008import java.net.URI; 009import java.util.Optional; 010 011import javax.xml.XMLConstants; 012import javax.xml.namespace.QName; 013 014import dev.metaschema.core.metapath.StaticContext; 015import dev.metaschema.core.metapath.StaticMetapathException; 016import dev.metaschema.core.util.ObjectUtils; 017import edu.umd.cs.findbugs.annotations.NonNull; 018import edu.umd.cs.findbugs.annotations.Nullable; 019 020/** 021 * An efficient cache-backed representation of a qualified name. 022 * <p> 023 * This implementation uses an underlying integer-based cache to reduce the 024 * memory footprint of qualified names and namespaces by reusing instances with 025 * the same namespace and local name. 026 */ 027public interface IEnhancedQName extends Comparable<IEnhancedQName> { 028 /** 029 * Get the index position of the qualified name. 030 * <p> 031 * This value can be used in place of this object. The object can be retrieved 032 * using this index with the {@link #of(int)} method. 033 * 034 * @return the index position 035 */ 036 int getIndexPosition(); 037 038 /** 039 * Get the namespace part of the qualified name. 040 * 041 * @return the namespace 042 */ 043 @NonNull 044 String getNamespace(); 045 046 /** 047 * Get the namespace part of the qualified name. 048 * 049 * @return the namespace as a URI 050 */ 051 @NonNull 052 URI getNamespaceAsUri(); 053 054 /** 055 * Get the local part of the qualified name. 056 * 057 * @return the local name 058 */ 059 @NonNull 060 String getLocalName(); 061 062 /** 063 * Get an existing qualified name by looking up the cached entry using the 064 * provided index value. 065 * 066 * @param index 067 * the index value to lookup 068 * @return an optional containing the qualified name, if it exists 069 */ 070 @NonNull 071 static Optional<IEnhancedQName> of(int index) { 072 return EQNameFactory.instance().get(index); 073 } 074 075 /** 076 * Get a qualified name using the provided {@link QName} value. 077 * 078 * @param qname 079 * the qualified name to get 080 * @return the qualified name 081 */ 082 @NonNull 083 static IEnhancedQName of(@NonNull QName qname) { 084 return of( 085 ObjectUtils.notNull(qname.getNamespaceURI()), 086 ObjectUtils.notNull(qname.getLocalPart())); 087 } 088 089 /** 090 * Get a qualified name using the provided local name value with no namespace. 091 * 092 * @param localName 093 * the qualified name local part 094 * @return the qualified name 095 */ 096 @NonNull 097 static IEnhancedQName of(@NonNull String localName) { 098 return of("", localName); 099 } 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}