1 /*
   2  * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.xml.internal.stream.buffer.stax;
  27 
  28 import java.util.ArrayList;
  29 import java.util.Collections;
  30 import java.util.Iterator;
  31 import java.util.List;
  32 import com.sun.xml.internal.org.jvnet.staxex.NamespaceContextEx;
  33 
  34 /**
  35  * A helper class for managing the declaration of namespaces.
  36  * <p>
  37  * A namespace is declared on a namespace context.
  38  * Namespace contexts are pushed on and popped off the namespace context stack.
  39  * <p>
  40  * A declared namespace will be in scope iff the context that it was declared on
  41  * has not been popped off the stack.
  42  * <p>
  43  * When instantiated the namespace stack consists of the root namespace context,
  44  * which contains, by default, the "xml" and "xmlns" declarations.
  45  * Namespaces may be declarations may be declared on the root context.
  46  * The root context cannot be popped but can be reset to contain just the
  47  * "xml" and "xmlns" declarations.
  48  * <p>
  49  * Implementation note: determining the prefix from a namespace URI
  50  * (or vice versa) is efficient when there are few namespace
  51  * declarations i.e. what is considered to be the case for namespace
  52  * declarations in 'average' XML documents. The look up of a namespace URI
  53  * given a prefix is performed in O(n) time. The look up of a prefix given
  54  * a namespace URI is performed in O(2n) time.
  55  * <p>
  56  * The implementation does not scale when there are many namespace
  57  * declarations. TODO: Use a hash map when there are many namespace
  58  * declarations.
  59  *
  60  * @author Paul.Sandoz@Sun.Com
  61  */
  62 final public class NamespaceContexHelper implements NamespaceContextEx {
  63     private static int DEFAULT_SIZE = 8;
  64 
  65     // The prefixes of the namespace declarations
  66     private String[] prefixes = new String[DEFAULT_SIZE];
  67     // The URIs of the namespace declarations
  68     private String[] namespaceURIs = new String[DEFAULT_SIZE];
  69     // Current position to store the next namespace declaration
  70     private int namespacePosition;
  71 
  72     // The namespace contexts
  73     private int[] contexts = new int[DEFAULT_SIZE];
  74     // Current position to store the next namespace context
  75     private int contextPosition;
  76 
  77     // The current namespace context
  78     private int currentContext;
  79 
  80     /**
  81      * Create a new NamespaceContexHelper.
  82      *
  83      */
  84     public NamespaceContexHelper() {
  85         // The default namespace declarations that are always in scope
  86         prefixes[0] = "xml";
  87         namespaceURIs[0] = "http://www.w3.org/XML/1998/namespace";
  88         prefixes[1] = "xmlns";
  89         namespaceURIs[1] = "http://www.w3.org/2000/xmlns/";
  90 
  91         currentContext = namespacePosition = 2;
  92     }
  93 
  94 
  95     // NamespaceContext interface
  96 
  97     public String getNamespaceURI(String prefix) {
  98         if (prefix == null) throw new IllegalArgumentException();
  99 
 100         prefix = prefix.intern();
 101 
 102         for (int i = namespacePosition - 1; i >= 0; i--) {
 103             final String declaredPrefix = prefixes[i];
 104             if (declaredPrefix == prefix) {
 105                 return namespaceURIs[i];
 106             }
 107         }
 108 
 109         return "";
 110     }
 111 
 112     public String getPrefix(String namespaceURI) {
 113         if (namespaceURI == null) throw new IllegalArgumentException();
 114 
 115         for (int i = namespacePosition - 1; i >= 0; i--) {
 116             final String declaredNamespaceURI = namespaceURIs[i];
 117             if (declaredNamespaceURI == namespaceURI || declaredNamespaceURI.equals(namespaceURI)) {
 118                 final String declaredPrefix = prefixes[i];
 119 
 120                 // Check if prefix is out of scope
 121                 for (++i; i < namespacePosition; i++)
 122                     if (declaredPrefix == prefixes[i])
 123                         return null;
 124 
 125                 return declaredPrefix;
 126             }
 127         }
 128 
 129         return null;
 130     }
 131 
 132     public Iterator getPrefixes(String namespaceURI) {
 133         if (namespaceURI == null) throw new IllegalArgumentException();
 134 
 135         List<String> l = new ArrayList<String>();
 136 
 137         NAMESPACE_LOOP: for (int i = namespacePosition - 1; i >= 0; i--) {
 138             final String declaredNamespaceURI = namespaceURIs[i];
 139             if (declaredNamespaceURI == namespaceURI || declaredNamespaceURI.equals(namespaceURI)) {
 140                 final String declaredPrefix = prefixes[i];
 141 
 142                 // Check if prefix is out of scope
 143                 for (int j = i + 1; j < namespacePosition; j++)
 144                     if (declaredPrefix == prefixes[j])
 145                         continue NAMESPACE_LOOP;
 146 
 147                 l.add(declaredPrefix);
 148             }
 149         }
 150 
 151         return l.iterator();
 152     }
 153 
 154     // NamespaceContextEx interface
 155 
 156     public Iterator<NamespaceContextEx.Binding> iterator() {
 157         if (namespacePosition == 2)
 158             return Collections.EMPTY_LIST.iterator();
 159 
 160         final List<NamespaceContextEx.Binding> namespaces =
 161                 new ArrayList<NamespaceContextEx.Binding>(namespacePosition);
 162 
 163         NAMESPACE_LOOP: for (int i = namespacePosition - 1; i >= 2; i--) {
 164             final String declaredPrefix = prefixes[i];
 165 
 166             // Check if prefix is out of scope
 167             for (int j = i + 1; j < namespacePosition; j++) {
 168                 if (declaredPrefix == prefixes[j])
 169                     continue NAMESPACE_LOOP;
 170 
 171                 namespaces.add(new NamespaceBindingImpl(i));
 172             }
 173         }
 174 
 175         return namespaces.iterator();
 176     }
 177 
 178     final private class NamespaceBindingImpl implements NamespaceContextEx.Binding {
 179         int index;
 180 
 181         NamespaceBindingImpl(int index) {
 182             this.index = index;
 183         }
 184 
 185         public String getPrefix() {
 186             return prefixes[index];
 187         }
 188 
 189         public String getNamespaceURI() {
 190             return namespaceURIs[index];
 191         }
 192     }
 193 
 194     /**
 195      * Declare a default namespace.
 196      * <p>
 197      * @param namespaceURI the namespace URI to declare, may be null.
 198      */
 199     public void declareDefaultNamespace(String namespaceURI) {
 200         declareNamespace("", namespaceURI);
 201     }
 202 
 203     /**
 204      * Declare a namespace.
 205      * <p>
 206      * The namespace will be declared on the current namespace context.
 207      * <p>
 208      * The namespace can be removed by popping the current namespace
 209      * context, or, if the declaration occured in the root context, by
 210      * reseting the namespace context.
 211      * <p>
 212      * A default namespace can be declared by passing <code>""</code> as
 213      * the value of the prefix parameter.
 214      * A namespace may be undeclared by passing <code>null</code> as the
 215      * value of the namespaceURI parameter.
 216      * <p>
 217      * @param prefix the namespace prefix to declare, may not be null.
 218      * @param namespaceURI the namespace URI to declare, may be null.
 219      * @throws IllegalArgumentException, if the prefix is null.
 220      */
 221     public void declareNamespace(String prefix, String namespaceURI) {
 222         if (prefix == null) throw new IllegalArgumentException();
 223 
 224         prefix = prefix.intern();
 225         // Ignore the "xml" or "xmlns" declarations
 226         if (prefix == "xml" || prefix == "xmlns")
 227             return;
 228 
 229         // Check for undeclaration
 230         if (namespaceURI != null)
 231             namespaceURI = namespaceURI.intern();
 232 
 233         if (namespacePosition == namespaceURIs.length)
 234             resizeNamespaces();
 235 
 236         // Add new declaration
 237         prefixes[namespacePosition] = prefix;
 238         namespaceURIs[namespacePosition++] = namespaceURI;
 239     }
 240 
 241     private void resizeNamespaces() {
 242         final int newLength = namespaceURIs.length * 3 / 2 + 1;
 243 
 244         String[] newPrefixes = new String[newLength];
 245         System.arraycopy(prefixes, 0, newPrefixes, 0, prefixes.length);
 246         prefixes = newPrefixes;
 247 
 248         String[] newNamespaceURIs = new String[newLength];
 249         System.arraycopy(namespaceURIs, 0, newNamespaceURIs, 0, namespaceURIs.length);
 250         namespaceURIs = newNamespaceURIs;
 251     }
 252 
 253     /**
 254      * Push a namespace context on the stack.
 255      */
 256     public void pushContext() {
 257         if (contextPosition == contexts.length)
 258             resizeContexts();
 259 
 260         contexts[contextPosition++] = currentContext = namespacePosition;
 261     }
 262 
 263     private void resizeContexts() {
 264         int[] newContexts = new int[contexts.length * 3 / 2 + 1];
 265         System.arraycopy(contexts, 0, newContexts, 0, contexts.length);
 266         contexts = newContexts;
 267     }
 268 
 269     /**
 270      * Pop the namespace context off the stack.
 271      * <p>
 272      * Namespaces declared within the context (to be popped)
 273      * will be removed and no longer be in scope.
 274      */
 275     public void popContext() {
 276         if (contextPosition > 0) {
 277             namespacePosition = currentContext = contexts[--contextPosition];
 278         }
 279     }
 280 
 281     /**
 282      * Reset namespace contexts.
 283      * <p>
 284      * Pop all namespace contexts and reset the root context.
 285      */
 286     public void resetContexts() {
 287         currentContext = namespacePosition = 2;
 288     }
 289 }