1 /* 2 * Copyright (C) 2007 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package org.apache.harmony.xml; 18 19 import java.io.IOException; 20 import java.io.InputStream; 21 import java.io.Reader; 22 import java.net.URI; 23 import java.net.URL; 24 import java.net.URLConnection; 25 import libcore.io.IoUtils; 26 import org.xml.sax.Attributes; 27 import org.xml.sax.ContentHandler; 28 import org.xml.sax.DTDHandler; 29 import org.xml.sax.EntityResolver; 30 import org.xml.sax.InputSource; 31 import org.xml.sax.Locator; 32 import org.xml.sax.SAXException; 33 import org.xml.sax.SAXParseException; 34 import org.xml.sax.ext.LexicalHandler; 35 36 /** 37 * Adapts SAX API to the Expat native XML parser. Not intended for reuse 38 * across documents. 39 * 40 * @see org.apache.harmony.xml.ExpatReader 41 */ 42 class ExpatParser { 43 44 private static final int BUFFER_SIZE = 8096; // in bytes 45 46 /** Pointer to XML_Parser instance. */ 47 private long pointer; 48 49 private boolean inStartElement = false; 50 private int attributeCount = -1; 51 private long attributePointer = 0; 52 53 private final Locator locator = new ExpatLocator(); 54 55 private final ExpatReader xmlReader; 56 57 private final String publicId; 58 private final String systemId; 59 60 private final String encoding; 61 62 private final ExpatAttributes attributes = new CurrentAttributes(); 63 64 private static final String OUTSIDE_START_ELEMENT 65 = "Attributes can only be used within the scope of startElement()."; 66 67 /** We default to UTF-8 when the user doesn't specify an encoding. */ 68 private static final String DEFAULT_ENCODING = "UTF-8"; 69 70 /** Encoding used for Java chars, used to parse Readers and Strings */ 71 /*package*/ static final String CHARACTER_ENCODING = "UTF-16"; 72 73 /** Timeout for HTTP connections (in ms) */ 74 private static final int TIMEOUT = 20 * 1000; 75 76 /** 77 * Constructs a new parser with the specified encoding. 78 */ ExpatParser(String encoding, ExpatReader xmlReader, boolean processNamespaces, String publicId, String systemId)79 /*package*/ ExpatParser(String encoding, ExpatReader xmlReader, 80 boolean processNamespaces, String publicId, String systemId) { 81 this.publicId = publicId; 82 this.systemId = systemId; 83 84 this.xmlReader = xmlReader; 85 86 /* 87 * TODO: Let Expat try to guess the encoding instead of defaulting. 88 * Unfortunately, I don't know how to tell which encoding Expat picked, 89 * so I won't know how to encode "<externalEntity>" below. The solution 90 * I think is to fix Expat to not require the "<externalEntity>" 91 * workaround. 92 */ 93 this.encoding = encoding == null ? DEFAULT_ENCODING : encoding; 94 this.pointer = initialize( 95 this.encoding, 96 processNamespaces 97 ); 98 } 99 100 /** 101 * Used by {@link EntityParser}. 102 */ ExpatParser(String encoding, ExpatReader xmlReader, long pointer, String publicId, String systemId)103 private ExpatParser(String encoding, ExpatReader xmlReader, long pointer, 104 String publicId, String systemId) { 105 this.encoding = encoding; 106 this.xmlReader = xmlReader; 107 this.pointer = pointer; 108 this.systemId = systemId; 109 this.publicId = publicId; 110 } 111 112 /** 113 * Initializes native resources. 114 * 115 * @return the pointer to the native parser 116 */ initialize(String encoding, boolean namespacesEnabled)117 private native long initialize(String encoding, boolean namespacesEnabled); 118 119 /** 120 * Called at the start of an element. 121 * 122 * @param uri namespace URI of element or "" if namespace processing is 123 * disabled 124 * @param localName local name of element or "" if namespace processing is 125 * disabled 126 * @param qName qualified name or "" if namespace processing is enabled 127 * @param attributePointer pointer to native attribute char*--we keep 128 * a separate pointer so we can detach it from the parser instance 129 * @param attributeCount number of attributes 130 */ startElement(String uri, String localName, String qName, long attributePointer, int attributeCount)131 /*package*/ void startElement(String uri, String localName, String qName, 132 long attributePointer, int attributeCount) throws SAXException { 133 ContentHandler contentHandler = xmlReader.contentHandler; 134 if (contentHandler == null) { 135 return; 136 } 137 138 try { 139 inStartElement = true; 140 this.attributePointer = attributePointer; 141 this.attributeCount = attributeCount; 142 143 contentHandler.startElement( 144 uri, localName, qName, this.attributes); 145 } finally { 146 inStartElement = false; 147 this.attributeCount = -1; 148 this.attributePointer = 0; 149 } 150 } 151 endElement(String uri, String localName, String qName)152 /*package*/ void endElement(String uri, String localName, String qName) 153 throws SAXException { 154 ContentHandler contentHandler = xmlReader.contentHandler; 155 if (contentHandler != null) { 156 contentHandler.endElement(uri, localName, qName); 157 } 158 } 159 text(char[] text, int length)160 /*package*/ void text(char[] text, int length) throws SAXException { 161 ContentHandler contentHandler = xmlReader.contentHandler; 162 if (contentHandler != null) { 163 contentHandler.characters(text, 0, length); 164 } 165 } 166 comment(char[] text, int length)167 /*package*/ void comment(char[] text, int length) throws SAXException { 168 LexicalHandler lexicalHandler = xmlReader.lexicalHandler; 169 if (lexicalHandler != null) { 170 lexicalHandler.comment(text, 0, length); 171 } 172 } 173 startCdata()174 /*package*/ void startCdata() throws SAXException { 175 LexicalHandler lexicalHandler = xmlReader.lexicalHandler; 176 if (lexicalHandler != null) { 177 lexicalHandler.startCDATA(); 178 } 179 } 180 endCdata()181 /*package*/ void endCdata() throws SAXException { 182 LexicalHandler lexicalHandler = xmlReader.lexicalHandler; 183 if (lexicalHandler != null) { 184 lexicalHandler.endCDATA(); 185 } 186 } 187 startNamespace(String prefix, String uri)188 /*package*/ void startNamespace(String prefix, String uri) 189 throws SAXException { 190 ContentHandler contentHandler = xmlReader.contentHandler; 191 if (contentHandler != null) { 192 contentHandler.startPrefixMapping(prefix, uri); 193 } 194 } 195 endNamespace(String prefix)196 /*package*/ void endNamespace(String prefix) throws SAXException { 197 ContentHandler contentHandler = xmlReader.contentHandler; 198 if (contentHandler != null) { 199 contentHandler.endPrefixMapping(prefix); 200 } 201 } 202 startDtd(String name, String publicId, String systemId)203 /*package*/ void startDtd(String name, String publicId, String systemId) 204 throws SAXException { 205 LexicalHandler lexicalHandler = xmlReader.lexicalHandler; 206 if (lexicalHandler != null) { 207 lexicalHandler.startDTD(name, publicId, systemId); 208 } 209 } 210 endDtd()211 /*package*/ void endDtd() throws SAXException { 212 LexicalHandler lexicalHandler = xmlReader.lexicalHandler; 213 if (lexicalHandler != null) { 214 lexicalHandler.endDTD(); 215 } 216 } 217 processingInstruction(String target, String data)218 /*package*/ void processingInstruction(String target, String data) 219 throws SAXException { 220 ContentHandler contentHandler = xmlReader.contentHandler; 221 if (contentHandler != null) { 222 contentHandler.processingInstruction(target, data); 223 } 224 } 225 notationDecl(String name, String publicId, String systemId)226 /*package*/ void notationDecl(String name, String publicId, String systemId) throws SAXException { 227 DTDHandler dtdHandler = xmlReader.dtdHandler; 228 if (dtdHandler != null) { 229 dtdHandler.notationDecl(name, publicId, systemId); 230 } 231 } 232 unparsedEntityDecl(String name, String publicId, String systemId, String notationName)233 /*package*/ void unparsedEntityDecl(String name, String publicId, String systemId, String notationName) throws SAXException { 234 DTDHandler dtdHandler = xmlReader.dtdHandler; 235 if (dtdHandler != null) { 236 dtdHandler.unparsedEntityDecl(name, publicId, systemId, notationName); 237 } 238 } 239 240 /** 241 * Handles an external entity. 242 * 243 * @param context to be passed back to Expat when we parse the entity 244 * @param publicId the publicId of the entity 245 * @param systemId the systemId of the entity 246 */ handleExternalEntity(String context, String publicId, String systemId)247 /*package*/ void handleExternalEntity(String context, String publicId, 248 String systemId) throws SAXException, IOException { 249 EntityResolver entityResolver = xmlReader.entityResolver; 250 if (entityResolver == null) { 251 return; 252 } 253 254 /* 255 * The spec. is terribly under-specified here. It says that if the 256 * systemId is a URL, we should try to resolve it, but it doesn't 257 * specify how to tell whether or not the systemId is a URL let alone 258 * how to resolve it. 259 * 260 * Other implementations do various insane things. We try to keep it 261 * simple: if the systemId parses as a URI and it's relative, we try to 262 * resolve it against the parent document's systemId. If anything goes 263 * wrong, we go with the original systemId. If crazybob had designed 264 * the API, he would have left all resolving to the EntityResolver. 265 */ 266 if (this.systemId != null) { 267 try { 268 URI systemUri = new URI(systemId); 269 if (!systemUri.isAbsolute() && !systemUri.isOpaque()) { 270 // It could be relative (or it may not be a URI at all!) 271 URI baseUri = new URI(this.systemId); 272 systemUri = baseUri.resolve(systemUri); 273 274 // Replace systemId w/ resolved URI 275 systemId = systemUri.toString(); 276 } 277 } catch (Exception e) { 278 System.logI("Could not resolve '" + systemId + "' relative to" 279 + " '" + this.systemId + "' at " + locator, e); 280 } 281 } 282 283 InputSource inputSource = entityResolver.resolveEntity( 284 publicId, systemId); 285 if (inputSource == null) { 286 /* 287 * The spec. actually says that we should try to treat systemId 288 * as a URL and download and parse its contents here, but an 289 * entity resolver can easily accomplish the same by returning 290 * new InputSource(systemId). 291 * 292 * Downloading external entities by default would result in several 293 * unwanted DTD downloads, not to mention pose a security risk 294 * when parsing untrusted XML -- see for example 295 * http://archive.cert.uni-stuttgart.de/bugtraq/2002/10/msg00421.html -- 296 * so we just do nothing instead. This also enables the user to 297 * opt out of entity parsing when using 298 * {@link org.xml.sax.helpers.DefaultHandler}, something that 299 * wouldn't be possible otherwise. 300 */ 301 return; 302 } 303 304 String encoding = pickEncoding(inputSource); 305 long pointer = createEntityParser(this.pointer, context); 306 try { 307 EntityParser entityParser = new EntityParser(encoding, xmlReader, 308 pointer, inputSource.getPublicId(), 309 inputSource.getSystemId()); 310 311 parseExternalEntity(entityParser, inputSource); 312 } finally { 313 releaseParser(pointer); 314 } 315 } 316 317 /** 318 * Picks an encoding for an external entity. Defaults to UTF-8. 319 */ pickEncoding(InputSource inputSource)320 private String pickEncoding(InputSource inputSource) { 321 Reader reader = inputSource.getCharacterStream(); 322 if (reader != null) { 323 return CHARACTER_ENCODING; 324 } 325 326 String encoding = inputSource.getEncoding(); 327 return encoding == null ? DEFAULT_ENCODING : encoding; 328 } 329 330 /** 331 * Parses the the external entity provided by the input source. 332 */ parseExternalEntity(ExpatParser entityParser, InputSource inputSource)333 private void parseExternalEntity(ExpatParser entityParser, 334 InputSource inputSource) throws IOException, SAXException { 335 /* 336 * Expat complains if the external entity isn't wrapped with a root 337 * element so we add one and ignore it later on during parsing. 338 */ 339 340 // Try the character stream. 341 Reader reader = inputSource.getCharacterStream(); 342 if (reader != null) { 343 try { 344 entityParser.append("<externalEntity>"); 345 entityParser.parseFragment(reader); 346 entityParser.append("</externalEntity>"); 347 } finally { 348 IoUtils.closeQuietly(reader); 349 } 350 return; 351 } 352 353 // Try the byte stream. 354 InputStream in = inputSource.getByteStream(); 355 if (in != null) { 356 try { 357 entityParser.append("<externalEntity>" 358 .getBytes(entityParser.encoding)); 359 entityParser.parseFragment(in); 360 entityParser.append("</externalEntity>" 361 .getBytes(entityParser.encoding)); 362 } finally { 363 IoUtils.closeQuietly(in); 364 } 365 return; 366 } 367 368 // Make sure we use the user-provided systemId. 369 String systemId = inputSource.getSystemId(); 370 if (systemId == null) { 371 // TODO: We could just try our systemId here. 372 throw new ParseException("No input specified.", locator); 373 } 374 375 // Try the system id. 376 in = openUrl(systemId); 377 try { 378 entityParser.append("<externalEntity>" 379 .getBytes(entityParser.encoding)); 380 entityParser.parseFragment(in); 381 entityParser.append("</externalEntity>" 382 .getBytes(entityParser.encoding)); 383 } finally { 384 IoUtils.closeQuietly(in); 385 } 386 } 387 388 /** 389 * Creates a native entity parser. 390 * 391 * @param parentPointer pointer to parent Expat parser 392 * @param context passed to {@link #handleExternalEntity} 393 * @return pointer to native parser 394 */ createEntityParser(long parentPointer, String context)395 private static native long createEntityParser(long parentPointer, String context); 396 397 /** 398 * Appends part of an XML document. This parser will parse the given XML to 399 * the extent possible and dispatch to the appropriate methods. 400 * 401 * @param xml a whole or partial snippet of XML 402 * @throws SAXException if an error occurs during parsing 403 */ append(String xml)404 /*package*/ void append(String xml) throws SAXException { 405 try { 406 appendString(this.pointer, xml, false); 407 } catch (ExpatException e) { 408 throw new ParseException(e.getMessage(), this.locator); 409 } 410 } 411 appendString(long pointer, String xml, boolean isFinal)412 private native void appendString(long pointer, String xml, boolean isFinal) 413 throws SAXException, ExpatException; 414 415 /** 416 * Appends part of an XML document. This parser will parse the given XML to 417 * the extent possible and dispatch to the appropriate methods. 418 * 419 * @param xml a whole or partial snippet of XML 420 * @param offset into the char[] 421 * @param length of characters to use 422 * @throws SAXException if an error occurs during parsing 423 */ append(char[] xml, int offset, int length)424 /*package*/ void append(char[] xml, int offset, int length) 425 throws SAXException { 426 try { 427 appendChars(this.pointer, xml, offset, length); 428 } catch (ExpatException e) { 429 throw new ParseException(e.getMessage(), this.locator); 430 } 431 } 432 appendChars(long pointer, char[] xml, int offset, int length)433 private native void appendChars(long pointer, char[] xml, int offset, 434 int length) throws SAXException, ExpatException; 435 436 /** 437 * Appends part of an XML document. This parser will parse the given XML to 438 * the extent possible and dispatch to the appropriate methods. 439 * 440 * @param xml a whole or partial snippet of XML 441 * @throws SAXException if an error occurs during parsing 442 */ append(byte[] xml)443 /*package*/ void append(byte[] xml) throws SAXException { 444 append(xml, 0, xml.length); 445 } 446 447 /** 448 * Appends part of an XML document. This parser will parse the given XML to 449 * the extent possible and dispatch to the appropriate methods. 450 * 451 * @param xml a whole or partial snippet of XML 452 * @param offset into the byte[] 453 * @param length of bytes to use 454 * @throws SAXException if an error occurs during parsing 455 */ append(byte[] xml, int offset, int length)456 /*package*/ void append(byte[] xml, int offset, int length) 457 throws SAXException { 458 try { 459 appendBytes(this.pointer, xml, offset, length); 460 } catch (ExpatException e) { 461 throw new ParseException(e.getMessage(), this.locator); 462 } 463 } 464 appendBytes(long pointer, byte[] xml, int offset, int length)465 private native void appendBytes(long pointer, byte[] xml, int offset, 466 int length) throws SAXException, ExpatException; 467 468 /** 469 * Parses an XML document from the given input stream. 470 */ parseDocument(InputStream in)471 /*package*/ void parseDocument(InputStream in) throws IOException, 472 SAXException { 473 startDocument(); 474 parseFragment(in); 475 finish(); 476 endDocument(); 477 } 478 479 /** 480 * Parses an XML Document from the given reader. 481 */ parseDocument(Reader in)482 /*package*/ void parseDocument(Reader in) throws IOException, SAXException { 483 startDocument(); 484 parseFragment(in); 485 finish(); 486 endDocument(); 487 } 488 489 /** 490 * Parses XML from the given Reader. 491 */ parseFragment(Reader in)492 private void parseFragment(Reader in) throws IOException, SAXException { 493 char[] buffer = new char[BUFFER_SIZE / 2]; 494 int length; 495 while ((length = in.read(buffer)) != -1) { 496 try { 497 appendChars(this.pointer, buffer, 0, length); 498 } catch (ExpatException e) { 499 throw new ParseException(e.getMessage(), locator); 500 } 501 } 502 } 503 504 /** 505 * Parses XML from the given input stream. 506 */ parseFragment(InputStream in)507 private void parseFragment(InputStream in) 508 throws IOException, SAXException { 509 byte[] buffer = new byte[BUFFER_SIZE]; 510 int length; 511 while ((length = in.read(buffer)) != -1) { 512 try { 513 appendBytes(this.pointer, buffer, 0, length); 514 } catch (ExpatException e) { 515 throw new ParseException(e.getMessage(), this.locator); 516 } 517 } 518 } 519 startDocument()520 private void startDocument() throws SAXException { 521 ContentHandler contentHandler = xmlReader.contentHandler; 522 if (contentHandler != null) { 523 contentHandler.setDocumentLocator(this.locator); 524 contentHandler.startDocument(); 525 } 526 } 527 endDocument()528 private void endDocument() throws SAXException { 529 ContentHandler contentHandler; 530 contentHandler = xmlReader.contentHandler; 531 if (contentHandler != null) { 532 contentHandler.endDocument(); 533 } 534 } 535 536 /** 537 * Indicate that we're finished parsing. 538 * 539 * @throws SAXException if the xml is incomplete 540 */ finish()541 /*package*/ void finish() throws SAXException { 542 try { 543 appendString(this.pointer, "", true); 544 } catch (ExpatException e) { 545 throw new ParseException(e.getMessage(), this.locator); 546 } 547 } 548 finalize()549 @Override protected synchronized void finalize() throws Throwable { 550 try { 551 if (this.pointer != 0) { 552 release(this.pointer); 553 this.pointer = 0; 554 } 555 } finally { 556 super.finalize(); 557 } 558 } 559 560 /** 561 * Releases all native objects. 562 */ release(long pointer)563 private native void release(long pointer); 564 565 /** 566 * Releases native parser only. 567 */ releaseParser(long pointer)568 private static native void releaseParser(long pointer); 569 570 /** 571 * Initialize static resources. 572 */ staticInitialize(String emptyString)573 private static native void staticInitialize(String emptyString); 574 575 static { 576 staticInitialize(""); 577 } 578 579 /** 580 * Gets the current line number within the XML file. 581 */ line()582 private int line() { 583 return line(this.pointer); 584 } 585 line(long pointer)586 private static native int line(long pointer); 587 588 /** 589 * Gets the current column number within the XML file. 590 */ column()591 private int column() { 592 return column(this.pointer); 593 } 594 column(long pointer)595 private static native int column(long pointer); 596 597 /** 598 * Clones the current attributes so they can be used outside of 599 * startElement(). 600 */ cloneAttributes()601 /*package*/ Attributes cloneAttributes() { 602 if (!inStartElement) { 603 throw new IllegalStateException(OUTSIDE_START_ELEMENT); 604 } 605 606 if (attributeCount == 0) { 607 return ClonedAttributes.EMPTY; 608 } 609 610 long clonePointer 611 = cloneAttributes(this.attributePointer, this.attributeCount); 612 return new ClonedAttributes(pointer, clonePointer, attributeCount); 613 } 614 cloneAttributes(long pointer, int attributeCount)615 private static native long cloneAttributes(long pointer, int attributeCount); 616 617 /** 618 * Used for cloned attributes. 619 */ 620 private static class ClonedAttributes extends ExpatAttributes { 621 622 private static final Attributes EMPTY = new ClonedAttributes(0, 0, 0); 623 624 private final long parserPointer; 625 private long pointer; 626 private final int length; 627 628 /** 629 * Constructs a Java wrapper for native attributes. 630 * 631 * @param parserPointer pointer to the parse, can be 0 if length is 0. 632 * @param pointer pointer to the attributes array, can be 0 if the 633 * length is 0. 634 * @param length number of attributes 635 */ ClonedAttributes(long parserPointer, long pointer, int length)636 private ClonedAttributes(long parserPointer, long pointer, int length) { 637 this.parserPointer = parserPointer; 638 this.pointer = pointer; 639 this.length = length; 640 } 641 642 @Override getParserPointer()643 public long getParserPointer() { 644 return this.parserPointer; 645 } 646 647 @Override getPointer()648 public long getPointer() { 649 return pointer; 650 } 651 652 @Override getLength()653 public int getLength() { 654 return length; 655 } 656 finalize()657 @Override protected synchronized void finalize() throws Throwable { 658 try { 659 if (pointer != 0) { 660 freeAttributes(pointer); 661 pointer = 0; 662 } 663 } finally { 664 super.finalize(); 665 } 666 } 667 } 668 669 private class ExpatLocator implements Locator { 670 getPublicId()671 public String getPublicId() { 672 return publicId; 673 } 674 getSystemId()675 public String getSystemId() { 676 return systemId; 677 } 678 getLineNumber()679 public int getLineNumber() { 680 return line(); 681 } 682 getColumnNumber()683 public int getColumnNumber() { 684 return column(); 685 } 686 687 @Override toString()688 public String toString() { 689 return "Locator[publicId: " + publicId + ", systemId: " + systemId 690 + ", line: " + getLineNumber() 691 + ", column: " + getColumnNumber() + "]"; 692 } 693 } 694 695 /** 696 * Attributes that are only valid during startElement(). 697 */ 698 private class CurrentAttributes extends ExpatAttributes { 699 700 @Override getParserPointer()701 public long getParserPointer() { 702 return pointer; 703 } 704 705 @Override getPointer()706 public long getPointer() { 707 if (!inStartElement) { 708 throw new IllegalStateException(OUTSIDE_START_ELEMENT); 709 } 710 return attributePointer; 711 } 712 713 @Override getLength()714 public int getLength() { 715 if (!inStartElement) { 716 throw new IllegalStateException(OUTSIDE_START_ELEMENT); 717 } 718 return attributeCount; 719 } 720 } 721 722 /** 723 * Includes line and column in the message. 724 */ 725 private static class ParseException extends SAXParseException { 726 ParseException(String message, Locator locator)727 private ParseException(String message, Locator locator) { 728 super(makeMessage(message, locator), locator); 729 } 730 makeMessage(String message, Locator locator)731 private static String makeMessage(String message, Locator locator) { 732 return makeMessage(message, locator.getLineNumber(), 733 locator.getColumnNumber()); 734 } 735 makeMessage( String message, int line, int column)736 private static String makeMessage( 737 String message, int line, int column) { 738 return "At line " + line + ", column " 739 + column + ": " + message; 740 } 741 } 742 743 /** 744 * Opens an InputStream for the given URL. 745 */ openUrl(String url)746 /*package*/ static InputStream openUrl(String url) throws IOException { 747 try { 748 URLConnection urlConnection = new URL(url).openConnection(); 749 urlConnection.setConnectTimeout(TIMEOUT); 750 urlConnection.setReadTimeout(TIMEOUT); 751 urlConnection.setDoInput(true); 752 urlConnection.setDoOutput(false); 753 return urlConnection.getInputStream(); 754 } catch (Exception e) { 755 IOException ioe = new IOException("Couldn't open " + url); 756 ioe.initCause(e); 757 throw ioe; 758 } 759 } 760 761 /** 762 * Parses an external entity. 763 */ 764 private static class EntityParser extends ExpatParser { 765 766 private int depth = 0; 767 EntityParser(String encoding, ExpatReader xmlReader, long pointer, String publicId, String systemId)768 private EntityParser(String encoding, ExpatReader xmlReader, 769 long pointer, String publicId, String systemId) { 770 super(encoding, xmlReader, pointer, publicId, systemId); 771 } 772 773 @Override startElement(String uri, String localName, String qName, long attributePointer, int attributeCount)774 void startElement(String uri, String localName, String qName, 775 long attributePointer, int attributeCount) throws SAXException { 776 /* 777 * Skip topmost element generated by our workaround in 778 * {@link #handleExternalEntity}. 779 */ 780 if (depth++ > 0) { 781 super.startElement(uri, localName, qName, attributePointer, 782 attributeCount); 783 } 784 } 785 786 @Override endElement(String uri, String localName, String qName)787 void endElement(String uri, String localName, String qName) 788 throws SAXException { 789 if (--depth > 0) { 790 super.endElement(uri, localName, qName); 791 } 792 } 793 794 @Override 795 @SuppressWarnings("FinalizeDoesntCallSuperFinalize") finalize()796 protected synchronized void finalize() throws Throwable { 797 /* 798 * Don't release our native resources. We do so explicitly in 799 * {@link #handleExternalEntity} and we don't want to release the 800 * parsing context--our parent is using it. 801 */ 802 } 803 } 804 } 805