1
2
3
4
5
6
7
8 package de.nierbeck.timeTrack.model.impl.runtime;
9
10 import java.util.HashSet;
11 import java.util.Iterator;
12 import java.util.Set;
13
14 import javax.xml.bind.JAXBException;
15 import javax.xml.bind.ValidationEvent;
16 import javax.xml.bind.ValidationEventHandler;
17 import javax.xml.bind.helpers.NotIdentifiableEventImpl;
18 import javax.xml.bind.helpers.ValidationEventLocatorImpl;
19
20 import org.xml.sax.ContentHandler;
21 import org.xml.sax.SAXException;
22 import org.xml.sax.helpers.AttributesImpl;
23
24 import com.sun.xml.bind.JAXBAssertionError;
25 import com.sun.xml.bind.JAXBObject;
26 import com.sun.xml.bind.marshaller.IdentifiableObject;
27 import com.sun.xml.bind.marshaller.Messages;
28 import com.sun.xml.bind.marshaller.NamespacePrefixMapper;
29 import com.sun.xml.bind.serializer.AbortSerializationException;
30 import com.sun.xml.bind.serializer.Util;
31
32 /***
33 * XMLSerializer that produces SAX2 events.
34 *
35 * To marshal an object, create an instance of SAXMarshaller and call the
36 * serializeElements method of the XMLSerializable object that you want to
37 * marshal.
38 *
39 * @author Kohsuke Kawaguchi
40 */
41 public class SAXMarshaller implements XMLSerializer {
42 /***
43 * "Attributes" object that is passed to the startElement event. One object
44 * is reused throughout the marshalling.
45 */
46 private final AttributesImpl attributes = new AttributesImpl();
47
48 /*** This object receives SAX2 events generated from the marshaller. */
49 private final ContentHandler writer;
50
51 /*** Marshaller object to which this object belongs. */
52 private final MarshallerImpl owner;
53
54 /*** Objects referenced through IDREF. */
55 private final Set idReferencedObjects = new HashSet();
56
57 /*** Objects with ID. */
58 private final Set objectsWithId = new HashSet();
59
60 /*** Object currently marshalling itself. */
61 private JAXBObject currentTarget;
62
63 /***
64 * Creates a marshalling context by designating the ContentHandler that
65 * receives generated SAX2 events.
66 */
67 public SAXMarshaller(ContentHandler _writer,
68 NamespacePrefixMapper prefixMapper, MarshallerImpl _owner) {
69 this.writer = _writer;
70 this.owner = _owner;
71 this.nsContext = new NamespaceContextImpl(
72 prefixMapper != null ? prefixMapper
73 : defaultNamespacePrefixMapper);
74 }
75
76 /*** namespace context. */
77 private final NamespaceContextImpl nsContext;
78
79 public NamespaceContext2 getNamespaceContext() {
80 return nsContext;
81 }
82
83
84
85
86
87
88
89 /*** Element name stack implemented as an array of (uri,local) pairs. */
90 private String[] elementStack = new String[16];;
91
92 private int elementLen = 0;
93
94 private void pushElement(String uri, String local) {
95 if (elementStack.length == elementLen) {
96
97 String[] buf = new String[elementStack.length * 2];
98 System.arraycopy(elementStack, 0, buf, 0, elementStack.length);
99 elementStack = buf;
100 }
101 elementStack[elementLen++] = uri;
102 elementStack[elementLen++] = local;
103 }
104
105 private void popElement() {
106 elementLen -= 2;
107 }
108
109 private String getCurrentElementUri() {
110 return elementStack[elementLen - 2];
111 }
112
113 private String getCurrentElementLocal() {
114 return elementStack[elementLen - 1];
115 }
116
117 /***
118 * Starts marshalling of an element. Calling this method will push the
119 * internal state into the internal stack.
120 */
121 public void startElement(String uri, String local) throws SAXException {
122 boolean isRoot = false;
123 String suggestion = null;
124 if (elementLen == 0) {
125 isRoot = true;
126
127 suggestion = "";
128 }
129
130 writePendingText();
131 nsContext.startElement();
132 pushElement(uri, local);
133
134
135 nsContext.declareNamespace(uri, suggestion, false);
136
137
138 if (isRoot) {
139
140 String[] uris = nsContext.getNamespacePrefixMapper()
141 .getPreDeclaredNamespaceUris();
142 if (uris != null) {
143 for (int i = 0; i < uris.length; i++) {
144 if (uris[i] != null)
145 nsContext.declareNamespace(uris[i], null, false);
146 }
147 }
148 }
149 }
150
151 private final PrefixCallback startPrefixCallback = new PrefixCallback() {
152 public void onPrefixMapping(String prefix, String nsUri)
153 throws SAXException {
154 writer.startPrefixMapping(prefix, nsUri);
155 }
156 };
157
158 private final PrefixCallback endPrefixCallback = new PrefixCallback() {
159 public void onPrefixMapping(String prefix, String nsUri)
160 throws SAXException {
161 writer.endPrefixMapping(prefix);
162 }
163 };
164
165 public void endNamespaceDecls() throws SAXException {
166 nsContext.endNamespaceDecls();
167 }
168
169 /***
170 * Switches to the "marshal child texts/elements" mode. This method has to
171 * be called after the 1st pass is completed.
172 */
173 public void endAttributes() throws SAXException {
174
175 String uri = getCurrentElementUri();
176 String local = getCurrentElementLocal();
177
178 String prefix = nsContext.getPrefix(uri);
179 _assert(prefix != null);
180
181
182 String qname;
183 if (prefix.length() != 0)
184 qname = prefix + ':' + local;
185 else
186 qname = local;
187
188
189 nsContext.iterateDeclaredPrefixes(startPrefixCallback);
190
191
192 writer.startElement(uri, local, qname, attributes);
193
194
195 attributes.clear();
196
197
198 textBuf.setLength(0);
199 }
200
201 /***
202 * Ends marshalling of an element. Pops the internal stack.
203 */
204 public void endElement() throws SAXException {
205 writePendingText();
206
207 String uri = getCurrentElementUri();
208 String local = getCurrentElementLocal();
209
210 String prefix = nsContext.getPrefix(uri);
211 _assert(prefix != null);
212
213 String qname;
214 if (prefix.length() != 0)
215 qname = prefix + ':' + local;
216 else
217 qname = local;
218
219 writer.endElement(uri, local, qname);
220
221
222
223 nsContext.iterateDeclaredPrefixes(endPrefixCallback);
224
225 popElement();
226
227
228 textBuf.setLength(0);
229
230 nsContext.endElement();
231 }
232
233 /*** Buffer for collecting characters. */
234 private final StringBuffer textBuf = new StringBuffer();
235
236 /***
237 * Marshalls text.
238 *
239 * <p>
240 * This method can be called (i) after the startAttribute method and (ii)
241 * before the endAttribute method, to marshal attribute values. If the
242 * method is called more than once, those texts are considered as separated
243 * by whitespaces. For example,
244 *
245 * <pre>
246 * c.startAttribute();
247 * c.text("abc");
248 * c.text("def");
249 * c.endAttribute("", "foo");
250 * </pre>
251 *
252 * will generate foo="abc def".
253 *
254 * <p>
255 * Similarly, this method can be called after the endAttributes method to
256 * marshal texts inside elements. The same rule about multiple invokations
257 * apply to this case, too. For example,
258 *
259 * <pre>
260 * c.startElement("", "foo");
261 * c.endAttributes();
262 * c.text("abc");
263 * c.text("def");
264 * c.startElement("", "bar");
265 * c.endAttributes();
266 * c.endElement();
267 * c.text("ghi");
268 * c.endElement();
269 * </pre>
270 *
271 * will generate <code><foo>abc def<bar/>ghi</foo></code>.
272 */
273 public void text(String text, String fieldName) throws SAXException {
274
275
276 if (text == null) {
277 reportError(Util.createMissingObjectError(currentTarget, fieldName));
278 return;
279 }
280
281 if (textBuf.length() != 0)
282 textBuf.append(' ');
283 textBuf.append(text);
284 }
285
286 /***
287 * Writes pending text (characters inside elements) to the writer. This
288 * method is called from startElement and endElement.
289 */
290 private void writePendingText() throws SAXException {
291
292 int len = textBuf.length();
293
294 if (len != 0)
295 writer.characters(textBuf.toString().toCharArray(), 0, len);
296 }
297
298 /***
299 * Starts marshalling of an attribute.
300 *
301 * The marshalling of an attribute will be done by
302 * <ol>
303 * <li>call the startAttribute method
304 * <li>call the text method (several times if necessary)
305 * <li>call the endAttribute method
306 * </ol>
307 *
308 * No two attributes can be marshalled at the same time. Note that the whole
309 * attribute marshalling must be happened after the startElement method and
310 * before the endAttributes method.
311 */
312 public void startAttribute(String uri, String local) {
313
314 textBuf.setLength(0);
315
316
317 this.attNamespaceUri = uri;
318 this.attLocalName = local;
319 }
320
321
322 private String attNamespaceUri;
323
324 private String attLocalName;
325
326 public void endAttribute() {
327
328
329
330
331
332
333
334
335
336
337
338 String qname;
339 if (attNamespaceUri.length() == 0) {
340
341 qname = attLocalName;
342 } else {
343 qname = nsContext.declareNamespace(attNamespaceUri, null, true)
344 + ':' + attLocalName;
345 }
346
347 attributes.addAttribute(attNamespaceUri, attLocalName, qname, "CDATA",
348 textBuf.toString());
349 }
350
351 public String onID(IdentifiableObject owner, String value)
352 throws SAXException {
353 objectsWithId.add(owner);
354 return value;
355 }
356
357 public String onIDREF(IdentifiableObject obj) throws SAXException {
358 idReferencedObjects.add(obj);
359 String id = obj.____jaxb____getId();
360 if (id == null) {
361 reportError(new NotIdentifiableEventImpl(ValidationEvent.ERROR,
362 Messages.format(Messages.ERR_NOT_IDENTIFIABLE),
363 new ValidationEventLocatorImpl(obj)));
364 }
365 return id;
366 }
367
368 void reconcileID() throws AbortSerializationException {
369
370 idReferencedObjects.removeAll(objectsWithId);
371
372 for (Iterator itr = idReferencedObjects.iterator(); itr.hasNext();) {
373 IdentifiableObject o = (IdentifiableObject) itr.next();
374 reportError(new NotIdentifiableEventImpl(ValidationEvent.ERROR,
375 Messages.format(Messages.ERR_DANGLING_IDREF, o
376 .____jaxb____getId()),
377 new ValidationEventLocatorImpl(o)));
378 }
379
380
381 idReferencedObjects.clear();
382 objectsWithId.clear();
383 }
384
385 public void childAsBody(JAXBObject o, String fieldName) throws SAXException {
386 if (o == null) {
387
388
389 reportMissingObjectError(fieldName);
390
391
392
393 return;
394 }
395
396 JAXBObject oldTarget = currentTarget;
397 currentTarget = o;
398
399 owner.context.getGrammarInfo().castToXMLSerializable(o).serializeBody(
400 this);
401
402 currentTarget = oldTarget;
403 }
404
405 public void childAsAttributes(JAXBObject o, String fieldName)
406 throws SAXException {
407 if (o == null) {
408 reportMissingObjectError(fieldName);
409 return;
410 }
411
412 JAXBObject oldTarget = currentTarget;
413 currentTarget = o;
414
415 owner.context.getGrammarInfo().castToXMLSerializable(o)
416 .serializeAttributes(this);
417
418 currentTarget = oldTarget;
419 }
420
421 public void childAsURIs(JAXBObject o, String fieldName) throws SAXException {
422 if (o == null) {
423 reportMissingObjectError(fieldName);
424 return;
425 }
426
427 JAXBObject oldTarget = currentTarget;
428 currentTarget = o;
429
430 owner.context.getGrammarInfo().castToXMLSerializable(o).serializeURIs(
431 this);
432
433 currentTarget = oldTarget;
434 }
435
436 public void reportError(ValidationEvent ve)
437 throws AbortSerializationException {
438 ValidationEventHandler handler;
439
440 try {
441 handler = owner.getEventHandler();
442 } catch (JAXBException e) {
443 throw new AbortSerializationException(e);
444 }
445
446 if (!handler.handleEvent(ve)) {
447 if (ve.getLinkedException() instanceof Exception)
448 throw new AbortSerializationException((Exception) ve
449 .getLinkedException());
450 else
451 throw new AbortSerializationException(ve.getMessage());
452 }
453 }
454
455 public void reportMissingObjectError(String fieldName) throws SAXException {
456 reportError(Util.createMissingObjectError(currentTarget, fieldName));
457 }
458
459 private static void _assert(boolean b) {
460 if (!b)
461 throw new JAXBAssertionError();
462 }
463
464 /***
465 * Default {@link NamespacePrefixMapper} implementation used when it is not
466 * specified by the user.
467 */
468 private static NamespacePrefixMapper defaultNamespacePrefixMapper = new NamespacePrefixMapper() {
469 public String getPreferredPrefix(String namespaceUri,
470 String suggestion, boolean requirePrefix) {
471 if (namespaceUri
472 .equals("http://www.w3.org/2001/XMLSchema-instance"))
473 return "xsi";
474 return suggestion;
475 }
476 };
477 }