1 /*
2  * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Distribution License v. 1.0, which is available at
6  * http://www.eclipse.org/org/documents/edl-v10.php.
7  *
8  * SPDX-License-Identifier: BSD-3-Clause
9  */

10
11 package com.sun.xml.bind.v2.runtime.output;
12
13 import java.io.IOException;
14 import java.util.Collections;
15 import java.util.Iterator;
16
17 import javax.xml.XMLConstants;
18 import javax.xml.stream.XMLStreamException;
19
20 import com.sun.istack.NotNull;
21 import com.sun.istack.Nullable;
22 import com.sun.xml.bind.marshaller.NamespacePrefixMapper;
23 import com.sun.xml.bind.v2.WellKnownNamespace;
24 import com.sun.xml.bind.v2.runtime.Name;
25 import com.sun.xml.bind.v2.runtime.NamespaceContext2;
26 import com.sun.xml.bind.v2.runtime.XMLSerializer;
27
28 import org.xml.sax.SAXException;
29
30 /**
31  * Keeps track of in-scope namespace bindings for the marshaller.
32  *
33  * <p>
34  * This class is also used to keep track of tag names for each element
35  * for the marshaller (for the performance reason.)
36  *
37  * @author Kohsuke Kawaguchi
38  */

39 public final class NamespaceContextImpl implements NamespaceContext2 {
40     private final XMLSerializer owner;
41
42     private String[] prefixes = new String[4];
43     private String[] nsUris = new String[4];
44 //    /**
45 //     * True if the correponding namespace declaration is an authentic one that should be printed.
46 //     *
47 //     * False if it's a re-discovered in-scope namespace binding available at the ancestor elements
48 //     * outside this marhsalling. The false value is used to incorporate the in-scope namespace binding
49 //     * information from {@link #inscopeNamespaceContext}. When false, such a declaration does not need
50 //     * to be printed, as it's already available in ancestors.
51 //     */

52 //    private boolean[] visible = new boolean[4];
53 //
54 //    /**
55 //     * {@link NamespaceContext} that informs this {@link XMLSerializer} about the
56 //     * in-scope namespace bindings of the ancestor elements outside this marshalling.
57 //     *
58 //     * <p>
59 //     * This is used when the marshaller is marshalling into a subtree that has ancestor
60 //     * elements created outside the JAXB marshaller.
61 //     *
62 //     * Its {@link NamespaceContext#getPrefix(String)} is used to discover in-scope namespace
63 //     * binding,
64 //     */

65 //    private final NamespaceContext inscopeNamespaceContext;
66
67     /**
68      * Number of URIs declared. Identifies the valid portion of
69      * the {@link #prefixes} and {@link #nsUris} arrays.
70      */

71     private int size;
72
73     private Element current;
74
75     /**
76      * This is the {@link Element} whose prev==null.
77      * This element is used to hold the contextual namespace bindings
78      * that are assumed to be outside of the document we are marshalling.
79      * Specifically the xml prefix and any other user-specified bindings.
80      *
81      * @see NamespacePrefixMapper#getPreDeclaredNamespaceUris()
82      */

83     private final Element top;
84
85     /**
86      * Never null.
87      */

88     private NamespacePrefixMapper prefixMapper = defaultNamespacePrefixMapper;
89
90     /**
91      * True to allow new URIs to be declared. False otherwise.
92      */

93     public boolean collectionMode;
94
95
96     public NamespaceContextImpl(XMLSerializer owner) {
97         this.owner = owner;
98
99         current = top = new Element(this,null);
100         // register namespace URIs that are implicitly bound
101         put(XMLConstants.XML_NS_URI,XMLConstants.XML_NS_PREFIX);
102     }
103
104     public void setPrefixMapper( NamespacePrefixMapper mapper ) {
105         if(mapper==null)
106             mapper = defaultNamespacePrefixMapper;
107         this.prefixMapper = mapper;
108     }
109
110     public NamespacePrefixMapper getPrefixMapper() {
111         return prefixMapper;
112     }
113
114     public void reset() {
115         current = top;
116         size = 1;
117         collectionMode = false;
118     }
119
120     /**
121      * Returns the prefix index to the specified URI.
122      * This method allocates a new URI if necessary.
123      */

124     public int declareNsUri( String uri, String preferedPrefix, boolean requirePrefix ) {
125         preferedPrefix = prefixMapper.getPreferredPrefix(uri,preferedPrefix,requirePrefix);
126
127         if(uri.length()==0) {
128             forint i=size-1; i>=0; i-- ) {
129                 if(nsUris[i].length()==0)
130                     return i; // already declared
131                 if(prefixes[i].length()==0) {
132                     // the default prefix is already taken.
133                     // move that URI to another prefix, then assign "" to the default prefix.
134                     assert current.defaultPrefixIndex==-1 && current.oldDefaultNamespaceUriIndex==-1;
135
136                     String oldUri = nsUris[i];
137                     String[] knownURIs = owner.nameList.namespaceURIs;
138
139                     if(current.baseIndex<=i) {
140                         // this default prefix is declared in this context. just reassign it
141
142                         nsUris[i] = "";
143
144                         int subst = put(oldUri,null);
145
146                         // update uri->prefix table if necessary
147                         forint j=knownURIs.length-1; j>=0; j-- ) {
148                             if(knownURIs[j].equals(oldUri)) {
149                                 owner.knownUri2prefixIndexMap[j] = subst;
150                                 break;
151                             }
152                         }
153                         if (current.elementLocalName != null) {
154                             current.setTagName(subst, current.elementLocalName, current.getOuterPeer());
155                         }
156                         return i;
157                     } else {
158                         // first, if the previous URI assigned to "" is
159                         // a "known URI", remember what we've reallocated
160                         // so that we can fix it when this context pops.
161                         forint j=knownURIs.length-1; j>=0; j-- ) {
162                             if(knownURIs[j].equals(oldUri)) {
163                                 current.defaultPrefixIndex = i;
164                                 current.oldDefaultNamespaceUriIndex = j;
165                                 // assert commented out; too strict/not valid any more
166                                 // assert owner.knownUri2prefixIndexMap[j]==current.defaultPrefixIndex;
167                                 // update the table to point to the prefix we'll declare
168                                 owner.knownUri2prefixIndexMap[j] = size;
169                                 break;
170                             }
171                         }
172                         if (current.elementLocalName!=null) {
173                             current.setTagName(size, current.elementLocalName, current.getOuterPeer());
174                         }
175                         
176                         put(nsUris[i],null);
177                         return put("""");
178                     }
179                 }
180             }
181
182             // "" isn't in use
183             return put("""");
184         } else {
185             // check for the existing binding
186             forint i=size-1; i>=0; i-- ) {
187                 String p = prefixes[i];
188                 if(nsUris[i].equals(uri)) {
189                     if (!requirePrefix || p.length()>0)
190                         return i;
191                     // declared but this URI is bound to empty. Look further
192                 }
193                 if(p.equals(preferedPrefix)) {
194                     // the suggested prefix is already taken. can't use it
195                     preferedPrefix = null;
196                 }
197             }
198
199             if(preferedPrefix==null && requirePrefix)
200                 // we know we can't bind to "", but we don't have any possible name at hand.
201                 // generate it here to avoid this namespace to be bound to "".
202                 preferedPrefix = makeUniquePrefix();
203
204             // haven't been declared. allocate a new one
205             // if the preferred prefix is already in use, it should have been set to null by this time
206             return put(uri, preferedPrefix);
207         }
208     }
209
210     public int force(@NotNull String uri, @NotNull String prefix) {
211         // check for the existing binding
212
213         forint i=size-1; i>=0; i-- ) {
214             if(prefixes[i].equals(prefix)) {
215                 if(nsUris[i].equals(uri))
216                     return i;   // found duplicate
217                 else
218                     // the prefix is used for another namespace. we need to declare it
219                     break;
220             }
221         }
222
223         return put(uri, prefix);
224     }
225
226     /**
227      * Puts this new binding into the declared prefixes list
228      * without doing any duplicate check.
229      *
230      * This can be used to forcibly set namespace declarations.
231      *
232      * <p>
233      * Most of the time {@link #declareNamespace(String, String, boolean)} shall be used.
234      *
235      * @return
236      *      the index of this new binding.
237      */

