View Javadoc
1   /*
2    * Copyright (c) 1997, 2011, 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 }