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 for( int 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 for( int 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 for( int 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 for( int 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 for( int 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 for( int 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 for( int 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 for( int 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