238     public int put(@NotNull String uri, @Nullable String prefix) {
239         if(size==nsUris.length) {
240             // reallocate
241             String[] u = new String[nsUris.length*2];
242             String[] p = new String[prefixes.length*2];
243             System.arraycopy(nsUris,0,u,0,nsUris.length);
244             System.arraycopy(prefixes,0,p,0,prefixes.length);
245             nsUris = u;
246             prefixes = p;
247         }
248         if(prefix==null) {
249             if(size==1)
250                 prefix = "";    // if this is the first user namespace URI we see, use "".
251             else {
252                 // otherwise make up an unique name
253                 prefix = makeUniquePrefix();
254             }
255         }
256         nsUris[size] = uri;
257         prefixes[size] = prefix;
258
259         return size++;
260     }
261
262     private String makeUniquePrefix() {
263         String prefix;
264         prefix = new StringBuilder(5).append("ns").append(size).toString();
265         while(getNamespaceURI(prefix)!=null) {
266             prefix += '_';  // under a rare circumstance there might be existing 'nsNNN', so rename them
267         }
268         return prefix;
269     }
270
271
272     public Element getCurrent() {
273         return current;
274     }
275
276     /**
277      * Returns the prefix index of the specified URI.
278      * It is an error if the URI is not declared.
279      */

