/* * JBoss, Home of Professional Open Source. * * See the LEGAL.txt file distributed with this work for information regarding copyright ownership and licensing. * * See the AUTHORS.txt file distributed with this work for a full listing of individual contributors. */ package org.teiid.designer.metamodels.xml.namespace; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Set; import org.teiid.designer.metamodels.xml.XmlElement; import org.teiid.designer.metamodels.xml.XmlNamespace; /** * NamespaceContext * * @since 8.0 */ public class NamespaceContext { /** * Control that defines whether URIs should be compared using case sensitive matching. * See {@link http://www.w3.org/TR/1999/REC-xml-names-19990114/#dt-identical} for the * specification. Particularly, "URI references which identify namespaces are considered * identical when they are exactly the same character-for-character." */ private static final boolean URI_MATCHING_IS_CASE_SENSITIVE = true; private final List nsDeclarations; private final NamespaceContext inherited; private final XmlElement element; // /** // * This is sort of a backwards way to do this; should be removed once the schema models can correctly // * store the relevant information. RMH 6/16/03 // */ // private SchemaIncludeMap schemaIncludeMap; // /** * Construct an instance of NamespaceContext. This is package-level so that it is accessible * to test cases only */ public NamespaceContext( final XmlElement element, final NamespaceContext inheritedNS ) { super(); this.element = element; this.inherited = inheritedNS; this.nsDeclarations = this.element.getDeclaredNamespaces(); // this.schemaIncludeMap = includeMap; } // /** // * Return the schema include map that can be used to determine what the actual target namespace // * is for a schema component. // * @return // * @since 3.1SP2 // */ // public SchemaIncludeMap getSchemaIncludeMap() { // if ( this.schemaIncludeMap != null ) { // return this.schemaIncludeMap; // } // if ( this.inherited != null ) { // return this.inherited.getSchemaIncludeMap(); // } // return null; // } // /** * @return the list of {@link XmlNamespace} instances declared in this set; never null */ public List getXmlNamespaces() { return nsDeclarations; } /** * @return */ public NamespaceContext getInherited() { return inherited; } /** * @return */ public XmlElement getXmlElement() { return this.element; } /** * @return the list of {@link XmlNamespace} instances declared in this set, including those * that are inherited; never null */ public List getAllXmlNamespaces() { final List results = new LinkedList(nsDeclarations); if ( this.inherited != null ) { results.addAll( this.inherited.getAllXmlNamespaces() ); } return results; } /** * Add the supplied {@link XmlNamespace} to this set as long as there is no conflict. * @param ns the namespace declaration to be added * @return true if the namespace declaration is non-null and could be added without a conflict, * or false otherwise */ public boolean addXmlNamespace( final XmlNamespace ns ) { if ( ns != null ) { // See if there is already a declaration that conflicts ... final Iterator iter = this.nsDeclarations.iterator(); while (iter.hasNext()) { final XmlNamespace existingNS = (XmlNamespace)iter.next(); // Considered a conflict if // - there is already a namespace with the same prefix on this owner if ( existingNS.getPrefix() != null && existingNS.getPrefix().equals(ns.getPrefix()) ) { return false; } // - if there is already a namespace with the same (case-insensitive) non-empty URI final String existingUri = existingNS.getUri(); if ( existingUri.length() != 0 && existingUri.equalsIgnoreCase(ns.getUri()) ) { return false; } } // Add the namespace declaration to the XML Element's list ... return this.nsDeclarations.add(ns); } return false; } // // /** // * Helper method that adds a namespace declaration, and is equivalent to the call: // * <code> // * return this.addDeclaredXmlNamespace( new XmlNamespace(prefix,uri) ); // * </code> // * @param prefix the prefix for the namespace declaration // * @param uri the URI of the referenced schema // * @return true if the namespace declaration is non-null and could be added without a conflict, // * or false otherwise // */ // public XmlNamespace declareNamespace( final String prefix, final String uri ) { // final XmlNamespace ns = new XmlNamespace(prefix,uri); // if ( this.addDeclaredXmlNamespace(ns) ) { // return ns; // } // return null; // } // // /* (non-Javadoc) * @see java.lang.Object#toString() */ @Override public String toString() { final StringBuffer sb = new StringBuffer(); if ( this.element != null ) { sb.append( this.element.getName() ); } sb.append(" ("); //$NON-NLS-1$ boolean first = true; final Iterator iter = this.nsDeclarations.iterator(); while (iter.hasNext()) { final XmlNamespace ns = (XmlNamespace)iter.next(); if ( !first ) { sb.append(","); //$NON-NLS-1$ } sb.append(ns.getPrefix()); sb.append(":"); //$NON-NLS-1$ sb.append(ns.getUri()); first = false; } sb.append(")"); //$NON-NLS-1$ return sb.toString(); } /** * Traverse up the stack and find the <i>first</i> namespace declaration that matches the supplied URI. * A namespace declaration is considered a match if * <ul> * <li>The namespace URI is a <i>case-insensitive</i> match</li> * <li>The prefix for the best match is not reused lower in the stack (e.g., lower in the document)</li> * </ul> * @param uri the URI to be matched * @return the XmlNamespace object that is a match, or null if there is no XmlNamespace * that matches the URI. */ public XmlNamespace getBestNamespace( final String uri ) { final Set prefixesFound = new HashSet(); return getBestNamespace(uri,prefixesFound); } /** * This method does the real work * @param uri * @param prefixesFound * @return */ protected XmlNamespace getBestNamespace( final String uri, final Set prefixesFound ) { final Iterator iter = this.nsDeclarations.iterator(); while (iter.hasNext()) { final XmlNamespace ns = (XmlNamespace)iter.next(); boolean urisMatch = false; if ( URI_MATCHING_IS_CASE_SENSITIVE ) { urisMatch = ns.getUri().equals(uri); } else { urisMatch = ns.getUri().equalsIgnoreCase(uri); } if ( urisMatch ) { // There is a match, so see if the ns's prefix was seen lower than this if ( !prefixesFound.contains(ns.getPrefix())) { // The prefix was not seen so far, so this is a good match! return ns; } // The prefix was overridden, so the matched namespace cannot be used; just continue } prefixesFound.add(ns.getPrefix()); } if ( this.inherited != null ) { return this.inherited.getBestNamespace(uri,prefixesFound); } return null; } // /** // * Looks for an prefix that is unused, starting with a specified prefix. // * @return // */ // public String findUnusedPrefix( final String startingPrefix ) { // String prefix = startingPrefix; // int counter = 0; // // // As long as there is a matching prefix // while ( hasMatchingPrefix(prefix) ) { // // Change the prefix ... // prefix = startingPrefix + (++counter); // } // return prefix; // } // // protected boolean hasMatchingPrefix( final String prefix ) { // final Iterator iter = this.nsDeclarations.iterator(); // while (iter.hasNext()) { // final XmlNamespace ns = (XmlNamespace)iter.next(); // if ( ns.getPrefix().equalsIgnoreCase(prefix) ) { // return true; // } // } // if ( this.inherited != null ) { // return this.inherited.hasMatchingPrefix(prefix); // } // return false; // } }