/* * Copyright (C) 2005 Alfresco, Inc. * * Licensed under the Mozilla Public License version 1.1 * with a permitted attribution clause. You may obtain a * copy of the License at * * http://www.alfresco.org/legal/license.txt * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific * language governing permissions and limitations under the * License. */ package org.alfresco.service.namespace; import java.io.Serializable; import java.util.Collection; /** * QName represents the qualified name of a Repository item. Each * QName consists of a local name qualified by a namespace. *

* The {@link org.alfresco.service.namespace.QNamePattern QNamePattern} is implemented * to allow instances of this class to be used for direct pattern matching where * required on interfaces. * * @author David Caruana * */ public final class QName implements QNamePattern, Serializable, Cloneable { private static final long serialVersionUID = 3977016258204348976L; private String namespaceURI; // never null private String localName; // never null private int hashCode; private String prefix; public static final char NAMESPACE_PREFIX = ':'; public static final char NAMESPACE_BEGIN = '{'; public static final char NAMESPACE_END = '}'; public static final int MAX_LENGTH = 100; /** * Create a QName * * @param namespaceURI the qualifying namespace (maybe null or empty string) * @param localName the qualified name * @return the QName */ public static QName createQName(String namespaceURI, String localName) throws InvalidQNameException { if (localName == null || localName.length() == 0) { throw new InvalidQNameException("A QName must consist of a local name"); } return new QName(namespaceURI, localName, null); } /** * Create a QName * * @param prefix namespace prefix (maybe null or empty string) * @param localName local name * @param prefixResolver lookup to resolve mappings between prefix and namespace * @return the QName */ public static QName createQName(String prefix, String localName, NamespacePrefixResolver prefixResolver) throws InvalidQNameException, NamespaceException { // Validate Arguments if (localName == null || localName.length() == 0) { throw new InvalidQNameException("A QName must consist of a local name"); } if (prefixResolver == null) { throw new IllegalArgumentException("A Prefix Resolver must be specified"); } if (prefix == null) { prefix = NamespaceService.DEFAULT_PREFIX; } // Calculate namespace URI and create QName String uri = prefixResolver.getNamespaceURI(prefix); if (uri == null) { throw new NamespaceException("Namespace prefix " + prefix + " is not mapped to a namespace URI"); } return new QName(uri, localName, prefix); } /** * Create a QName * * @param qname qualified name of the following format prefix:localName * @param prefixResolver lookup to resolve mappings between prefix and namespace * @return the QName */ public static QName createQName(String qname, NamespacePrefixResolver prefixResolver) throws InvalidQNameException, NamespaceException { QName name = null; if (qname != null) { int colonIndex = qname.indexOf(NAMESPACE_PREFIX); String prefix = (colonIndex == -1) ? NamespaceService.DEFAULT_PREFIX : qname.substring(0, colonIndex); String localName = (colonIndex == -1) ? qname : qname.substring(colonIndex +1); name = createQName(prefix, localName, prefixResolver); } return name; } /** * Create a QName from its internal string representation of the following format: * * {namespaceURI}localName * * @param qname the string representation of the QName * @return the QName * @throws IllegalArgumentException * @throws InvalidQNameException */ public static QName createQName(String qname) throws InvalidQNameException { if (qname == null || qname.length() == 0) { throw new InvalidQNameException("Argument qname is mandatory"); } String namespaceURI = null; String localName = null; // Parse namespace int namespaceBegin = qname.indexOf(NAMESPACE_BEGIN); int namespaceEnd = -1; if (namespaceBegin != -1) { if (namespaceBegin != 0) { throw new InvalidQNameException("QName '" + qname + "' must start with a namespaceURI"); } namespaceEnd = qname.indexOf(NAMESPACE_END, namespaceBegin + 1); if (namespaceEnd == -1) { throw new InvalidQNameException("QName '" + qname + "' is missing the closing namespace " + NAMESPACE_END + " token"); } namespaceURI = qname.substring(namespaceBegin + 1, namespaceEnd); } // Parse name localName = qname.substring(namespaceEnd + 1); if (localName == null || localName.length() == 0) { throw new InvalidQNameException("QName '" + qname + "' must consist of a local name"); } // Construct QName return new QName(namespaceURI, localName, null); } /** * Create a valid local name from the specified name * * @param name name to create valid local name from * @return valid local name */ public static String createValidLocalName(String name) { // Validate length if (name == null || name.length() == 0) { throw new IllegalArgumentException("Local name cannot be null or empty."); } if (name.length() > MAX_LENGTH) { name = name.substring(0, MAX_LENGTH); } return name; } /** * Create a QName * * @param qname qualified name of the following format prefix:localName * @return string array where index 0 => prefix and index 1 => local name */ public static String[] splitPrefixedQName(String qname) throws InvalidQNameException, NamespaceException { if (qname != null) { int colonIndex = qname.indexOf(NAMESPACE_PREFIX); String prefix = (colonIndex == -1) ? NamespaceService.DEFAULT_PREFIX : qname.substring(0, colonIndex); String localName = (colonIndex == -1) ? qname : qname.substring(colonIndex +1); return new String[] { prefix, localName }; } return null; } /** * Construct QName * * @param namespace qualifying namespace (maybe null or empty string) * @param name qualified name * @param prefix prefix (maybe null or empty string) */ private QName(String namespace, String name, String prefix) { this.namespaceURI = (namespace == null) ? NamespaceService.DEFAULT_URI : namespace; this.prefix = prefix; this.localName = name; this.hashCode = 0; } @Override public Object clone() throws CloneNotSupportedException { return super.clone(); } /** * Gets the name * * @return the name */ public String getLocalName() { return this.localName; } /** * Gets the namespace * * @return the namespace (empty string when not specified, but never null) */ public String getNamespaceURI() { return this.namespaceURI; } /** * Gets a prefix resolved version of this QName * * @param resolver namespace prefix resolver * @return QName with prefix resolved */ public QName getPrefixedQName(NamespacePrefixResolver resolver) { Collection prefixes = resolver.getPrefixes(namespaceURI); if (prefixes.size() == 0) { throw new NamespaceException("A namespace prefix is not registered for uri " + namespaceURI); } String resolvedPrefix = prefixes.iterator().next(); if (prefix != null && prefix.equals(resolvedPrefix)) { return this; } return new QName(namespaceURI, localName, resolvedPrefix); } /** * Two QNames are equal only when both their name and namespace match. * * Note: The prefix is ignored during the comparison. */ public boolean equals(Object object) { if (this == object) { return true; } else if (object == null) { return false; } if (object instanceof QName) { QName other = (QName) object; // namespaceURI and localname are not allowed to be null return (this.namespaceURI.equals(other.namespaceURI) && this.localName.equals(other.localName)); } else { return false; } } /** * Performs a direct comparison between qnames. * * @see #equals(Object) */ public boolean isMatch(QName qname) { return this.equals(qname); } /** * Calculate hashCode. Follows pattern used by String where hashCode is * cached (QName is immutable). */ public int hashCode() { if (this.hashCode == 0) { // the hashcode assignment is atomic - it is only an integer this.hashCode = ((37 * localName.hashCode()) + namespaceURI.hashCode()); } return this.hashCode; } /** * Render string representation of QName using format: * * {namespace}name * * @return the string representation */ public String toString() { return NAMESPACE_BEGIN + namespaceURI + NAMESPACE_END + localName; } /** * Render string representation of QName using format: * * prefix:name * * @return the string representation */ public String toPrefixString() { return (prefix == null) ? localName : prefix + NAMESPACE_PREFIX + localName; } /** * Render string representation of QName using format: * * prefix:name * * according to namespace prefix mappings of specified namespace resolver. * * @param prefixResolver namespace prefix resolver * * @return the string representation */ public String toPrefixString(NamespacePrefixResolver prefixResolver) { Collection prefixes = prefixResolver.getPrefixes(namespaceURI); if (prefixes.size() == 0) { throw new NamespaceException("A namespace prefix is not registered for uri " + namespaceURI); } String prefix = prefixes.iterator().next(); if (prefix.equals(NamespaceService.DEFAULT_PREFIX)) { return localName; } else { return prefix + NAMESPACE_PREFIX + localName; } } /** * Creates a QName representation for the given String. If the String has no namespace the Alfresco namespace is * added. If the String has a prefix an attempt to resolve the prefix to the full URI will be made. * * @param str The string to convert * @return A QName representation of the given string */ public static QName resolveToQName(NamespacePrefixResolver prefixResolver, String str) { QName qname = null; if (str == null && str.length() == 0) { throw new IllegalArgumentException("str parameter is mandatory"); } if (str.charAt(0) == (NAMESPACE_BEGIN)) { // create QName directly qname = createQName(str); } else if (str.indexOf(NAMESPACE_PREFIX) != -1) { // extract the prefix and try and resolve using the // namespace service int end = str.indexOf(NAMESPACE_PREFIX); String prefix = str.substring(0, end); String localName = str.substring(end + 1); String uri = prefixResolver.getNamespaceURI(prefix); if (uri != null) { qname = createQName(uri, localName); } } else { // there's no namespace so prefix with Alfresco's Content Model qname = createQName(NamespaceService.CONTENT_MODEL_1_0_URI, str); } return qname; } /** * Creates a string representation of a QName for the given string. If the given string already has a namespace, * either a URL or a prefix, nothing the given string is returned. If it does not have a namespace the Alfresco * namespace is added. * * @param str * The string to convert * * @return A QName String representation of the given string */ public static String resolveToQNameString(NamespacePrefixResolver prefixResolver, String str) { String result = str; if (str == null && str.length() == 0) { throw new IllegalArgumentException("str parameter is mandatory"); } if (str.charAt(0) != NAMESPACE_BEGIN && str.indexOf(NAMESPACE_PREFIX) != -1) { // get the prefix and resolve to the uri int end = str.indexOf(NAMESPACE_PREFIX); String prefix = str.substring(0, end); String localName = str.substring(end + 1); String uri = prefixResolver.getNamespaceURI(prefix); if (uri != null) { result = new StringBuilder(64).append(NAMESPACE_BEGIN).append(uri).append(NAMESPACE_END).append( localName).toString(); } } else if (str.charAt(0) != NAMESPACE_BEGIN) { // there's no namespace so prefix with Alfresco's Content Model result = new StringBuilder(64).append(NAMESPACE_BEGIN).append(NamespaceService.CONTENT_MODEL_1_0_URI) .append(NAMESPACE_END).append(str).toString(); } return result; } }