280     public int getPrefixIndex( String uri ) {
281         forint i=size-1; i>=0; i-- ) {
282                 if(nsUris[i].equals(uri))
283                     return i;
284         }
285         throw new IllegalStateException();
286     }
287
288     /**
289      * Gets the prefix from a prefix index.
290      *
291      * The behavior is undefined if the index is out of range.
292      */

293     public String getPrefix(int prefixIndex) {
294         return prefixes[prefixIndex];
295     }
296
297     public String getNamespaceURI(int prefixIndex) {
298         return nsUris[prefixIndex];
299     }
300
301     /**
302      * Gets the namespace URI that is bound to the specified prefix.
303      *
304      * @return null
305      *      if the prefix is unbound.
306      */

307     public String getNamespaceURI(String prefix) {
308         forint i=size-1; i>=0; i-- )
309             if(prefixes[i].equals(prefix))
310                 return nsUris[i];
311         return null;
312     }
313
314     /**
315      * Returns the prefix of the specified URI,
316      * or null if none exists.
317      */

318     public String getPrefix( String uri ) {
319         if(collectionMode) {
320             return declareNamespace(uri,null,false);
321         } else {
322             forint i=size-1; i>=0; i-- )
323                 if(nsUris[i].equals(uri))
324                     return prefixes[i];
325             return null;
326         }
327     }
328
329     public Iterator<String> getPrefixes(String uri) {
330         String prefix = getPrefix(uri);
331         if(prefix==null)
332             return Collections.<String>emptySet().iterator();
333         else
334             return Collections.singleton(uri).iterator();
335     }
336
337     public String declareNamespace(String namespaceUri, String preferedPrefix, boolean requirePrefix) {
338         int idx = declareNsUri(namespaceUri,preferedPrefix,requirePrefix);
339         return getPrefix(idx);
340     }
341
342     /**
343      * Number of total bindings declared.
344      */

345     public int count() {
346         return size;
347     }
348
349
350     /**
351      * This model of namespace declarations maintain the following invariants.
352      *
353      * <ul>
354      *  <li>If a non-empty prefix is declared, it will never be reassigned to different namespace URIs.</li>
355      * </ul>
356      */

