1 /*
   2  * Copyright (c) 1997, 2012, 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.bind.v2.runtime.output;
  27 
  28 import java.io.IOException;
  29 import java.util.Collections;
  30 import java.util.Iterator;
  31 
  32 import javax.xml.XMLConstants;
  33 import javax.xml.stream.XMLStreamException;
  34 
  35 import com.sun.istack.internal.NotNull;
  36 import com.sun.istack.internal.Nullable;
  37 import com.sun.xml.internal.bind.marshaller.NamespacePrefixMapper;
  38 import com.sun.xml.internal.bind.v2.WellKnownNamespace;
  39 import com.sun.xml.internal.bind.v2.runtime.Name;
  40 import com.sun.xml.internal.bind.v2.runtime.NamespaceContext2;
  41 import com.sun.xml.internal.bind.v2.runtime.XMLSerializer;
  42 
  43 import org.xml.sax.SAXException;
  44 
  45 /**
  46  * Keeps track of in-scope namespace bindings for the marshaller.
  47  *
  48  * <p>
  49  * This class is also used to keep track of tag names for each element
  50  * for the marshaller (for the performance reason.)
  51  *
  52  * @author Kohsuke Kawaguchi
  53  */
  54 public final class NamespaceContextImpl implements NamespaceContext2 {
  55     private final XMLSerializer owner;
  56 
  57     private String[] prefixes = new String[4];
  58     private String[] nsUris = new String[4];
  59 //    /**
  60 //     * True if the correponding namespace declaration is an authentic one that should be printed.
  61 //     *
  62 //     * False if it's a re-discovered in-scope namespace binding available at the ancestor elements
  63 //     * outside this marhsalling. The false value is used to incorporate the in-scope namespace binding
  64 //     * information from {@link #inscopeNamespaceContext}. When false, such a declaration does not need
  65 //     * to be printed, as it's already available in ancestors.
  66 //     */
  67 //    private boolean[] visible = new boolean[4];
  68 //
  69 //    /**
  70 //     * {@link NamespaceContext} that informs this {@link XMLSerializer} about the
  71 //     * in-scope namespace bindings of the ancestor elements outside this marshalling.
  72 //     *
  73 //     * <p>
  74 //     * This is used when the marshaller is marshalling into a subtree that has ancestor
  75 //     * elements created outside the JAXB marshaller.
  76 //     *
  77 //     * Its {@link NamespaceContext#getPrefix(String)} is used to discover in-scope namespace
  78 //     * binding,
  79 //     */
  80 //    private final NamespaceContext inscopeNamespaceContext;
  81 
  82     /**
  83      * Number of URIs declared. Identifies the valid portion of
  84      * the {@link #prefixes} and {@link #nsUris} arrays.
  85      */
  86     private int size;
  87 
  88     private Element current;
  89 
  90     /**
  91      * This is the {@link Element} whose prev==null.
  92      * This element is used to hold the contextual namespace bindings
  93      * that are assumed to be outside of the document we are marshalling.
  94      * Specifically the xml prefix and any other user-specified bindings.
  95      *
  96      * @see NamespacePrefixMapper#getPreDeclaredNamespaceUris()
  97      */
  98     private final Element top;
  99 
 100     /**
 101      * Never null.
 102      */
 103     private NamespacePrefixMapper prefixMapper = defaultNamespacePrefixMapper;
 104 
 105     /**
 106      * True to allow new URIs to be declared. False otherwise.
 107      */
 108     public boolean collectionMode;
 109 
 110 
 111     public NamespaceContextImpl(XMLSerializer owner) {
 112         this.owner = owner;
 113 
 114         current = top = new Element(this,null);
 115         // register namespace URIs that are implicitly bound
 116         put(XMLConstants.XML_NS_URI,XMLConstants.XML_NS_PREFIX);
 117     }
 118 
 119     public void setPrefixMapper( NamespacePrefixMapper mapper ) {
 120         if(mapper==null)
 121             mapper = defaultNamespacePrefixMapper;
 122         this.prefixMapper = mapper;
 123     }
 124 
 125     public NamespacePrefixMapper getPrefixMapper() {
 126         return prefixMapper;
 127     }
 128 
 129     public void reset() {
 130         current = top;
 131         size = 1;
 132         collectionMode = false;
 133     }
 134 
 135     /**
 136      * Returns the prefix index to the specified URI.
 137      * This method allocates a new URI if necessary.
 138      */
 139     public int declareNsUri( String uri, String preferedPrefix, boolean requirePrefix ) {
 140         preferedPrefix = prefixMapper.getPreferredPrefix(uri,preferedPrefix,requirePrefix);
 141 
 142         if(uri.length()==0) {
 143             for( int i=size-1; i>=0; i-- ) {
 144                 if(nsUris[i].length()==0)
 145                     return i; // already declared
 146                 if(prefixes[i].length()==0) {
 147                     // the default prefix is already taken.
 148                     // move that URI to another prefix, then assign "" to the default prefix.
 149                     assert current.defaultPrefixIndex==-1 && current.oldDefaultNamespaceUriIndex==-1;
 150 
 151                     String oldUri = nsUris[i];
 152                     String[] knownURIs = owner.nameList.namespaceURIs;
 153 
 154                     if(current.baseIndex<=i) {
 155                         // this default prefix is declared in this context. just reassign it
 156 
 157                         nsUris[i] = "";
 158 
 159                         int subst = put(oldUri,null);
 160 
 161                         // update uri->prefix table if necessary
 162                         for( int j=knownURIs.length-1; j>=0; j-- ) {
 163                             if(knownURIs[j].equals(oldUri)) {
 164                                 owner.knownUri2prefixIndexMap[j] = subst;
 165                                 break;
 166                             }
 167                         }
 168                         if (current.elementLocalName != null) {
 169                             current.setTagName(subst, current.elementLocalName, current.getOuterPeer());
 170                         }
 171                         return i;
 172                     } else {
 173                         // first, if the previous URI assigned to "" is
 174                         // a "known URI", remember what we've reallocated
 175                         // so that we can fix it when this context pops.
 176                         for( int j=knownURIs.length-1; j>=0; j-- ) {
 177                             if(knownURIs[j].equals(oldUri)) {
 178                                 current.defaultPrefixIndex = i;
 179                                 current.oldDefaultNamespaceUriIndex = j;
 180                                 // assert commented out; too strict/not valid any more
 181                                 // assert owner.knownUri2prefixIndexMap[j]==current.defaultPrefixIndex;
 182                                 // update the table to point to the prefix we'll declare
 183                                 owner.knownUri2prefixIndexMap[j] = size;
 184                                 break;
 185                             }
 186                         }
 187                         if (current.elementLocalName!=null) {
 188                                                 current.setTagName(size, current.elementLocalName, current.getOuterPeer());
 189                         }
 190 
 191                         put(nsUris[i],null);
 192                         return put("", "");
 193                     }
 194                 }
 195             }
 196 
 197             // "" isn't in use
 198             return put("", "");
 199         } else {
 200             // check for the existing binding
 201             for( int i=size-1; i>=0; i-- ) {
 202                 String p = prefixes[i];
 203                 if(nsUris[i].equals(uri)) {
 204                     if (!requirePrefix || p.length()>0)
 205                         return i;
 206                     // declared but this URI is bound to empty. Look further
 207                 }
 208                 if(p.equals(preferedPrefix)) {
 209                     // the suggested prefix is already taken. can't use it
 210                     preferedPrefix = null;
 211                 }
 212             }
 213 
 214             if(preferedPrefix==null && requirePrefix)
 215                 // we know we can't bind to "", but we don't have any possible name at hand.
 216                 // generate it here to avoid this namespace to be bound to "".
 217                 preferedPrefix = makeUniquePrefix();
 218 
 219             // haven't been declared. allocate a new one
 220             // if the preferred prefix is already in use, it should have been set to null by this time
 221             return put(uri, preferedPrefix);
 222         }
 223     }
 224 
 225     public int force(@NotNull String uri, @NotNull String prefix) {
 226         // check for the existing binding
 227 
 228         for( int i=size-1; i>=0; i-- ) {
 229             if(prefixes[i].equals(prefix)) {
 230                 if(nsUris[i].equals(uri))
 231                     return i;   // found duplicate
 232                 else
 233                     // the prefix is used for another namespace. we need to declare it
 234                     break;
 235             }
 236         }
 237 
 238         return put(uri, prefix);
 239     }
 240 
 241     /**
 242      * Puts this new binding into the declared prefixes list
 243      * without doing any duplicate check.
 244      *
 245      * This can be used to forcibly set namespace declarations.
 246      *
 247      * <p>
 248      * Most of the time {@link #declareNamespace(String, String, boolean)} shall be used.
 249      *
 250      * @return
 251      *      the index of this new binding.
 252      */
 253     public int put(@NotNull String uri, @Nullable String prefix) {
 254         if(size==nsUris.length) {
 255             // reallocate
 256             String[] u = new String[nsUris.length*2];
 257             String[] p = new String[prefixes.length*2];
 258             System.arraycopy(nsUris,0,u,0,nsUris.length);
 259             System.arraycopy(prefixes,0,p,0,prefixes.length);
 260             nsUris = u;
 261             prefixes = p;
 262         }
 263         if(prefix==null) {
 264             if(size==1)
 265                 prefix = "";    // if this is the first user namespace URI we see, use "".
 266             else {
 267                 // otherwise make up an unique name
 268                 prefix = makeUniquePrefix();
 269             }
 270         }
 271         nsUris[size] = uri;
 272         prefixes[size] = prefix;
 273 
 274         return size++;
 275     }
 276 
 277     private String makeUniquePrefix() {
 278         String prefix;
 279         prefix = new StringBuilder(5).append("ns").append(size).toString();
 280         while(getNamespaceURI(prefix)!=null) {
 281             prefix += '_';  // under a rare circumstance there might be existing 'nsNNN', so rename them
 282         }
 283         return prefix;
 284     }
 285 
 286 
 287     public Element getCurrent() {
 288         return current;
 289     }
 290 
 291     /**
 292      * Returns the prefix index of the specified URI.
 293      * It is an error if the URI is not declared.
 294      */
 295     public int getPrefixIndex( String uri ) {
 296         for( int i=size-1; i>=0; i-- ) {
 297                 if(nsUris[i].equals(uri))
 298                     return i;
 299         }
 300         throw new IllegalStateException();
 301     }
 302 
 303     /**
 304      * Gets the prefix from a prefix index.
 305      *
 306      * The behavior is undefined if the index is out of range.
 307      */
 308     public String getPrefix(int prefixIndex) {
 309         return prefixes[prefixIndex];
 310     }
 311 
 312     public String getNamespaceURI(int prefixIndex) {
 313         return nsUris[prefixIndex];
 314     }
 315 
 316     /**
 317      * Gets the namespace URI that is bound to the specified prefix.
 318      *
 319      * @return null
 320      *      if the prefix is unbound.
 321      */
 322     public String getNamespaceURI(String prefix) {
 323         for( int i=size-1; i>=0; i-- )
 324             if(prefixes[i].equals(prefix))
 325                 return nsUris[i];
 326         return null;
 327     }
 328 
 329     /**
 330      * Returns the prefix of the specified URI,
 331      * or null if none exists.
 332      */
 333     public String getPrefix( String uri ) {
 334         if(collectionMode) {
 335             return declareNamespace(uri,null,false);
 336         } else {
 337             for( int i=size-1; i>=0; i-- )
 338                 if(nsUris[i].equals(uri))
 339                     return prefixes[i];
 340             return null;
 341         }
 342     }
 343 
 344     public Iterator<String> getPrefixes(String uri) {
 345         String prefix = getPrefix(uri);
 346         if(prefix==null)
 347             return Collections.<String>emptySet().iterator();
 348         else
 349             return Collections.singleton(uri).iterator();
 350     }
 351 
 352     public String declareNamespace(String namespaceUri, String preferedPrefix, boolean requirePrefix) {
 353         int idx = declareNsUri(namespaceUri,preferedPrefix,requirePrefix);
 354         return getPrefix(idx);
 355     }
 356 
 357     /**
 358      * Number of total bindings declared.
 359      */
 360     public int count() {
 361         return size;
 362     }
 363 
 364 
 365     /**
 366      * This model of namespace declarations maintain the following invariants.
 367      *
 368      * <ul>
 369      *  <li>If a non-empty prefix is declared, it will never be reassigned to different namespace URIs.
 370      *  <li>
 371      */
 372     public final class Element {
 373 
 374         public final NamespaceContextImpl context;
 375 
 376         /**
 377          * {@link Element}s form a doubly-linked list.
 378          */
 379         private final Element prev;
 380         private Element next;
 381 
 382         private int oldDefaultNamespaceUriIndex;
 383         private int defaultPrefixIndex;
 384 
 385 
 386         /**
 387          * The numbe of prefixes declared by ancestor {@link Element}s.
 388          */
 389         private int baseIndex;
 390 
 391         /**
 392          * The depth of the {@link Element}.
 393          *
 394          * This value is equivalent as the result of the following computation.
 395          *
 396          * <pre>
 397          * int depth() {
 398          *   int i=-1;
 399          *   for(Element e=this; e!=null;e=e.prev)
 400          *     i++;
 401          *   return i;
 402          * }
 403          * </pre>
 404          */
 405         private final int depth;
 406 
 407 
 408 
 409         private int elementNamePrefix;
 410         private String elementLocalName;
 411 
 412         /**
 413          * Tag name of this element.
 414          * Either this field is used or the {@link #elementNamePrefix} and {@link #elementLocalName} pair.
 415          */
 416         private Name elementName;
 417 
 418         /**
 419          * Used for the binder. The JAXB object that corresponds to this element.
 420          */
 421         private Object outerPeer;
 422         private Object innerPeer;
 423 
 424 
 425         private Element(NamespaceContextImpl context,Element prev) {
 426             this.context = context;
 427             this.prev = prev;
 428             this.depth = (prev==null) ? 0 : prev.depth+1;
 429         }
 430 
 431         /**
 432          * Returns true if this {@link Element} represents the root element that
 433          * we are marshalling.
 434          */
 435         public boolean isRootElement() {
 436             return depth==1;
 437         }
 438 
 439         public Element push() {
 440             if(next==null)
 441                 next = new Element(context,this);
 442             next.onPushed();
 443             return next;
 444         }
 445 
 446         public Element pop() {
 447             if(oldDefaultNamespaceUriIndex>=0) {
 448                 // restore the old default namespace URI binding
 449                 context.owner.knownUri2prefixIndexMap[oldDefaultNamespaceUriIndex] = defaultPrefixIndex;
 450             }
 451             context.size = baseIndex;
 452             context.current = prev;
 453             // release references to user objects
 454             outerPeer = innerPeer = null;
 455             return prev;
 456         }
 457 
 458         private void onPushed() {
 459             oldDefaultNamespaceUriIndex = defaultPrefixIndex = -1;
 460             baseIndex = context.size;
 461             context.current = this;
 462         }
 463 
 464         public void setTagName( int prefix, String localName, Object outerPeer ) {
 465             assert localName!=null;
 466             this.elementNamePrefix = prefix;
 467             this.elementLocalName = localName;
 468             this.elementName = null;
 469             this.outerPeer = outerPeer;
 470         }
 471 
 472         public void setTagName( Name tagName, Object outerPeer ) {
 473             assert tagName!=null;
 474             this.elementName = tagName;
 475             this.outerPeer = outerPeer;
 476         }
 477 
 478         public void startElement(XmlOutput out, Object innerPeer) throws IOException, XMLStreamException {
 479             this.innerPeer = innerPeer;
 480             if(elementName!=null) {
 481                 out.beginStartTag(elementName);
 482             } else {
 483                 out.beginStartTag(elementNamePrefix,elementLocalName);
 484             }
 485         }
 486 
 487         public void endElement(XmlOutput out) throws IOException, SAXException, XMLStreamException {
 488             if(elementName!=null) {
 489                 out.endTag(elementName);
 490                 elementName = null;
 491             } else {
 492                 out.endTag(elementNamePrefix,elementLocalName);
 493             }
 494         }
 495 
 496         /**
 497          * Gets the number of bindings declared on this element.
 498          */
 499         public final int count() {
 500             return context.size-baseIndex;
 501         }
 502 
 503         /**
 504          * Gets the prefix declared in this context.
 505          *
 506          * @param idx
 507          *      between 0 and {@link #count()}
 508          */
 509         public final String getPrefix(int idx) {
 510             return context.prefixes[baseIndex+idx];
 511         }
 512 
 513         /**
 514          * Gets the namespace URI declared in this context.
 515          *
 516          * @param idx
 517          *      between 0 and {@link #count()}
 518          */
 519         public final String getNsUri(int idx) {
 520             return context.nsUris[baseIndex+idx];
 521         }
 522 
 523         public int getBase() {
 524             return baseIndex;
 525         }
 526 
 527         public Object getOuterPeer() {
 528             return outerPeer;
 529         }
 530 
 531         public Object getInnerPeer() {
 532             return innerPeer;
 533         }
 534 
 535         /**
 536          * Gets the parent {@link Element}.
 537          */
 538         public Element getParent() {
 539             return prev;
 540         }
 541     }
 542 
 543 
 544     /**
 545      * Default {@link NamespacePrefixMapper} implementation used when
 546      * it is not specified by the user.
 547      */
 548     private static final NamespacePrefixMapper defaultNamespacePrefixMapper = new NamespacePrefixMapper() {
 549         public String getPreferredPrefix(String namespaceUri, String suggestion, boolean requirePrefix) {
 550             if( namespaceUri.equals(WellKnownNamespace.XML_SCHEMA_INSTANCE) )
 551                 return "xsi";
 552             if( namespaceUri.equals(WellKnownNamespace.XML_SCHEMA) )
 553                 return "xs";
 554             if( namespaceUri.equals(WellKnownNamespace.XML_MIME_URI) )
 555                 return "xmime";
 556             return suggestion;
 557         }
 558     };
 559 }