author | mkos |
Sun, 30 Dec 2012 00:00:00 +0100 | |
changeset 22678 | ac1ea46be942 |
parent 12009 | 4abb694f273a |
permissions | -rw-r--r-- |
12009 | 1 |
/* |
22678
ac1ea46be942
8029237: Update copyright year to match last edit in jaxws repository for 2012
mkos
parents:
12009
diff
changeset
|
2 |
* Copyright (c) 1997, 2012, Oracle and/or its affiliates. All rights reserved. |
12009 | 3 |
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
4 |
* |
|
5 |
* This code is free software; you can redistribute it and/or modify it |
|
6 |
* under the terms of the GNU General Public License version 2 only, as |
|
7 |
* published by the Free Software Foundation. Oracle designates this |
|
8 |
* particular file as subject to the "Classpath" exception as provided |
|
9 |
* by Oracle in the LICENSE file that accompanied this code. |
|
10 |
* |
|
11 |
* This code is distributed in the hope that it will be useful, but WITHOUT |
|
12 |
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
|
13 |
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
|
14 |
* version 2 for more details (a copy is included in the LICENSE file that |
|
15 |
* accompanied this code). |
|
16 |
* |
|
17 |
* You should have received a copy of the GNU General Public License version |
|
18 |
* 2 along with this work; if not, write to the Free Software Foundation, |
|
19 |
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
|
20 |
* |
|
21 |
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
|
22 |
* or visit www.oracle.com if you need additional information or have any |
|
23 |
* questions. |
|
24 |
*/ |
|
25 |
||
26 |
package com.sun.xml.internal.xsom.impl.parser.state; |
|
27 |
||
28 |
import java.text.MessageFormat; |
|
29 |
import java.util.ArrayList; |
|
30 |
import java.util.Stack; |
|
31 |
import java.util.StringTokenizer; |
|
32 |
||
33 |
import org.xml.sax.Attributes; |
|
34 |
import org.xml.sax.ContentHandler; |
|
35 |
import org.xml.sax.Locator; |
|
36 |
import org.xml.sax.SAXException; |
|
37 |
import org.xml.sax.SAXParseException; |
|
38 |
||
39 |
/** |
|
40 |
* Runtime Engine for RELAXNGCC execution. |
|
41 |
* |
|
42 |
* This class has the following functionalities: |
|
43 |
* |
|
44 |
* <ol> |
|
45 |
* <li>Managing a stack of NGCCHandler objects and |
|
46 |
* switching between them appropriately. |
|
47 |
* |
|
48 |
* <li>Keep track of all Attributes. |
|
49 |
* |
|
50 |
* <li>manage mapping between namespace URIs and prefixes. |
|
51 |
* |
|
52 |
* <li>TODO: provide support for interleaving. |
|
53 |
* |
|
54 |
* @version $Id: NGCCRuntime.java,v 1.15 2002/09/29 02:55:48 okajima Exp $ |
|
55 |
* @author Kohsuke Kawaguchi (kk@kohsuke.org) |
|
56 |
*/ |
|
57 |
public class NGCCRuntime implements ContentHandler, NGCCEventSource { |
|
58 |
||
59 |
public NGCCRuntime() { |
|
60 |
reset(); |
|
61 |
} |
|
62 |
||
63 |
/** |
|
64 |
* Sets the root handler, which will be used to parse the |
|
65 |
* root element. |
|
66 |
* <p> |
|
67 |
* This method can be called right after the object is created |
|
68 |
* or the reset method is called. You can't replace the root |
|
69 |
* handler while parsing is in progress. |
|
70 |
* <p> |
|
71 |
* Usually a generated class that corresponds to the <start> |
|
72 |
* pattern will be used as the root handler, but any NGCCHandler |
|
73 |
* can be a root handler. |
|
74 |
* |
|
75 |
* @exception IllegalStateException |
|
76 |
* If this method is called but it doesn't satisfy the |
|
77 |
* pre-condition stated above. |
|
78 |
*/ |
|
79 |
public void setRootHandler( NGCCHandler rootHandler ) { |
|
80 |
if(currentHandler!=null) |
|
81 |
throw new IllegalStateException(); |
|
82 |
currentHandler = rootHandler; |
|
83 |
} |
|
84 |
||
85 |
||
86 |
/** |
|
87 |
* Cleans up all the data structure so that the object can be reused later. |
|
88 |
* Normally, applications do not need to call this method directly, |
|
89 |
* |
|
90 |
* as the runtime resets itself after the endDocument method. |
|
91 |
*/ |
|
92 |
public void reset() { |
|
93 |
attStack.clear(); |
|
94 |
currentAtts = null; |
|
95 |
currentHandler = null; |
|
96 |
indent=0; |
|
97 |
locator = null; |
|
98 |
namespaces.clear(); |
|
99 |
needIndent = true; |
|
100 |
redirect = null; |
|
101 |
redirectionDepth = 0; |
|
102 |
text = new StringBuffer(); |
|
103 |
||
104 |
// add a dummy attributes at the bottom as a "centinel." |
|
105 |
attStack.push(new AttributesImpl()); |
|
106 |
} |
|
107 |
||
108 |
// current content handler can be acccessed via set/getContentHandler. |
|
109 |
||
110 |
private Locator locator; |
|
111 |
public void setDocumentLocator( Locator _loc ) { this.locator=_loc; } |
|
112 |
/** |
|
113 |
* Gets the source location of the current event. |
|
114 |
* |
|
115 |
* <p> |
|
116 |
* One can call this method from RelaxNGCC handlers to access |
|
117 |
* the line number information. Note that to |
|
118 |
*/ |
|
119 |
public Locator getLocator() { return locator; } |
|
120 |
||
121 |
||
122 |
/** stack of {@link Attributes}. */ |
|
123 |
private final Stack attStack = new Stack(); |
|
124 |
/** current attributes set. always equal to attStack.peek() */ |
|
125 |
private AttributesImpl currentAtts; |
|
126 |
||
127 |
/** |
|
128 |
* Attributes that belong to the current element. |
|
129 |
* <p> |
|
130 |
* It's generally not recommended for applications to use |
|
131 |
* this method. RelaxNGCC internally removes processed attributes, |
|
132 |
* so this doesn't correctly reflect all the attributes an element |
|
133 |
* carries. |
|
134 |
*/ |
|
135 |
public Attributes getCurrentAttributes() { |
|
136 |
return currentAtts; |
|
137 |
} |
|
138 |
||
139 |
/** accumulated text. */ |
|
140 |
private StringBuffer text = new StringBuffer(); |
|
141 |
||
142 |
||
143 |
||
144 |
||
145 |
/** The current NGCCHandler. Always equals to handlerStack.peek() */ |
|
146 |
private NGCCEventReceiver currentHandler; |
|
147 |
||
148 |
public int replace( NGCCEventReceiver o, NGCCEventReceiver n ) { |
|
149 |
if(o!=currentHandler) |
|
150 |
throw new IllegalStateException(); // bug of RelaxNGCC |
|
151 |
currentHandler = n; |
|
152 |
||
153 |
return 0; // we only have one thread. |
|
154 |
} |
|
155 |
||
156 |
/** |
|
157 |
* Processes buffered text. |
|
158 |
* |
|
159 |
* This method will be called by the start/endElement event to process |
|
160 |
* buffered text as a text event. |
|
161 |
* |
|
162 |
* <p> |
|
163 |
* Whitespace handling is a tricky business. Consider the following |
|
164 |
* schema fragment: |
|
165 |
* |
|
166 |
* <xmp> |
|
167 |
* <element name="foo"> |
|
168 |
* <choice> |
|
169 |
* <element name="bar"><empty/></element> |
|
170 |
* <text/> |
|
171 |
* </choice> |
|
172 |
* </element> |
|
173 |
* </xmp> |
|
174 |
* |
|
175 |
* Assume we hit the following instance: |
|
176 |
* <xmp> |
|
177 |
* <foo> <bar/></foo> |
|
178 |
* </xmp> |
|
179 |
* |
|
180 |
* Then this first space needs to be ignored (for otherwise, we will |
|
181 |
* end up treating this space as the match to <text/> and won't |
|
182 |
* be able to process <bar>.) |
|
183 |
* |
|
184 |
* Now assume the following instance: |
|
185 |
* <xmp> |
|
186 |
* <foo/> |
|
187 |
* </xmp> |
|
188 |
* |
|
189 |
* This time, we need to treat this empty string as a text, for |
|
190 |
* otherwise we won't be able to accept this instance. |
|
191 |
* |
|
192 |
* <p> |
|
193 |
* This is very difficult to solve in general, but one seemingly |
|
194 |
* easy solution is to use the type of next event. If a text is |
|
195 |
* followed by a start tag, it follows from the constraint on |
|
196 |
* RELAX NG that that text must be either whitespaces or a match |
|
197 |
* to <text/>. |
|
198 |
* |
|
199 |
* <p> |
|
200 |
* On the contrary, if a text is followed by a end tag, then it |
|
201 |
* cannot be whitespace unless the content model can accept empty, |
|
202 |
* in which case sending a text event will be harmlessly ignored |
|
203 |
* by the NGCCHandler. |
|
204 |
* |
|
205 |
* <p> |
|
206 |
* Thus this method take one parameter, which controls the |
|
207 |
* behavior of this method. |
|
208 |
* |
|
209 |
* <p> |
|
210 |
* TODO: according to the constraint of RELAX NG, if characters |
|
211 |
* follow an end tag, then they must be either whitespaces or |
|
212 |
* must match to <text/>. |
|
213 |
* |
|
214 |
* @param possiblyWhitespace |
|
215 |
* True if the buffered character can be ignorabale. False if |
|
216 |
* it needs to be consumed. |
|
217 |
*/ |
|
218 |
private void processPendingText(boolean ignorable) throws SAXException { |
|
219 |
if(ignorable && text.toString().trim().length()==0) |
|
220 |
; // ignore. See the above javadoc comment for the description |
|
221 |
else |
|
222 |
currentHandler.text(text.toString()); // otherwise consume this token |
|
223 |
||
224 |
// truncate StringBuffer, but avoid excessive allocation. |
|
225 |
if(text.length()>1024) text = new StringBuffer(); |
|
226 |
else text.setLength(0); |
|
227 |
} |
|
228 |
||
229 |
public void processList( String str ) throws SAXException { |
|
230 |
StringTokenizer t = new StringTokenizer(str, " \t\r\n"); |
|
231 |
while(t.hasMoreTokens()) |
|
232 |
currentHandler.text(t.nextToken()); |
|
233 |
} |
|
234 |
||
235 |
public void startElement(String uri, String localname, String qname, Attributes atts) |
|
236 |
throws SAXException { |
|
237 |
||
238 |
if(redirect!=null) { |
|
239 |
redirect.startElement(uri,localname,qname,atts); |
|
240 |
redirectionDepth++; |
|
241 |
} else { |
|
242 |
processPendingText(true); |
|
243 |
// System.out.println("startElement:"+localname+"->"+_attrStack.size()); |
|
244 |
currentHandler.enterElement(uri, localname, qname, atts); |
|
245 |
} |
|
246 |
} |
|
247 |
||
248 |
/** |
|
249 |
* Called by the generated handler code when an enter element |
|
250 |
* event is consumed. |
|
251 |
* |
|
252 |
* <p> |
|
253 |
* Pushes a new attribute set. |
|
254 |
* |
|
255 |
* <p> |
|
256 |
* Note that attributes are NOT pushed at the startElement method, |
|
257 |
* because the processing of the enterElement event can trigger |
|
258 |
* other attribute events and etc. |
|
259 |
* <p> |
|
260 |
* This method will be called from one of handlers when it truely |
|
261 |
* consumes the enterElement event. |
|
262 |
*/ |
|
263 |
public void onEnterElementConsumed( |
|
264 |
String uri, String localName, String qname,Attributes atts) throws SAXException { |
|
265 |
attStack.push(currentAtts=new AttributesImpl(atts)); |
|
266 |
nsEffectiveStack.push( new Integer(nsEffectivePtr) ); |
|
267 |
nsEffectivePtr = namespaces.size(); |
|
268 |
} |
|
269 |
||
270 |
public void onLeaveElementConsumed(String uri, String localName, String qname) throws SAXException { |
|
271 |
attStack.pop(); |
|
272 |
if(attStack.isEmpty()) |
|
273 |
currentAtts = null; |
|
274 |
else |
|
275 |
currentAtts = (AttributesImpl)attStack.peek(); |
|
276 |
nsEffectivePtr = ((Integer)nsEffectiveStack.pop()).intValue(); |
|
277 |
} |
|
278 |
||
279 |
public void endElement(String uri, String localname, String qname) |
|
280 |
throws SAXException { |
|
281 |
||
282 |
if(redirect!=null) { |
|
283 |
redirect.endElement(uri,localname,qname); |
|
284 |
redirectionDepth--; |
|
285 |
||
286 |
if(redirectionDepth!=0) |
|
287 |
return; |
|
288 |
||
289 |
// finished redirection. |
|
290 |
for( int i=0; i<namespaces.size(); i+=2 ) |
|
291 |
redirect.endPrefixMapping((String)namespaces.get(i)); |
|
292 |
redirect.endDocument(); |
|
293 |
||
294 |
redirect = null; |
|
295 |
// then process this element normally |
|
296 |
} |
|
297 |
||
298 |
processPendingText(false); |
|
299 |
||
300 |
currentHandler.leaveElement(uri, localname, qname); |
|
301 |
// System.out.println("endElement:"+localname); |
|
302 |
} |
|
303 |
||
304 |
public void characters(char[] ch, int start, int length) throws SAXException { |
|
305 |
if(redirect!=null) |
|
306 |
redirect.characters(ch,start,length); |
|
307 |
else |
|
308 |
text.append(ch,start,length); |
|
309 |
} |
|
310 |
public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException { |
|
311 |
if(redirect!=null) |
|
312 |
redirect.ignorableWhitespace(ch,start,length); |
|
313 |
else |
|
314 |
text.append(ch,start,length); |
|
315 |
} |
|
316 |
||
317 |
public int getAttributeIndex(String uri, String localname) { |
|
318 |
return currentAtts.getIndex(uri, localname); |
|
319 |
} |
|
320 |
public void consumeAttribute(int index) throws SAXException { |
|
321 |
final String uri = currentAtts.getURI(index); |
|
322 |
final String local = currentAtts.getLocalName(index); |
|
323 |
final String qname = currentAtts.getQName(index); |
|
324 |
final String value = currentAtts.getValue(index); |
|
325 |
currentAtts.removeAttribute(index); |
|
326 |
||
327 |
currentHandler.enterAttribute(uri,local,qname); |
|
328 |
currentHandler.text(value); |
|
329 |
currentHandler.leaveAttribute(uri,local,qname); |
|
330 |
} |
|
331 |
||
332 |
||
333 |
public void startPrefixMapping( String prefix, String uri ) throws SAXException { |
|
334 |
if(redirect!=null) |
|
335 |
redirect.startPrefixMapping(prefix,uri); |
|
336 |
else { |
|
337 |
namespaces.add(prefix); |
|
338 |
namespaces.add(uri); |
|
339 |
} |
|
340 |
} |
|
341 |
||
342 |
public void endPrefixMapping( String prefix ) throws SAXException { |
|
343 |
if(redirect!=null) |
|
344 |
redirect.endPrefixMapping(prefix); |
|
345 |
else { |
|
346 |
namespaces.remove(namespaces.size()-1); |
|
347 |
namespaces.remove(namespaces.size()-1); |
|
348 |
} |
|
349 |
} |
|
350 |
||
351 |
public void skippedEntity( String name ) throws SAXException { |
|
352 |
if(redirect!=null) |
|
353 |
redirect.skippedEntity(name); |
|
354 |
} |
|
355 |
||
356 |
public void processingInstruction( String target, String data ) throws SAXException { |
|
357 |
if(redirect!=null) |
|
358 |
redirect.processingInstruction(target,data); |
|
359 |
} |
|
360 |
||
361 |
/** Impossible token. This value can never be a valid XML name. */ |
|
362 |
static final String IMPOSSIBLE = "\u0000"; |
|
363 |
||
364 |
public void endDocument() throws SAXException { |
|
365 |
// consume the special "end document" token so that all the handlers |
|
366 |
// currently at the stack will revert to their respective parents. |
|
367 |
// |
|
368 |
// this is necessary to handle a grammar like |
|
369 |
// <start><ref name="X"/></start> |
|
370 |
// <define name="X"> |
|
371 |
// <element name="root"><empty/></element> |
|
372 |
// </define> |
|
373 |
// |
|
374 |
// With this grammar, when the endElement event is consumed, two handlers |
|
375 |
// are on the stack (because a child object won't revert to its parent |
|
376 |
// unless it sees a next event.) |
|
377 |
||
378 |
// pass around an "impossible" token. |
|
379 |
currentHandler.leaveElement(IMPOSSIBLE,IMPOSSIBLE,IMPOSSIBLE); |
|
380 |
||
381 |
reset(); |
|
382 |
} |
|
383 |
public void startDocument() {} |
|
384 |
||
385 |
||
386 |
||
387 |
||
388 |
// |
|
389 |
// |
|
390 |
// event dispatching methods |
|
391 |
// |
|
392 |
// |
|
393 |
||
394 |
public void sendEnterAttribute( int threadId, |
|
395 |
String uri, String local, String qname) throws SAXException { |
|
396 |
||
397 |
currentHandler.enterAttribute(uri,local,qname); |
|
398 |
} |
|
399 |
||
400 |
public void sendEnterElement( int threadId, |
|
401 |
String uri, String local, String qname, Attributes atts) throws SAXException { |
|
402 |
||
403 |
currentHandler.enterElement(uri,local,qname,atts); |
|
404 |
} |
|
405 |
||
406 |
public void sendLeaveAttribute( int threadId, |
|
407 |
String uri, String local, String qname) throws SAXException { |
|
408 |
||
409 |
currentHandler.leaveAttribute(uri,local,qname); |
|
410 |
} |
|
411 |
||
412 |
public void sendLeaveElement( int threadId, |
|
413 |
String uri, String local, String qname) throws SAXException { |
|
414 |
||
415 |
currentHandler.leaveElement(uri,local,qname); |
|
416 |
} |
|
417 |
||
418 |
public void sendText(int threadId, String value) throws SAXException { |
|
419 |
currentHandler.text(value); |
|
420 |
} |
|
421 |
||
422 |
||
423 |
// |
|
424 |
// |
|
425 |
// redirection of SAX2 events. |
|
426 |
// |
|
427 |
// |
|
428 |
/** When redirecting a sub-tree, this value will be non-null. */ |
|
429 |
private ContentHandler redirect = null; |
|
430 |
||
431 |
/** |
|
432 |
* Counts the depth of the elements when we are re-directing |
|
433 |
* a sub-tree to another ContentHandler. |
|
434 |
*/ |
|
435 |
private int redirectionDepth = 0; |
|
436 |
||
437 |
/** |
|
438 |
* This method can be called only from the enterElement handler. |
|
439 |
* The sub-tree rooted at the new element will be redirected |
|
440 |
* to the specified ContentHandler. |
|
441 |
* |
|
442 |
* <p> |
|
443 |
* Currently active NGCCHandler will only receive the leaveElement |
|
444 |
* event of the newly started element. |
|
445 |
* |
|
446 |
* @param uri,local,qname |
|
447 |
* Parameters passed to the enter element event. Used to |
|
448 |
* simulate the startElement event for the new ContentHandler. |
|
449 |
*/ |
|
450 |
public void redirectSubtree( ContentHandler child, |
|
451 |
String uri, String local, String qname ) throws SAXException { |
|
452 |
||
453 |
redirect = child; |
|
454 |
redirect.setDocumentLocator(locator); |
|
455 |
redirect.startDocument(); |
|
456 |
||
457 |
// TODO: when a prefix is re-bound to something else, |
|
458 |
// the following code is potentially dangerous. It should be |
|
459 |
// modified to report active bindings only. |
|
460 |
for( int i=0; i<namespaces.size(); i+=2 ) |
|
461 |
redirect.startPrefixMapping( |
|
462 |
(String)namespaces.get(i), |
|
463 |
(String)namespaces.get(i+1) |
|
464 |
); |
|
465 |
||
466 |
redirect.startElement(uri,local,qname,currentAtts); |
|
467 |
redirectionDepth=1; |
|
468 |
} |
|
469 |
||
470 |
// |
|
471 |
// |
|
472 |
// validation context implementation |
|
473 |
// |
|
474 |
// |
|
475 |
/** in-scope namespace mapping. |
|
476 |
* namespaces[2n ] := prefix |
|
477 |
* namespaces[2n+1] := namespace URI */ |
|
478 |
private final ArrayList namespaces = new ArrayList(); |
|
479 |
/** |
|
480 |
* Index on the namespaces array, which points to |
|
481 |
* the top of the effective bindings. Because of the |
|
482 |
* timing difference between the startPrefixMapping method |
|
483 |
* and the execution of the corresponding actions, |
|
484 |
* this value can be different from <code>namespaces.size()</code>. |
|
485 |
* <p> |
|
486 |
* For example, consider the following schema: |
|
487 |
* <pre><xmp> |
|
488 |
* <oneOrMore> |
|
489 |
* <element name="foo"><empty/></element> |
|
490 |
* </oneOrMore> |
|
491 |
* code fragment X |
|
492 |
* <element name="bob"/> |
|
493 |
* </xmp></pre> |
|
494 |
* Code fragment X is executed after we see a startElement event, |
|
495 |
* but at this time the namespaces variable already include new |
|
496 |
* namespace bindings declared on "bob". |
|
497 |
*/ |
|
498 |
private int nsEffectivePtr=0; |
|
499 |
||
500 |
/** |
|
501 |
* Stack to preserve old nsEffectivePtr values. |
|
502 |
*/ |
|
503 |
private final Stack nsEffectiveStack = new Stack(); |
|
504 |
||
505 |
public String resolveNamespacePrefix( String prefix ) { |
|
506 |
for( int i = nsEffectivePtr-2; i>=0; i-=2 ) |
|
507 |
if( namespaces.get(i).equals(prefix) ) |
|
508 |
return (String)namespaces.get(i+1); |
|
509 |
||
510 |
// no binding was found. |
|
511 |
if(prefix.equals("")) return ""; // return the default no-namespace |
|
512 |
if(prefix.equals("xml")) // pre-defined xml prefix |
|
513 |
return "http://www.w3.org/XML/1998/namespace"; |
|
514 |
else return null; // prefix undefined |
|
515 |
} |
|
516 |
||
517 |
||
518 |
// error reporting |
|
519 |
protected void unexpectedX(String token) throws SAXException { |
|
520 |
throw new SAXParseException(MessageFormat.format( |
|
521 |
"Unexpected {0} appears at line {1} column {2}", |
|
522 |
new Object[]{ |
|
523 |
token, |
|
524 |
new Integer(getLocator().getLineNumber()), |
|
525 |
new Integer(getLocator().getColumnNumber()) }), |
|
526 |
getLocator()); |
|
527 |
} |
|
528 |
||
529 |
||
530 |
||
531 |
||
532 |
// |
|
533 |
// |
|
534 |
// trace functions |
|
535 |
// |
|
536 |
// |
|
537 |
private int indent=0; |
|
538 |
private boolean needIndent=true; |
|
539 |
private void printIndent() { |
|
540 |
for( int i=0; i<indent; i++ ) |
|
541 |
System.out.print(" "); |
|
542 |
} |
|
543 |
public void trace( String s ) { |
|
544 |
if(needIndent) { |
|
545 |
needIndent=false; |
|
546 |
printIndent(); |
|
547 |
} |
|
548 |
System.out.print(s); |
|
549 |
} |
|
550 |
public void traceln( String s ) { |
|
551 |
trace(s); |
|
552 |
trace("\n"); |
|
553 |
needIndent=true; |
|
554 |
} |
|
555 |
} |