357     public final class Element {
358
359         public final NamespaceContextImpl context;
360
361         /**
362          * {@link Element}s form a doubly-linked list.
363          */

364         private final Element prev;
365         private Element next;
366
367         private int oldDefaultNamespaceUriIndex;
368         private int defaultPrefixIndex;
369
370
371         /**
372          * The numbe of prefixes declared by ancestor {@link Element}s.
373          */

374         private int baseIndex;
375
376         /**
377          * The depth of the {@link Element}.
378          *
379          * This value is equivalent as the result of the following computation.
380          *
381          * <pre>
382          * int depth() {
383          *   int i=-1;
384          *   for(Element e=this; e!=null;e=e.prev)
385          *     i++;
386          *   return i;
387          * }
388          * </pre>
389          */

390         private final int depth;
391
392
393
394         private int elementNamePrefix;
395         private String elementLocalName;
396
397         /**
398          * Tag name of this element.
399          * Either this field is used or the {@link #elementNamePrefix} and {@link #elementLocalName} pair.
400          */

401         private Name elementName;
402
403         /**
404          * Used for the binder. The JAXB object that corresponds to this element.
405          */

406         private Object outerPeer;
407         private Object innerPeer;
408
409
410         private Element(NamespaceContextImpl context,Element prev) {
411             this.context = context;
412             this.prev = prev;
413             this.depth = (prev==null) ? 0 : prev.depth+1;
414         }
415
416         /**
417          * Returns true if this {@link Element} represents the root element that
418          * we are marshalling.
419          */

420         public boolean isRootElement() {
421             return depth==1;
422         }
423
424         public Element push() {
425             if(next==null)
426                 next = new Element(context,this);
427             next.onPushed();
428             return next;
429         }
430
431         public Element pop() {
432             if(oldDefaultNamespaceUriIndex>=0) {
433                 // restore the old default namespace URI binding
434                 context.owner.knownUri2prefixIndexMap[oldDefaultNamespaceUriIndex] = defaultPrefixIndex;
435             }
436             context.size = baseIndex;
437             context.current = prev;
438             // release references to user objects
439             outerPeer = innerPeer = null;
440             return prev;
441         }
442
443         private void onPushed() {
444             oldDefaultNamespaceUriIndex = defaultPrefixIndex = -1;
445             baseIndex = context.size;
446             context.current = this;
447         }
448
449         public void setTagName( int prefix, String localName, Object outerPeer ) {
450             assert localName!=null;
451             this.elementNamePrefix = prefix;
452             this.elementLocalName = localName;
453             this.elementName = null;
454             this.outerPeer = outerPeer;
455         }
456
457         public void setTagName( Name tagName, Object outerPeer ) {
458             assert tagName!=null;
459             this.elementName = tagName;
460             this.outerPeer = outerPeer;
461         }
462
463         public void startElement(XmlOutput out, Object innerPeer) throws IOException, XMLStreamException {
464             this.innerPeer = innerPeer;
465             if(elementName!=null) {
466                 out.beginStartTag(elementName);
467             } else {
468                 out.beginStartTag(elementNamePrefix,elementLocalName);
469             }
470         }
471
472         public void endElement(XmlOutput out) throws IOException, SAXException, XMLStreamException {
473             if(elementName!=null) {
474                 out.endTag(elementName);
475                 elementName = null;
476             } else {
477                 out.endTag(elementNamePrefix,elementLocalName);
478             }
479         }
480
481         /**
482          * Gets the number of bindings declared on this element.
483          */

484         public final int count() {
485             return context.size-baseIndex;
486         }
487
488         /**
489          * Gets the prefix declared in this context.
490          *
491          * @param idx
492          *      between 0 and {@link #count()}
493          */

494         public final String getPrefix(int idx) {
495             return context.prefixes[baseIndex+idx];
496         }
497
498         /**
499          * Gets the namespace URI declared in this context.
500          *
501          * @param idx
502          *      between 0 and {@link #count()}
503          */

504         public final String getNsUri(int idx) {
505             return context.nsUris[baseIndex+idx];
506         }
507
508         public int getBase() {
509             return baseIndex;
510         }
511
512         public Object getOuterPeer() {
513             return outerPeer;
514         }
515
516         public Object getInnerPeer() {
517             return innerPeer;
518         }
519
520         /**
521          * Gets the parent {@link Element}.
522          */

523         public Element getParent() {
524             return prev;
525         }
526     }
527
528
529     /**
530      * Default {@link NamespacePrefixMapper} implementation used when
531      * it is not specified by the user.
532      */

533     private static final NamespacePrefixMapper defaultNamespacePrefixMapper = new NamespacePrefixMapper() {
534         public String getPreferredPrefix(String namespaceUri, String suggestion, boolean requirePrefix) {
535             if( namespaceUri.equals(WellKnownNamespace.XML_SCHEMA_INSTANCE) )
536                 return "xsi";
537             if( namespaceUri.equals(WellKnownNamespace.XML_SCHEMA) )
538                 return "xs";
539             if( namespaceUri.equals(WellKnownNamespace.XML_MIME_URI) )
540                 return "xmime";
541             return suggestion;
542         }
543     };
544 }
545