Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Sort out failing test-case. Improve code namespace handling.

Remove enumeration for tracking state... it's easier with a simple boolean and some condition checks.
  • Loading branch information...
commit ba464a74d5839e87beef2c73afa612efadfcd4e2 1 parent 7bfd216
Rolf authored January 09, 2013
600  core/src/java/org/jdom2/jaxb/JDOMStreamWriter.java
@@ -55,7 +55,6 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
55 55
 
56 56
 import java.util.ArrayList;
57 57
 import java.util.LinkedList;
58  
-import java.util.List;
59 58
 
60 59
 import javax.xml.namespace.NamespaceContext;
61 60
 import javax.xml.stream.XMLStreamException;
@@ -80,15 +79,14 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
80 79
 public class JDOMStreamWriter implements XMLStreamWriter {
81 80
 
82 81
 	private static final DefaultJDOMFactory DEFFAC = new DefaultJDOMFactory();
83  
-	private static final Namespace[] EMPTYNSA = new Namespace[0];
84 82
 
85 83
 	// The optional global namespace context for namespace bindings.
86 84
 	private NamespaceContext globalcontext = null;
87 85
 
88 86
 	// The active Element NamespaceStack of actual used Namespaces.
89  
-	private final NamespaceStack usednsstack = new NamespaceStack();
  87
+	private NamespaceStack usednsstack = new NamespaceStack();
90 88
 	// The stack of namespace bindings which may not necessarily be used in an element
91  
-	private final NamespaceStack boundstack = new NamespaceStack();
  89
+	private NamespaceStack boundstack = new NamespaceStack();
92 90
 	
93 91
 	// The namespaces pending in the current Element that need to be actively bound.
94 92
 	private LinkedList<Namespace> pendingns = new LinkedList<Namespace>();
@@ -97,6 +95,7 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
97 95
 	private final boolean repairnamespace;
98 96
 
99 97
 	private Document document = null;
  98
+	private boolean done = false;
100 99
 	private Parent parent = null;
101 100
 	private Element activeelement = null;
102 101
 	private boolean isempty = false;
@@ -106,8 +105,6 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
106 105
 	
107 106
 	private final JDOMFactory factory;
108 107
 
109  
-	private StreamWriterState state = StreamWriterState.BEFORE_DOCUMENT_START;
110  
-
111 108
 	/**
112 109
 	 * Create a JDOMStreamWriter with the default JDOMFactory for creating JDOM
113 110
 	 * Content.
@@ -140,11 +137,11 @@ public JDOMStreamWriter(final JDOMFactory fac, final boolean repairnamespace) {
140 137
 	 * @return The created {@link Document}
141 138
 	 */
142 139
 	public Document getDocument() {
143  
-		if (state == StreamWriterState.DOCUMENT_ENDED) {
  140
+		if (done && document != null) {
144 141
 			return document;
145 142
 		}
146 143
 		
147  
-		if (state == StreamWriterState.CLOSED) {
  144
+		if (done) {
148 145
 			throw new IllegalStateException("Writer is closed");
149 146
 		}
150 147
 
@@ -171,41 +168,36 @@ public void writeStartDocument(final String encoding, final String version)
171 168
 		// JDOM has no support for XML Version specification/handling
172 169
 		// ignore version.
173 170
 		
174  
-		if (this.state != StreamWriterState.BEFORE_DOCUMENT_START) {
  171
+		if (done || document != null) {
175 172
 			throw new IllegalStateException(
176  
-					"Cannot write start document while in state " + this.state);
  173
+					"Cannot write start document twice.");
177 174
 		}
178 175
 
179  
-		this.document = factory.document(null);
  176
+		document = factory.document(null);
  177
+		parent = document;
180 178
 		if (encoding != null && !"".equals(encoding))
181 179
 			this.document.setProperty("ENCODING", encoding);
182 180
 
183  
-
184  
-		this.state = StreamWriterState.DOCUMENT_START;
185 181
 		activeelement = null;
186 182
 	}
187 183
 
188 184
 	@Override
189 185
 	public void setNamespaceContext(final NamespaceContext context)
190 186
 			throws XMLStreamException {
191  
-		if (state != StreamWriterState.DOCUMENT_START) {
  187
+		if (document == null || document.hasRootElement()) {
192 188
 			throw new XMLStreamException("Can only set the NamespaceContext at the Document start");
193 189
 		}
194 190
 		globalcontext = context;
195 191
 	}
196 192
 
197 193
 	@Override
198  
-	public void writeDTD(final String dtd) throws XMLStreamException {
199  
-		// FIXME to do ... ?
200  
-		throw new UnsupportedOperationException("not supported yet");
201  
-	}
202  
-
203  
-	@Override
204 194
 	public String getPrefix(final String uri) throws XMLStreamException {
205  
-		for (Namespace n : boundstack) {
206  
-			if (n.getURI().equals(uri)) {
207  
-				return n.getPrefix();
208  
-			}
  195
+		if (document == null) {
  196
+			return null;
  197
+		}
  198
+		final Namespace n = boundstack.getFirstNamespaceForURI(uri);
  199
+		if (n != null) {
  200
+			return n.getPrefix();
209 201
 		}
210 202
 		if (globalcontext != null) {
211 203
 			return globalcontext.getPrefix(uri);
@@ -219,7 +211,7 @@ public void setPrefix(final String prefix, final String uri)
219 211
 		if (prefix == null) {
220 212
 			throw new IllegalArgumentException("prefix may not be null");
221 213
 		}
222  
-		if (state != StreamWriterState.IN_ELEMENT && state != StreamWriterState.DOCUMENT_START) {
  214
+		if (document == null || done) {
223 215
 			throw new IllegalStateException(
224 216
 					"Attempt to set prefix at an illegal stream state.");
225 217
 		}
@@ -235,7 +227,7 @@ public void setPrefix(final String prefix, final String uri)
235 227
 
236 228
 		final Namespace ns = Namespace.getNamespace(prefix, uri);
237 229
 		if (!boundstack.isInScope(ns)) {
238  
-			ArrayList<Namespace> al = new ArrayList<Namespace>();
  230
+			final ArrayList<Namespace> al = new ArrayList<Namespace>();
239 231
 			for (Namespace n : boundstack) {
240 232
 				if (n.getPrefix().equals(prefix)) {
241 233
 					// do not rebind the same prefix with the old URI.
@@ -245,7 +237,7 @@ public void setPrefix(final String prefix, final String uri)
245 237
 			}
246 238
 			// bind the new URI to the prefix.
247 239
 			al.add(ns);
248  
-			// kill the previous binding.
  240
+			// kill the previous bindings.
249 241
 			boundstack.pop();
250 242
 			// reset the binding.
251 243
 			boundstack.push(al);
@@ -257,126 +249,10 @@ public void setDefaultNamespace(final String uri) throws XMLStreamException {
257 249
 		setPrefix(JDOMConstants.NS_PREFIX_DEFAULT, uri);
258 250
 	}
259 251
 
260  
-	/**
261  
-	 * Simple method that implements the start-element logic without the spec-required checks
262  
-	 * for null values.
263  
-	 * @param prefix The prefix for the namespace (may be null).
264  
-	 * @param localName The localName for the Element
265  
-	 * @param namespaceURI The namespaceURI (null implies "");
266  
-	 * @param withpfx true if the prefix is user-specified.
267  
-	 * @throws XMLStreamException if the stream is not in a good state for an Element.
268  
-	 */
269  
-	private void buildStartElement(final String prefix, final String localName,
270  
-			final String namespaceURI, boolean withpfx) throws XMLStreamException {
271  
-		
272  
-		flushActiveElement();
273  
-		flushActiveText();
274  
-		
275  
-		final Namespace ns = resolveElementNamespace(prefix, namespaceURI, withpfx);
276  
-		final Element e = factory.element(localName, ns);
277  
-
278  
-		switch (state) {
279  
-			case DOCUMENT_START:
280  
-				factory.setRoot(document, e);
281  
-				parent = e;
282  
-				state = StreamWriterState.IN_ELEMENT;
283  
-				break;
284  
-
285  
-			case IN_ELEMENT:
286  
-				factory.addContent(parent, e);
287  
-				parent = e;
288  
-				break;
289  
-
290  
-			default:
291  
-				throw new XMLStreamException(
292  
-						"Cannot write new element when in state " + state);
293  
-		}
294  
-		activeelement = e;
295  
-		isempty = false;
296  
-	}
297  
-
298  
-	private Namespace resolveElementNamespace(String prefix, String namespaceURI,
299  
-			boolean withpfx) throws XMLStreamException {
300  
-		final Namespace ns = Namespace.getNamespace(prefix, namespaceURI);
301  
-		if (withpfx) {
302  
-			final Namespace bnd = boundstack.getNamespaceForPrefix(prefix);
303  
-			if (bnd == null || bnd == ns) {
304  
-				// no existing binding for prefix, or same binding.
305  
-				return ns;
306  
-			}
307  
-			// prefix is bound to a different URI
308  
-			if (repairnamespace) {
309  
-				return Namespace.getNamespace(generatePrefix(), namespaceURI);
310  
-			}
311  
-			throw new XMLStreamException("Namespace prefix " + prefix + 
312  
-					" in this scope is bound to a different URI '" + bnd.getURI() + 
313  
-					"' (repairing not set for this XMLStreamWriter)."); 
314  
-		}
315  
-		// see if the default namespace is bound correctly...
316  
-		if (boundstack.getNamespaceForPrefix("") == ns) {
317  
-			return ns;
318  
-		}
319  
-		// nope, so see if there's any other bound namespace with the same URI.
320  
-		final Namespace bound = boundstack.getFirstNamespaceForURI(namespaceURI);
321  
-		if (bound != null) {
322  
-			return bound;
323  
-		}
324  
-		// there are no existing bindings with the specified URI.
325  
-		if (repairnamespace) {
326  
-			return Namespace.getNamespace(generatePrefix(), namespaceURI);
327  
-		}
328  
-		throw new XMLStreamException("Namespace URI " + namespaceURI + 
329  
-				" is not bound in this scope (repairing not set for this XMLStreamWriter)."); 
330  
-	}
331  
-
332  
-	private Namespace resolveAttributeNamespace(String prefix, String namespaceURI,
333  
-			boolean withpfx) throws XMLStreamException {
334  
-		final Namespace ns = Namespace.getNamespace(prefix, namespaceURI);
335  
-		if (ns == Namespace.NO_NAMESPACE) {
336  
-			return ns;
337  
-		}
338  
-		if (withpfx && !"".equals(prefix)) {
339  
-
340  
-			Namespace bnd = boundstack.getNamespaceForPrefix(prefix);
341  
-			
342  
-			if (bnd == null || bnd == ns) {
343  
-				// no existing binding for prefix, or same binding.
344  
-				return ns;
345  
-			}
346  
-			// prefix is bound to a different URI
347  
-			if (repairnamespace) {
348  
-				final Namespace gen = Namespace.getNamespace(generatePrefix(), namespaceURI);
349  
-				setPrefix(gen.getPrefix(), gen.getURI());
350  
-				return gen;
351  
-			}
352  
-			throw new XMLStreamException("Namespace prefix " + prefix + 
353  
-					" in this scope is bound to a different URI '" + bnd.getURI() + 
354  
-					"' (repairing not set for this XMLStreamWriter)."); 
355  
-		}
356  
-		
357  
-		// see if there's any other bound namespace with the same URI.
358  
-		final Namespace[] bound = boundstack.getAllNamespacesForURI(namespaceURI);
359  
-		for (Namespace b : bound) {
360  
-			if (!"".equals(b.getPrefix())) {
361  
-				// use an existing prefixed Namespace binding.
362  
-				// good for both repairing and non-repairing
363  
-				return b;
364  
-			}
365  
-		}
366  
-		// there are no existing prefixed bindings with the specified URI.
367  
-		if (repairnamespace || bound.length > 0) {
368  
-			return Namespace.getNamespace(generatePrefix(), namespaceURI);
369  
-		}
370  
-		throw new XMLStreamException("Namespace URI " + namespaceURI + 
371  
-				" is not bound in this attribute scope (repairing not set for this XMLStreamWriter)."); 
372  
-	}
373  
-
374  
-	private String generatePrefix() {
375  
-		String pfx = String.format("ns%03d", ++genprefix);
376  
-		while (boundstack.getNamespaceForPrefix(pfx) != null) {
377  
-			pfx = String.format("ns%03d", ++genprefix);
378  
-		}
379  
-		return pfx;
  252
+	@Override
  253
+	public void writeDTD(final String dtd) throws XMLStreamException {
  254
+		// FIXME to do ... ?
  255
+		throw new UnsupportedOperationException("not supported yet");
380 256
 	}
381 257
 
382 258
 	@Override
@@ -393,7 +269,7 @@ public void writeStartElement(final String namespaceURI, final String localName)
393 269
 		if (localName == null) {
394 270
 			throw new XMLStreamException("Cannot have a null localname");
395 271
 		}
396  
-		this.buildStartElement("", localName, namespaceURI, false);
  272
+		this.buildElement("", localName, namespaceURI, false, false);
397 273
 	}
398 274
 
399 275
 	@Override
@@ -408,45 +284,7 @@ public void writeStartElement(final String prefix, final String localName,
408 284
 		if (namespaceURI == null) {
409 285
 			throw new XMLStreamException("Cannot have a null namespaceURI");
410 286
 		}
411  
-		buildStartElement(prefix, localName, namespaceURI, true);
412  
-	}
413  
-
414  
-	/**
415  
-	 * Simple method that implements the empty-element logic without the spec-required
416  
-	 * null-check logic
417  
-	 * @param prefix The namespace prefix (may be null).
418  
-	 * @param localName The Element tag
419  
-	 * @param namespaceURI The namespace URI (may be null).
420  
-	 * @throws XMLStreamException If the stream is not in an appropriate state for a new Element.
421  
-	 */
422  
-	private final void buildEmptyElement(final String prefix, final String localName,
423  
-			final String namespaceURI, final boolean withpfx) throws XMLStreamException {
424  
-		
425  
-		flushActiveElement();
426  
-		flushActiveText();
427  
-		
428  
-		final Namespace ns = resolveElementNamespace(prefix, namespaceURI, withpfx);
429  
-
430  
-		final Element e = factory.element(localName, ns);
431  
-
432  
-		switch (state) {
433  
-			case DOCUMENT_START:
434  
-				// an empty root element
435  
-				factory.setRoot(document, e);
436  
-				state = StreamWriterState.OUT_ELEMENT;
437  
-				break;
438  
-
439  
-			case IN_ELEMENT:
440  
-				factory.addContent(parent, e);
441  
-				// still in element
442  
-				break;
443  
-
444  
-			default:
445  
-				throw new XMLStreamException(
446  
-						"Cannot write new element when in state " + state);
447  
-		}
448  
-		activeelement = e;
449  
-		isempty = true;
  287
+		buildElement(prefix, localName, namespaceURI, true, false);
450 288
 	}
451 289
 
452 290
 	@Override
@@ -455,7 +293,7 @@ public void writeEmptyElement(final String localName)
455 293
 		if (localName == null) {
456 294
 			throw new XMLStreamException("Cannot have a null localname");
457 295
 		}
458  
-		this.buildEmptyElement("", localName, "", false);
  296
+		this.buildElement("", localName, "", false, true);
459 297
 	}
460 298
 
461 299
 	@Override
@@ -467,7 +305,7 @@ public void writeEmptyElement(final String namespaceURI,
467 305
 		if (localName == null) {
468 306
 			throw new XMLStreamException("Cannot have a null localname");
469 307
 		}
470  
-		this.buildEmptyElement("", localName, namespaceURI, false);
  308
+		this.buildElement("", localName, namespaceURI, false, true);
471 309
 	}
472 310
 
473 311
 	@Override
@@ -482,7 +320,7 @@ public void writeEmptyElement(final String prefix, final String localName,
482 320
 		if (namespaceURI == null) {
483 321
 			throw new XMLStreamException("Cannot have a null namespaceURI");
484 322
 		}
485  
-		buildEmptyElement(prefix, localName, namespaceURI, true);
  323
+		buildElement(prefix, localName, namespaceURI, true, true);
486 324
 	}
487 325
 	
488 326
 	@Override
@@ -494,47 +332,18 @@ public void writeDefaultNamespace(final String namespaceURI)
494 332
 	@Override
495 333
 	public void writeNamespace(final String prefix, final String namespaceURI)
496 334
 			throws XMLStreamException {
  335
+		if (activeelement == null) {
  336
+			throw new IllegalStateException("Can only write a Namespace after starting an Element" +
  337
+					" and before adding content to that Element.");
  338
+		}
497 339
 		if (prefix == null || JDOMConstants.NS_PREFIX_XMLNS.equals(prefix)) {
  340
+			// recurse with the "" prefix.
498 341
 			writeNamespace("", namespaceURI);
499  
-			return;
500  
-		}
501  
-		final Namespace ns = Namespace.getNamespace(prefix, namespaceURI);
502  
-		switch (state) {
503  
-			case IN_ELEMENT:
504  
-				if (activeelement == null) {
505  
-					throw new IllegalStateException("Not able to write a Namespace after adding content.");
506  
-				}
507  
-				pendingns.add(ns);
508  
-				break;
509  
-
510  
-			default:
511  
-				throw new IllegalStateException(
512  
-						"Cannot write namespace when in state " + state);
  342
+		} else {
  343
+			pendingns.add(Namespace.getNamespace(prefix, namespaceURI));
513 344
 		}
514 345
 	}
515 346
 
516  
-	private final void buildAttribute(final String prefix,
517  
-			final String namespaceURI, final String localName,
518  
-			final String value, final boolean withpfx) throws XMLStreamException {
519  
-		if (state != StreamWriterState.IN_ELEMENT) {
520  
-			throw new IllegalStateException(
521  
-					"Cannot write attribute when in state " + state);
522  
-		}
523  
-		if (localName == null) {
524  
-			throw new XMLStreamException("localName is not allowed to be null");
525  
-		}
526  
-		if (value == null) {
527  
-			throw new XMLStreamException("value is not allowed to be null");
528  
-		}
529  
-		if (activeelement == null) { 
530  
-			throw new IllegalStateException("Cannot add Attributes to an Element after other content was added.");
531  
-		}
532  
-		
533  
-		Namespace ns = resolveAttributeNamespace(prefix, namespaceURI, withpfx);
534  
-		
535  
-		factory.setAttribute(activeelement, factory.attribute(localName, value, ns));
536  
-	}
537  
-
538 347
 	@Override
539 348
 	public void writeAttribute(final String localName, final String value)
540 349
 			throws XMLStreamException {
@@ -555,77 +364,43 @@ public void writeAttribute(final String prefix, final String namespaceURI,
555 364
 		buildAttribute(prefix == null ? "" : prefix, namespaceURI == null ? "" : namespaceURI, localName, value, true);
556 365
 	}
557 366
 
558  
-	private final void flushActiveElement() {
559  
-		if (activeelement != null) {
560  
-			
561  
-			/*
562  
-			 * Add any written namespaces to the element as declared namespaces
563  
-			 * unless they are implied namespaces used by attributes, or something.
564  
-			 * If this Element is expecting content (it's not empty) then we also
565  
-			 * push the modified Element on to the Namespace stack.
566  
-			 */
567  
-
568  
-			boolean mod = false;
569  
-			usednsstack.push(activeelement);
570  
-			for (Namespace ns : pendingns) {
571  
-				if (!usednsstack.isInScope(ns)) {
572  
-					activeelement.addNamespaceDeclaration(ns);
573  
-					mod = true;
574  
-				}
575  
-			}
576  
-			pendingns.clear();
577  
-			
578  
-			if (mod) {
579  
-				usednsstack.pop();
580  
-				if (isempty) {
581  
-					activeelement = null;
582  
-					return;
583  
-				}
584  
-				usednsstack.push(activeelement);
585  
-			}
586  
-			if (isempty) {
587  
-				activeelement = null;
588  
-				return;
589  
-			}
590  
-			boundstack.push(activeelement);
591  
-			boundstack.push(EMPTYNSA);
592  
-			
593  
-			activeelement = null;
594  
-		}
595  
-	}
596  
-
597  
-	private final void flushActiveText() {
598  
-		activetext = null;
599  
-	}
600  
-
601 367
 	@Override
602 368
 	public void writeComment(final String data) throws XMLStreamException {
  369
+		if (document == null || done) {
  370
+			throw new XMLStreamException("Can only add a Comment to the Document or an Element.");
  371
+		}
603 372
 		flushActiveElement();
604 373
 		flushActiveText();
605  
-		factory.addContent(state == StreamWriterState.DOCUMENT_START ? document : parent, 
606  
-				factory.comment(data));
  374
+		factory.addContent(parent, factory.comment(data));
607 375
 	}
608 376
 
609 377
 	@Override
610 378
 	public void writeProcessingInstruction(final String target)
611 379
 			throws XMLStreamException {
  380
+		if (document == null || done) {
  381
+			throw new XMLStreamException("Can only add a ProcessingInstruction to the Document or an Element.");
  382
+		}
612 383
 		flushActiveElement();
613 384
 		flushActiveText();
614  
-		factory.addContent(state == StreamWriterState.DOCUMENT_START ? document : parent, 
615  
-				factory.processingInstruction(target));
  385
+		factory.addContent(parent, factory.processingInstruction(target));
616 386
 	}
617 387
 
618 388
 	@Override
619 389
 	public void writeProcessingInstruction(final String target,
620 390
 			final String data) throws XMLStreamException {
  391
+		if (document == null || done) {
  392
+			throw new XMLStreamException("Can only add a ProcessingInstruction to the Document or an Element.");
  393
+		}
621 394
 		flushActiveElement();
622 395
 		flushActiveText();
623  
-		factory.addContent(state == StreamWriterState.DOCUMENT_START ? document : parent, 
624  
-				factory.processingInstruction(target, data));
  396
+		factory.addContent(parent, factory.processingInstruction(target, data));
625 397
 	}
626 398
 
627 399
 	@Override
628 400
 	public void writeCData(final String data) throws XMLStreamException {
  401
+		if (!(parent instanceof Element)) {
  402
+			throw new XMLStreamException("Can only writeCDATA() inside an Element.");
  403
+		}
629 404
 		flushActiveElement();
630 405
 		flushActiveText();
631 406
 		factory.addContent(parent, factory.cdata(data));
@@ -633,6 +408,9 @@ public void writeCData(final String data) throws XMLStreamException {
633 408
 
634 409
 	@Override
635 410
 	public void writeEntityRef(final String name) throws XMLStreamException {
  411
+		if (!(parent instanceof Element)) {
  412
+			throw new XMLStreamException("Can only writeEntityRef() inside an Element.");
  413
+		}
636 414
 		flushActiveElement();
637 415
 		flushActiveText();
638 416
 		factory.addContent(parent, factory.entityRef(name));
@@ -640,26 +418,22 @@ public void writeEntityRef(final String name) throws XMLStreamException {
640 418
 	
641 419
 	@Override
642 420
 	public void writeCharacters(final String chars) throws XMLStreamException {
  421
+		if (document == null || done) {
  422
+			throw new XMLStreamException("Unable to add Characters at this point in the stream.");
  423
+		}
643 424
 		flushActiveElement();
644 425
 		if (chars == null) {
645 426
 			return;
646 427
 		}
647  
-		switch (state) {
648  
-			case IN_ELEMENT:
649  
-				if (activetext != null) {
650  
-					activetext.append(chars);
651  
-				} else {
652  
-					activetext = factory.text(chars);
653  
-					factory.addContent(parent, activetext);
654  
-				}
655  
-				return;
656  
-			case DOCUMENT_START:
657  
-			case OUT_ELEMENT:
658  
-				// JDOM Does not support activetext at document level (it can only be space anyway).
659  
-				return;
660  
-			default:
661  
-				throw new XMLStreamException("Unable to add Characters at this point in the stream.");
  428
+		if (parent instanceof Element) {
  429
+			if (activetext != null) {
  430
+				activetext.append(chars);
  431
+			} else {
  432
+				activetext = factory.text(chars);
  433
+				factory.addContent(parent, activetext);
  434
+			}
662 435
 		}
  436
+		// ignore case where parent is Document.
663 437
 	}
664 438
 
665 439
 	@Override
@@ -670,38 +444,38 @@ public void writeCharacters(final char[] chars, final int start,
670 444
 
671 445
 	@Override
672 446
 	public void writeEndElement() throws XMLStreamException {
673  
-		if (state != StreamWriterState.IN_ELEMENT) {
674  
-			throw new XMLStreamException(
675  
-					"Cannot write end element when in state " + state);
  447
+		if (!(parent instanceof Element)) {
  448
+			throw new XMLStreamException("Cannot end an Element unless you are in an Element.");
676 449
 		}
677 450
 		flushActiveElement();
678 451
 		flushActiveText();
679 452
 		usednsstack.pop();
680 453
 		boundstack.pop();
681 454
 		boundstack.pop();
  455
+		boundstack.pop();
682 456
 		parent = parent.getParent();
683  
-		if (parent == document) {
684  
-			// back to root element
685  
-			state = StreamWriterState.OUT_ELEMENT;
686  
-		}
687 457
 	}
688 458
 
689 459
 	@Override
690 460
 	public void writeEndDocument() throws XMLStreamException {
691  
-		if (state != StreamWriterState.OUT_ELEMENT) {
  461
+		// flush may change state if isempty root element
  462
+		if (document == null || done || parent instanceof Element) {
692 463
 			throw new IllegalStateException(
693  
-					"Cannot write end document before writing end of root element");
  464
+					"Cannot write end document before writing the end of root element");
694 465
 		}
695  
-		state = StreamWriterState.DOCUMENT_ENDED;
  466
+		flushActiveElement();
  467
+		done = true;
696 468
 	}
697 469
 
698 470
 	@Override
699 471
 	public void close() throws XMLStreamException {
700 472
 		this.document = null;
701 473
 		this.parent = null;
702  
-		this.state = StreamWriterState.CLOSED;
703 474
 		activeelement = null;
704 475
 		activetext = null;
  476
+		boundstack = null;
  477
+		usednsstack = null;
  478
+		done = true;
705 479
 	}
706 480
 
707 481
 	@Override
@@ -711,9 +485,10 @@ public void flush() throws XMLStreamException {
711 485
 
712 486
 	@Override
713 487
 	public NamespaceContext getNamespaceContext() {
714  
-		final List<Namespace> namespaces = getCurrentNamespaces();
715  
-
716  
-		return new JDOMNamespaceContext(namespaces.toArray(new Namespace[namespaces.size()]));
  488
+		if (document == null) {
  489
+			return new JDOMNamespaceContext(new Namespace[0]);
  490
+		}
  491
+		return new JDOMNamespaceContext(boundstack.getScope());
717 492
 	}
718 493
 
719 494
 	@Override
@@ -725,23 +500,214 @@ public Object getProperty(String name) throws IllegalArgumentException {
725 500
 	// Private methods.
726 501
 	// *****************************
727 502
 
728  
-	private List<Namespace> getCurrentNamespaces() {
729  
-		switch (state) {
730  
-			case IN_ELEMENT:
731  
-				return parent.getNamespacesInScope();
  503
+	/**
  504
+	 * Simple method that implements the empty-element logic without the spec-required
  505
+	 * null-check logic
  506
+	 * @param prefix The namespace prefix (may be null).
  507
+	 * @param localName The Element tag
  508
+	 * @param namespaceURI The namespace URI (may be null).
  509
+	 * @param empty Is this an Empty element (expecting children?)
  510
+	 * @throws XMLStreamException If the stream is not in an appropriate state for a new Element.
  511
+	 */
  512
+	private final void buildElement(final String prefix, final String localName,
  513
+			final String namespaceURI, final boolean withpfx, boolean empty) throws XMLStreamException {
  514
+		
  515
+		if (document == null || done) {
  516
+			throw new XMLStreamException(
  517
+					"Cannot write new element when in current state.");
  518
+		}
  519
+		
  520
+		if (parent == document && document.hasRootElement()) {
  521
+			throw new XMLStreamException(
  522
+					"Document can have only one root Element.");
  523
+		}
  524
+			
  525
+		
  526
+		flushActiveElement();
  527
+		flushActiveText();
  528
+		
  529
+		// create an element-specific namespace binding layer.
  530
+		boundstack.push();
  531
+		
  532
+		final Namespace ns = resolveElementNamespace(prefix, namespaceURI, withpfx);
732 533
 
733  
-			case DOCUMENT_START:
734  
-			case OUT_ELEMENT:
735  
-				return document.getNamespacesInScope();
  534
+		final Element e = factory.element(localName, ns);
736 535
 
737  
-			default:
738  
-				throw new IllegalStateException(
739  
-						"Attempt to get namespaces in unsupported state "
740  
-								+ this.state);
  536
+		factory.addContent(parent, e);
  537
+		activeelement = e;
  538
+		if (empty) {
  539
+			isempty = true;
  540
+		} else {
  541
+			isempty = false;
  542
+			parent = e;
741 543
 		}
742 544
 	}
743 545
 
744  
-	private enum StreamWriterState {
745  
-		BEFORE_DOCUMENT_START, DOCUMENT_START, IN_ELEMENT, OUT_ELEMENT, DOCUMENT_ENDED, CLOSED
  546
+	private Namespace resolveElementNamespace(String prefix, String namespaceURI,
  547
+			boolean withpfx) throws XMLStreamException {
  548
+		
  549
+		if ("".equals(namespaceURI)) {
  550
+			final Namespace defns = boundstack.getNamespaceForPrefix("");
  551
+			if(Namespace.NO_NAMESPACE != defns) {
  552
+				// inconsistency in XMLStreamWriter specification....
  553
+				// In theory the repairing code should create a generated prefic for unbound
  554
+				// namespace URI, but you can't create a prefixed ""-URI namespace.
  555
+				throw new XMLStreamException("This attempt to use the empty URI \"\" as an " +
  556
+						"Element Namespace is illegal because the default Namespace is already " +
  557
+						"bound to the URI '" + defns.getURI() + "'. You must call " +
  558
+						"setPrefix(\"\", \"\") prior to this call.");
  559
+			}
  560
+		}
  561
+		
  562
+		final Namespace ns = Namespace.getNamespace(prefix, namespaceURI);
  563
+		
  564
+		if (withpfx) {
  565
+			final Namespace bnd = boundstack.getNamespaceForPrefix(prefix);
  566
+			if (bnd == null || bnd == ns) {
  567
+				// no existing binding for prefix, or same binding.
  568
+				return ns;
  569
+			}
  570
+			// prefix is bound to a different URI
  571
+			if (repairnamespace) {
  572
+				return Namespace.getNamespace(generatePrefix(), namespaceURI);
  573
+			}
  574
+			throw new XMLStreamException("Namespace prefix " + prefix + 
  575
+					" in this scope is bound to a different URI '" + bnd.getURI() + 
  576
+					"' (repairing not set for this XMLStreamWriter)."); 
  577
+		}
  578
+		// see if the default namespace is bound correctly...
  579
+		if (boundstack.getNamespaceForPrefix("") == ns) {
  580
+			return ns;
  581
+		}
  582
+		// nope, so see if there's any other bound namespace with the same URI.
  583
+		final Namespace bound = boundstack.getFirstNamespaceForURI(namespaceURI);
  584
+		if (bound != null) {
  585
+			return bound;
  586
+		}
  587
+		// there are no existing bindings with the specified URI.
  588
+		if (repairnamespace) {
  589
+			return Namespace.getNamespace(generatePrefix(), namespaceURI);
  590
+		}
  591
+		throw new XMLStreamException("Namespace URI " + namespaceURI + 
  592
+				" is not bound in this scope (repairing not set for this XMLStreamWriter)."); 
746 593
 	}
  594
+
  595
+	private Namespace resolveAttributeNamespace(String prefix, String namespaceURI,
  596
+			boolean withpfx) throws XMLStreamException {
  597
+		final Namespace ns = Namespace.getNamespace(prefix, namespaceURI);
  598
+		if (ns == Namespace.NO_NAMESPACE) {
  599
+			return ns;
  600
+		}
  601
+		if (withpfx && !"".equals(prefix)) {
  602
+
  603
+			Namespace bnd = boundstack.getNamespaceForPrefix(prefix);
  604
+			
  605
+			if (bnd == null || bnd == ns) {
  606
+				// no existing binding for prefix, or same binding.
  607
+				return ns;
  608
+			}
  609
+			// prefix is bound to a different URI
  610
+			if (repairnamespace) {
  611
+				final Namespace gen = Namespace.getNamespace(generatePrefix(), namespaceURI);
  612
+				setPrefix(gen.getPrefix(), gen.getURI());
  613
+				return gen;
  614
+			}
  615
+			throw new XMLStreamException("Namespace prefix " + prefix + 
  616
+					" in this scope is bound to a different URI '" + bnd.getURI() + 
  617
+					"' (repairing not set for this XMLStreamWriter)."); 
  618
+		}
  619
+		
  620
+		// see if there's any other bound namespace with the same URI.
  621
+		final Namespace[] bound = boundstack.getAllNamespacesForURI(namespaceURI);
  622
+		for (Namespace b : bound) {
  623
+			if (!"".equals(b.getPrefix())) {
  624
+				// use an existing prefixed Namespace binding.
  625
+				// good for both repairing and non-repairing
  626
+				return b;
  627
+			}
  628
+		}
  629
+		// there are no existing prefixed bindings with the specified URI.
  630
+		if (repairnamespace || bound.length > 0) {
  631
+			return Namespace.getNamespace(generatePrefix(), namespaceURI);
  632
+		}
  633
+		throw new XMLStreamException("Namespace URI " + namespaceURI + 
  634
+				" is not bound in this attribute scope (repairing not set for this XMLStreamWriter)."); 
  635
+	}
  636
+
  637
+	private String generatePrefix() {
  638
+		String pfx = String.format("ns%03d", ++genprefix);
  639
+		while (boundstack.getNamespaceForPrefix(pfx) != null) {
  640
+			pfx = String.format("ns%03d", ++genprefix);
  641
+		}
  642
+		return pfx;
  643
+	}
  644
+
  645
+	private final void buildAttribute(final String prefix,
  646
+			final String namespaceURI, final String localName,
  647
+			final String value, final boolean withpfx) throws XMLStreamException {
  648
+		if (!(parent instanceof Element)) {
  649
+			throw new IllegalStateException(
  650
+					"Cannot write attribute unless inside an Element.");
  651
+		}
  652
+		if (localName == null) {
  653
+			throw new XMLStreamException("localName is not allowed to be null");
  654
+		}
  655
+		if (value == null) {
  656
+			throw new XMLStreamException("value is not allowed to be null");
  657
+		}
  658
+		if (activeelement == null) { 
  659
+			throw new IllegalStateException("Cannot add Attributes to an Element after other content was added.");
  660
+		}
  661
+		
  662
+		Namespace ns = resolveAttributeNamespace(prefix, namespaceURI, withpfx);
  663
+		
  664
+		factory.setAttribute(activeelement, factory.attribute(localName, value, ns));
  665
+	}
  666
+
  667
+	private final void flushActiveElement() {
  668
+		if (activeelement != null) {
  669
+			
  670
+			/*
  671
+			 * Add any written namespaces to the element as declared namespaces
  672
+			 * unless they are implied namespaces used by attributes, or something.
  673
+			 * If this Element is expecting content (it's not empty) then we also
  674
+			 * push the modified Element on to the Namespace stack.
  675
+			 */
  676
+
  677
+			boolean mod = false;
  678
+			usednsstack.push(activeelement);
  679
+			for (Namespace ns : pendingns) {
  680
+				if (!usednsstack.isInScope(ns)) {
  681
+					activeelement.addNamespaceDeclaration(ns);
  682
+					mod = true;
  683
+				}
  684
+			}
  685
+			pendingns.clear();
  686
+			
  687
+			if (mod) {
  688
+				usednsstack.pop();
  689
+				if (isempty) {
  690
+					boundstack.pop();
  691
+					activeelement = null;
  692
+					return;
  693
+				}
  694
+				// reload the stack with the additional namespaces.
  695
+				usednsstack.push(activeelement);
  696
+			}
  697
+			if (isempty) {
  698
+				boundstack.pop();
  699
+				activeelement = null;
  700
+				return;
  701
+			}
  702
+			boundstack.push(activeelement);
  703
+			boundstack.push();
  704
+			
  705
+			activeelement = null;
  706
+		}
  707
+	}
  708
+
  709
+	private final void flushActiveText() {
  710
+		activetext = null;
  711
+	}
  712
+
747 713
 }
17  core/src/java/org/jdom2/util/NamespaceStack.java
@@ -56,6 +56,7 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
56 56
 
57 57
 import java.util.ArrayList;
58 58
 import java.util.Arrays;
  59
+import java.util.Collections;
59 60
 import java.util.Comparator;
60 61
 import java.util.Iterator;
61 62
 import java.util.List;
@@ -227,6 +228,9 @@ public void remove() {
227 228
 
228 229
 	/** A simple empty Namespace Array to avoid redundant empty instances */
229 230
 	private static final Namespace[] EMPTY = new Namespace[0];
  231
+	
  232
+	private static final List<Namespace> EMPTYLIST = Collections.emptyList();
  233
+	
230 234
 	/** A simple Iterable instance that is always empty. Saves some memory */
231 235
 	private static final Iterable<Namespace> EMPTYITER = new EmptyIterable();
232 236
 
@@ -494,10 +498,19 @@ public void push(Iterable<Namespace> namespaces) {
494 498
 	
495 499
 	/**
496 500
 	 * Create a new in-scope level for the Stack based on an arbitrary set of Namespaces.
  501
+	 * The first Namespace in the list will be considered the 'primary' namespace for this scope
  502
+	 * and will be sorted to the front. If no namespaces are supplied then the 'current' scope will
  503
+	 * be duplicated (including sort order) as the new scope. 
497 504
 	 * @param namespaces The array of Namespaces.
498 505
 	 */
499  
-	public void push(Namespace[] namespaces) {
  506
+	public void push(Namespace ... namespaces) {
500 507
 
  508
+		if (namespaces == null || namespaces.length == 0) {
  509
+			// duplicate the current level to the new one.
  510
+			pushStack(scope[depth][0], scope[depth], EMPTYLIST);
  511
+			return;
  512
+		}
  513
+		
501 514
 		// how many times do you add more than 8 namespaces in one go...
502 515
 		// we can add more if we need to...
503 516
 		final List<Namespace> toadd = new ArrayList<Namespace>(8);
@@ -507,7 +520,7 @@ public void push(Namespace[] namespaces) {
507 520
 			newscope = checkNamespace(toadd, ns, newscope);
508 521
 		}
509 522
 		
510  
-		pushStack(Namespace.XML_NAMESPACE, newscope, toadd);
  523
+		pushStack(namespaces[0], newscope, toadd);
511 524
 	}
512 525
 	
513 526
 	private final void pushStack(final Namespace mns, Namespace[] newscope, 
9  test/src/java/org/jdom2/test/cases/jaxb/TestJDOMStreamWriter.java
@@ -58,6 +58,9 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
58 58
 
59 59
 import java.util.List;
60 60
 
  61
+//import javax.xml.stream.XMLOutputFactory;
  62
+//import javax.xml.stream.XMLStreamWriter;
  63
+
61 64
 import org.junit.Test;
62 65
 
63 66
 import org.jdom2.Content;
@@ -152,10 +155,13 @@ public void testElementsWithNamespace() throws Exception {
152 155
         System.out.println("testElementsWithNamespace");
153 156
         
154 157
         Document doc;
  158
+        //XMLStreamWriter writer = XMLOutputFactory.newInstance().createXMLStreamWriter(System.out);
155 159
         JDOMStreamWriter writer = new JDOMStreamWriter();
156 160
         try{
157 161
             writer.writeStartDocument();
  162
+            writer.setPrefix("", "testUri");
158 163
             writer.writeStartElement("testUri", "root");
  164
+                writer.writeDefaultNamespace("testUri");
159 165
                 writer.writeNamespace("tst", "testUri2");
160 166
             
161 167
                 writer.writeStartElement("tst", "element", "testUri2");
@@ -167,7 +173,9 @@ public void testElementsWithNamespace() throws Exception {
167 173
                     writer.writeCharacters("same ns");
168 174
                 writer.writeEndElement();
169 175
                 
  176
+                writer.setPrefix("", "");
170 177
                 writer.writeStartElement("element");
  178
+                    writer.writeDefaultNamespace("");
171 179
                     writer.writeCharacters("no ns");
172 180
                 writer.writeEndElement();
173 181
             
@@ -175,6 +183,7 @@ public void testElementsWithNamespace() throws Exception {
175 183
             writer.writeEndDocument();
176 184
             
177 185
             doc = writer.getDocument();
  186
+            //doc = null;
178 187
         }
179 188
         finally{
180 189
             writer.close();
4  test/src/resources/DOMBuilder/complex.xml
@@ -2,7 +2,7 @@
2 2
 <!-- root comment -->
3 3
 
4 4
 <?jdomtest root level ?>
5  
-   
  5
+
6 6
 <root att1="val1" att2="val2" >
7 7
   text
8 8
 	<child att="child1" xml:space="preserve"> hello Frodo Baggins! </child>
@@ -17,5 +17,7 @@
17 17
 	</child>
18 18
 </root>
19 19
 
  20
+<!-- trailing comment -->
  21
+<?jdomtest post root?>
20 22
 
21 23
       

0 notes on commit ba464a7

Please sign in to comment.
Something went wrong with that request. Please try again.