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