1 /* 2 * Copyright (C) 2008 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 #define LOG_TAG "ExpatParser" 18 19 #include "JNIHelp.h" 20 #include "JniConstants.h" 21 #include "JniException.h" 22 #include "LocalArray.h" 23 #include "ScopedLocalRef.h" 24 #include "ScopedPrimitiveArray.h" 25 #include "ScopedStringChars.h" 26 #include "ScopedUtfChars.h" 27 #include "jni.h" 28 #include "cutils/log.h" 29 #include "unicode/unistr.h" 30 31 #include <memory> 32 33 #include <string.h> 34 #include <expat.h> 35 36 #define BUCKET_COUNT 128 37 38 /** 39 * Wrapper around an interned string. 40 */ 41 struct InternedString { 42 InternedString() : interned(NULL), bytes(NULL) { 43 } 44 45 ~InternedString() { 46 delete[] bytes; 47 } 48 49 /** The interned string itself. */ 50 jstring interned; 51 52 /** UTF-8 equivalent of the interned string. */ 53 const char* bytes; 54 55 /** Hash code of the interned string. */ 56 int hash; 57 }; 58 59 /** 60 * Keeps track of strings between start and end events. 61 */ 62 class StringStack { 63 public: 64 StringStack() : array(new jstring[DEFAULT_CAPACITY]), capacity(DEFAULT_CAPACITY), size(0) { 65 } 66 67 ~StringStack() { 68 delete[] array; 69 } 70 71 void push(JNIEnv* env, jstring s) { 72 if (size == capacity) { 73 int newCapacity = capacity * 2; 74 jstring* newArray = new jstring[newCapacity]; 75 if (newArray == NULL) { 76 jniThrowOutOfMemoryError(env, NULL); 77 return; 78 } 79 memcpy(newArray, array, capacity * sizeof(jstring)); 80 81 delete[] array; 82 array = newArray; 83 capacity = newCapacity; 84 } 85 86 array[size++] = s; 87 } 88 89 jstring pop() { 90 return (size == 0) ? NULL : array[--size]; 91 } 92 93 private: 94 enum { DEFAULT_CAPACITY = 10 }; 95 96 jstring* array; 97 int capacity; 98 int size; 99 }; 100 101 /** 102 * Data passed to parser handler method by the parser. 103 */ 104 struct ParsingContext { 105 ParsingContext(jobject object) : env(NULL), object(object), buffer(NULL), bufferSize(-1) { 106 for (int i = 0; i < BUCKET_COUNT; i++) { 107 internedStrings[i] = NULL; 108 } 109 } 110 111 // Warning: 'env' must be valid on entry. 112 ~ParsingContext() { 113 freeBuffer(); 114 115 // Free interned string cache. 116 for (int i = 0; i < BUCKET_COUNT; i++) { 117 if (internedStrings[i]) { 118 InternedString** bucket = internedStrings[i]; 119 InternedString* current; 120 while ((current = *(bucket++)) != NULL) { 121 // Free the interned string reference. 122 env->DeleteGlobalRef(current->interned); 123 124 // Free the bucket. 125 delete current; 126 } 127 128 // Free the buckets. 129 delete[] internedStrings[i]; 130 } 131 } 132 } 133 134 jcharArray ensureCapacity(int length) { 135 if (bufferSize < length) { 136 // Free the existing char[]. 137 freeBuffer(); 138 139 // Allocate a new char[]. 140 jcharArray javaBuffer = env->NewCharArray(length); 141 if (javaBuffer == NULL) return NULL; 142 143 // Create a global reference. 144 javaBuffer = reinterpret_cast<jcharArray>(env->NewGlobalRef(javaBuffer)); 145 if (javaBuffer == NULL) return NULL; 146 147 buffer = javaBuffer; 148 bufferSize = length; 149 } 150 return buffer; 151 } 152 153 private: 154 void freeBuffer() { 155 if (buffer != NULL) { 156 env->DeleteGlobalRef(buffer); 157 buffer = NULL; 158 bufferSize = -1; 159 } 160 } 161 162 public: 163 /** 164 * The JNI environment for the current thread. This should only be used 165 * to keep a reference to the env for use in Expat callbacks. 166 */ 167 JNIEnv* env; 168 169 /** The Java parser object. */ 170 jobject object; 171 172 /** Buffer for text events. */ 173 jcharArray buffer; 174 175 private: 176 /** The size of our buffer in jchars. */ 177 int bufferSize; 178 179 public: 180 /** Current attributes. */ 181 const char** attributes; 182 183 /** Number of attributes. */ 184 int attributeCount; 185 186 /** True if namespace support is enabled. */ 187 bool processNamespaces; 188 189 /** Keep track of names. */ 190 StringStack stringStack; 191 192 /** Cache of interned strings. */ 193 InternedString** internedStrings[BUCKET_COUNT]; 194 }; 195 196 static ParsingContext* toParsingContext(void* data) { 197 return reinterpret_cast<ParsingContext*>(data); 198 } 199 200 static ParsingContext* toParsingContext(XML_Parser parser) { 201 return reinterpret_cast<ParsingContext*>(XML_GetUserData(parser)); 202 } 203 204 static XML_Parser toXMLParser(jlong address) { 205 return reinterpret_cast<XML_Parser>(address); 206 } 207 208 static jlong fromXMLParser(XML_Parser parser) { 209 return reinterpret_cast<uintptr_t>(parser); 210 } 211 212 static jmethodID commentMethod; 213 static jmethodID endCdataMethod; 214 static jmethodID endDtdMethod; 215 static jmethodID endElementMethod; 216 static jmethodID endNamespaceMethod; 217 static jmethodID handleExternalEntityMethod; 218 static jmethodID internMethod; 219 static jmethodID notationDeclMethod; 220 static jmethodID processingInstructionMethod; 221 static jmethodID startCdataMethod; 222 static jmethodID startDtdMethod; 223 static jmethodID startElementMethod; 224 static jmethodID startNamespaceMethod; 225 static jmethodID textMethod; 226 static jmethodID unparsedEntityDeclMethod; 227 static jstring emptyString; 228 229 /** 230 * Calculates a hash code for a null-terminated string. This is *not* equivalent 231 * to Java's String.hashCode(). This hashes the bytes while String.hashCode() 232 * hashes UTF-16 chars. 233 * 234 * @param s null-terminated string to hash 235 * @returns hash code 236 */ 237 static int hashString(const char* s) { 238 int hash = 0; 239 if (s) { 240 while (*s) { 241 hash = hash * 31 + *s++; 242 } 243 } 244 return hash; 245 } 246 247 /** 248 * Creates a new interned string wrapper. Looks up the interned string 249 * representing the given UTF-8 bytes. 250 * 251 * @param bytes null-terminated string to intern 252 * @param hash of bytes 253 * @returns wrapper of interned Java string 254 */ 255 static InternedString* newInternedString(JNIEnv* env, const char* bytes, int hash) { 256 // Allocate a new wrapper. 257 std::unique_ptr<InternedString> wrapper(new InternedString); 258 if (wrapper.get() == NULL) { 259 jniThrowOutOfMemoryError(env, NULL); 260 return NULL; 261 } 262 263 // Create a copy of the UTF-8 bytes. 264 // TODO: sometimes we already know the length. Reuse it if so. 265 char* copy = new char[strlen(bytes) + 1]; 266 if (copy == NULL) { 267 jniThrowOutOfMemoryError(env, NULL); 268 return NULL; 269 } 270 strcpy(copy, bytes); 271 wrapper->bytes = copy; 272 273 // Save the hash. 274 wrapper->hash = hash; 275 276 // To intern a string, we must first create a new string and then call 277 // intern() on it. We then keep a global reference to the interned string. 278 ScopedLocalRef<jstring> newString(env, env->NewStringUTF(bytes)); 279 if (env->ExceptionCheck()) { 280 return NULL; 281 } 282 283 // Call intern(). 284 ScopedLocalRef<jstring> interned(env, 285 reinterpret_cast<jstring>(env->CallObjectMethod(newString.get(), internMethod))); 286 if (env->ExceptionCheck()) { 287 return NULL; 288 } 289 290 // Create a global reference to the interned string. 291 wrapper->interned = reinterpret_cast<jstring>(env->NewGlobalRef(interned.get())); 292 if (env->ExceptionCheck()) { 293 return NULL; 294 } 295 296 return wrapper.release(); 297 } 298 299 /** 300 * Allocates a new bucket with one entry. 301 * 302 * @param entry to store in the bucket 303 * @returns a reference to the bucket 304 */ 305 static InternedString** newInternedStringBucket(InternedString* entry) { 306 InternedString** bucket = new InternedString*[2]; 307 if (bucket != NULL) { 308 bucket[0] = entry; 309 bucket[1] = NULL; 310 } 311 return bucket; 312 } 313 314 /** 315 * Expands an interned string bucket and adds the given entry. Frees the 316 * provided bucket and returns a new one. 317 * 318 * @param existingBucket the bucket to replace 319 * @param entry to add to the bucket 320 * @returns a reference to the newly-allocated bucket containing the given entry 321 */ 322 static InternedString** expandInternedStringBucket( 323 InternedString** existingBucket, InternedString* entry) { 324 // Determine the size of the existing bucket. 325 int size = 0; 326 while (existingBucket[size]) size++; 327 328 // Allocate the new bucket with enough space for one more entry and 329 // a null terminator. 330 InternedString** newBucket = new InternedString*[size + 2]; 331 if (newBucket == NULL) return NULL; 332 333 memcpy(newBucket, existingBucket, size * sizeof(InternedString*)); 334 newBucket[size] = entry; 335 newBucket[size + 1] = NULL; 336 delete[] existingBucket; 337 338 return newBucket; 339 } 340 341 /** 342 * Returns an interned string for the given UTF-8 string. 343 * 344 * @param bucket to search for s 345 * @param s null-terminated string to find 346 * @param hash of s 347 * @returns interned Java string equivalent of s or null if not found 348 */ 349 static jstring findInternedString(InternedString** bucket, const char* s, int hash) { 350 InternedString* current; 351 while ((current = *(bucket++)) != NULL) { 352 if (current->hash != hash) continue; 353 if (!strcmp(s, current->bytes)) return current->interned; 354 } 355 return NULL; 356 } 357 358 /** 359 * Returns an interned string for the given UTF-8 string. 360 * 361 * @param s null-terminated string to intern 362 * @returns interned Java string equivelent of s or NULL if s is null 363 */ 364 static jstring internString(JNIEnv* env, ParsingContext* parsingContext, const char* s) { 365 if (s == NULL) return NULL; 366 367 int hash = hashString(s); 368 int bucketIndex = hash & (BUCKET_COUNT - 1); 369 370 InternedString*** buckets = parsingContext->internedStrings; 371 InternedString** bucket = buckets[bucketIndex]; 372 InternedString* internedString; 373 374 if (bucket) { 375 // We have a bucket already. Look for the given string. 376 jstring found = findInternedString(bucket, s, hash); 377 if (found) { 378 // We found it! 379 return found; 380 } 381 382 // We didn't find it. :( 383 // Create a new entry. 384 internedString = newInternedString(env, s, hash); 385 if (internedString == NULL) return NULL; 386 387 // Expand the bucket. 388 bucket = expandInternedStringBucket(bucket, internedString); 389 if (bucket == NULL) { 390 delete internedString; 391 jniThrowOutOfMemoryError(env, NULL); 392 return NULL; 393 } 394 395 buckets[bucketIndex] = bucket; 396 397 return internedString->interned; 398 } else { 399 // We don't even have a bucket yet. Create an entry. 400 internedString = newInternedString(env, s, hash); 401 if (internedString == NULL) return NULL; 402 403 // Create a new bucket with one entry. 404 bucket = newInternedStringBucket(internedString); 405 if (bucket == NULL) { 406 delete internedString; 407 jniThrowOutOfMemoryError(env, NULL); 408 return NULL; 409 } 410 411 buckets[bucketIndex] = bucket; 412 413 return internedString->interned; 414 } 415 } 416 417 static void jniThrowExpatException(JNIEnv* env, XML_Error error) { 418 const char* message = XML_ErrorString(error); 419 jniThrowException(env, "org/apache/harmony/xml/ExpatException", message); 420 } 421 422 /** 423 * Copies UTF-8 characters into the buffer. Returns the number of Java chars 424 * which were buffered. 425 * 426 * @returns number of UTF-16 characters which were copied 427 */ 428 static size_t fillBuffer(ParsingContext* parsingContext, const char* utf8, int byteCount) { 429 JNIEnv* env = parsingContext->env; 430 431 // Grow buffer if necessary (the length in bytes is always >= the length in chars). 432 jcharArray javaChars = parsingContext->ensureCapacity(byteCount); 433 if (javaChars == NULL) { 434 return -1; 435 } 436 437 // Decode UTF-8 characters into our char[]. 438 ScopedCharArrayRW chars(env, javaChars); 439 if (chars.get() == NULL) { 440 return -1; 441 } 442 UErrorCode status = U_ZERO_ERROR; 443 icu::UnicodeString utf16(icu::UnicodeString::fromUTF8(icu::StringPiece(utf8, byteCount))); 444 return utf16.extract(chars.get(), byteCount, status); 445 } 446 447 /** 448 * Buffers the given text and passes it to the given method. 449 * 450 * @param method to pass the characters and length to with signature 451 * (char[], int) 452 * @param data parsing context 453 * @param text to copy into the buffer 454 * @param length of text to copy (in bytes) 455 */ 456 static void bufferAndInvoke(jmethodID method, void* data, const char* text, size_t length) { 457 ParsingContext* parsingContext = toParsingContext(data); 458 JNIEnv* env = parsingContext->env; 459 460 // Bail out if a previously called handler threw an exception. 461 if (env->ExceptionCheck()) return; 462 463 // Buffer the element name. 464 size_t utf16length = fillBuffer(parsingContext, text, length); 465 466 // Invoke given method. 467 jobject javaParser = parsingContext->object; 468 jcharArray buffer = parsingContext->buffer; 469 env->CallVoidMethod(javaParser, method, buffer, utf16length); 470 } 471 472 static const char** toAttributes(jlong attributePointer) { 473 return reinterpret_cast<const char**>(static_cast<uintptr_t>(attributePointer)); 474 } 475 476 /** 477 * The component parts of an attribute or element name. 478 */ 479 class ExpatElementName { 480 public: 481 ExpatElementName(JNIEnv* env, ParsingContext* parsingContext, jlong attributePointer, jint index) { 482 const char** attributes = toAttributes(attributePointer); 483 const char* name = attributes[index * 2]; 484 init(env, parsingContext, name); 485 } 486 487 ExpatElementName(JNIEnv* env, ParsingContext* parsingContext, const char* s) { 488 init(env, parsingContext, s); 489 } 490 491 ~ExpatElementName() { 492 free(mCopy); 493 } 494 495 /** 496 * Returns the namespace URI, like "http://www.w3.org/1999/xhtml". 497 * Possibly empty. 498 */ 499 jstring uri() { 500 return internString(mEnv, mParsingContext, mUri); 501 } 502 503 /** 504 * Returns the element or attribute local name, like "h1". Never empty. When 505 * namespace processing is disabled, this may contain a prefix, yielding a 506 * local name like "html:h1". In such cases, the qName will always be empty. 507 */ 508 jstring localName() { 509 return internString(mEnv, mParsingContext, mLocalName); 510 } 511 512 /** 513 * Returns the namespace prefix, like "html". Possibly empty. 514 */ 515 jstring qName() { 516 if (*mPrefix == 0) { 517 return localName(); 518 } 519 520 // return prefix + ":" + localName 521 ::LocalArray<1024> qName(strlen(mPrefix) + 1 + strlen(mLocalName) + 1); 522 snprintf(&qName[0], qName.size(), "%s:%s", mPrefix, mLocalName); 523 return internString(mEnv, mParsingContext, &qName[0]); 524 } 525 526 /** 527 * Returns true if this expat name has the same URI and local name. 528 */ 529 bool matches(const char* uri, const char* localName) { 530 return strcmp(uri, mUri) == 0 && strcmp(localName, mLocalName) == 0; 531 } 532 533 /** 534 * Returns true if this expat name has the same qualified name. 535 */ 536 bool matchesQName(const char* qName) { 537 const char* lastColon = strrchr(qName, ':'); 538 539 // Compare local names only if either: 540 // - the input qualified name doesn't have a colon (like "h1") 541 // - this element doesn't have a prefix. Such is the case when it 542 // doesn't belong to a namespace, or when this parser's namespace 543 // processing is disabled. In the latter case, this element's local 544 // name may still contain a colon (like "html:h1"). 545 if (lastColon == NULL || *mPrefix == 0) { 546 return strcmp(qName, mLocalName) == 0; 547 } 548 549 // Otherwise compare both prefix and local name 550 size_t prefixLength = lastColon - qName; 551 return strlen(mPrefix) == prefixLength 552 && strncmp(qName, mPrefix, prefixLength) == 0 553 && strcmp(lastColon + 1, mLocalName) == 0; 554 } 555 556 private: 557 JNIEnv* mEnv; 558 ParsingContext* mParsingContext; 559 char* mCopy; 560 const char* mUri; 561 const char* mLocalName; 562 const char* mPrefix; 563 564 /** 565 * Decodes an Expat-encoded name of one of these three forms: 566 * "uri|localName|prefix" (example: "http://www.w3.org/1999/xhtml|h1|html") 567 * "uri|localName" (example: "http://www.w3.org/1999/xhtml|h1") 568 * "localName" (example: "h1") 569 */ 570 void init(JNIEnv* env, ParsingContext* parsingContext, const char* s) { 571 mEnv = env; 572 mParsingContext = parsingContext; 573 mCopy = strdup(s); 574 575 // split the input into up to 3 parts: a|b|c 576 char* context = NULL; 577 char* a = strtok_r(mCopy, "|", &context); 578 char* b = strtok_r(NULL, "|", &context); 579 char* c = strtok_r(NULL, "|", &context); 580 581 if (c != NULL) { // input of the form "uri|localName|prefix" 582 mUri = a; 583 mLocalName = b; 584 mPrefix = c; 585 } else if (b != NULL) { // input of the form "uri|localName" 586 mUri = a; 587 mLocalName = b; 588 mPrefix = ""; 589 } else { // input of the form "localName" 590 mLocalName = a; 591 mUri = ""; 592 mPrefix = ""; 593 } 594 } 595 596 // Disallow copy and assignment. 597 ExpatElementName(const ExpatElementName&); 598 void operator=(const ExpatElementName&); 599 }; 600 601 /** 602 * Called by Expat at the start of an element. Delegates to the same method 603 * on the Java parser. 604 * 605 * @param data parsing context 606 * @param elementName "uri|localName" or "localName" for the current element 607 * @param attributes alternating attribute names and values. Like element 608 * names, attribute names follow the format "uri|localName" or "localName". 609 */ 610 static void startElement(void* data, const char* elementName, const char** attributes) { 611 ParsingContext* parsingContext = toParsingContext(data); 612 JNIEnv* env = parsingContext->env; 613 614 // Bail out if a previously called handler threw an exception. 615 if (env->ExceptionCheck()) return; 616 617 // Count the number of attributes. 618 int count = 0; 619 while (attributes[count * 2]) count++; 620 621 // Make the attributes available for the duration of this call. 622 parsingContext->attributes = attributes; 623 parsingContext->attributeCount = count; 624 625 jobject javaParser = parsingContext->object; 626 627 ExpatElementName e(env, parsingContext, elementName); 628 jstring uri = parsingContext->processNamespaces ? e.uri() : emptyString; 629 jstring localName = parsingContext->processNamespaces ? e.localName() : emptyString; 630 jstring qName = e.qName(); 631 632 parsingContext->stringStack.push(env, qName); 633 parsingContext->stringStack.push(env, uri); 634 parsingContext->stringStack.push(env, localName); 635 636 jlong attributesAddress = reinterpret_cast<jlong>(attributes); 637 env->CallVoidMethod(javaParser, startElementMethod, uri, localName, qName, attributesAddress, count); 638 639 parsingContext->attributes = NULL; 640 parsingContext->attributeCount = -1; 641 } 642 643 /** 644 * Called by Expat at the end of an element. Delegates to the same method 645 * on the Java parser. 646 * 647 * @param data parsing context 648 * @param elementName "uri|localName" or "localName" for the current element; 649 * we assume that this matches the last data on our stack. 650 */ 651 static void endElement(void* data, const char* /*elementName*/) { 652 ParsingContext* parsingContext = toParsingContext(data); 653 JNIEnv* env = parsingContext->env; 654 655 // Bail out if a previously called handler threw an exception. 656 if (env->ExceptionCheck()) return; 657 658 jobject javaParser = parsingContext->object; 659 660 jstring localName = parsingContext->stringStack.pop(); 661 jstring uri = parsingContext->stringStack.pop(); 662 jstring qName = parsingContext->stringStack.pop(); 663 664 env->CallVoidMethod(javaParser, endElementMethod, uri, localName, qName); 665 } 666 667 /** 668 * Called by Expat when it encounters text. Delegates to the same method 669 * on the Java parser. This may be called mutiple times with incremental pieces 670 * of the same contiguous block of text. 671 * 672 * @param data parsing context 673 * @param characters buffer containing encountered text 674 * @param length number of characters in the buffer 675 */ 676 static void text(void* data, const char* characters, int length) { 677 bufferAndInvoke(textMethod, data, characters, length); 678 } 679 680 /** 681 * Called by Expat when it encounters a comment. Delegates to the same method 682 * on the Java parser. 683 684 * @param data parsing context 685 * @param comment 0-terminated 686 */ 687 static void comment(void* data, const char* comment) { 688 bufferAndInvoke(commentMethod, data, comment, strlen(comment)); 689 } 690 691 /** 692 * Called by Expat at the beginning of a namespace mapping. 693 * 694 * @param data parsing context 695 * @param prefix null-terminated namespace prefix used in the XML 696 * @param uri of the namespace 697 */ 698 static void startNamespace(void* data, const char* prefix, const char* uri) { 699 ParsingContext* parsingContext = toParsingContext(data); 700 JNIEnv* env = parsingContext->env; 701 702 // Bail out if a previously called handler threw an exception. 703 if (env->ExceptionCheck()) return; 704 705 jstring internedPrefix = emptyString; 706 if (prefix != NULL) { 707 internedPrefix = internString(env, parsingContext, prefix); 708 if (env->ExceptionCheck()) return; 709 } 710 711 jstring internedUri = emptyString; 712 if (uri != NULL) { 713 internedUri = internString(env, parsingContext, uri); 714 if (env->ExceptionCheck()) return; 715 } 716 717 parsingContext->stringStack.push(env, internedPrefix); 718 719 jobject javaParser = parsingContext->object; 720 env->CallVoidMethod(javaParser, startNamespaceMethod, internedPrefix, internedUri); 721 } 722 723 /** 724 * Called by Expat at the end of a namespace mapping. 725 * 726 * @param data parsing context 727 * @param prefix null-terminated namespace prefix used in the XML; 728 * we assume this is the same as the last prefix on the stack. 729 */ 730 static void endNamespace(void* data, const char* /*prefix*/) { 731 ParsingContext* parsingContext = toParsingContext(data); 732 JNIEnv* env = parsingContext->env; 733 734 // Bail out if a previously called handler threw an exception. 735 if (env->ExceptionCheck()) return; 736 737 jstring internedPrefix = parsingContext->stringStack.pop(); 738 739 jobject javaParser = parsingContext->object; 740 env->CallVoidMethod(javaParser, endNamespaceMethod, internedPrefix); 741 } 742 743 /** 744 * Called by Expat at the beginning of a CDATA section. 745 * 746 * @param data parsing context 747 */ 748 static void startCdata(void* data) { 749 ParsingContext* parsingContext = toParsingContext(data); 750 JNIEnv* env = parsingContext->env; 751 752 // Bail out if a previously called handler threw an exception. 753 if (env->ExceptionCheck()) return; 754 755 jobject javaParser = parsingContext->object; 756 env->CallVoidMethod(javaParser, startCdataMethod); 757 } 758 759 /** 760 * Called by Expat at the end of a CDATA section. 761 * 762 * @param data parsing context 763 */ 764 static void endCdata(void* data) { 765 ParsingContext* parsingContext = toParsingContext(data); 766 JNIEnv* env = parsingContext->env; 767 768 // Bail out if a previously called handler threw an exception. 769 if (env->ExceptionCheck()) return; 770 771 jobject javaParser = parsingContext->object; 772 env->CallVoidMethod(javaParser, endCdataMethod); 773 } 774 775 /** 776 * Called by Expat at the beginning of a DOCTYPE section. 777 * Expat gives us 'hasInternalSubset', but the Java API doesn't expect it, so we don't need it. 778 */ 779 static void startDtd(void* data, const char* name, 780 const char* systemId, const char* publicId, int /*hasInternalSubset*/) { 781 ParsingContext* parsingContext = toParsingContext(data); 782 JNIEnv* env = parsingContext->env; 783 784 // Bail out if a previously called handler threw an exception. 785 if (env->ExceptionCheck()) return; 786 787 jstring javaName = internString(env, parsingContext, name); 788 if (env->ExceptionCheck()) return; 789 790 jstring javaPublicId = internString(env, parsingContext, publicId); 791 if (env->ExceptionCheck()) return; 792 793 jstring javaSystemId = internString(env, parsingContext, systemId); 794 if (env->ExceptionCheck()) return; 795 796 jobject javaParser = parsingContext->object; 797 env->CallVoidMethod(javaParser, startDtdMethod, javaName, javaPublicId, 798 javaSystemId); 799 } 800 801 /** 802 * Called by Expat at the end of a DOCTYPE section. 803 * 804 * @param data parsing context 805 */ 806 static void endDtd(void* data) { 807 ParsingContext* parsingContext = toParsingContext(data); 808 JNIEnv* env = parsingContext->env; 809 810 // Bail out if a previously called handler threw an exception. 811 if (env->ExceptionCheck()) return; 812 813 jobject javaParser = parsingContext->object; 814 env->CallVoidMethod(javaParser, endDtdMethod); 815 } 816 817 /** 818 * Called by Expat when it encounters processing instructions. 819 * 820 * @param data parsing context 821 * @param target of the instruction 822 * @param instructionData 823 */ 824 static void processingInstruction(void* data, const char* target, const char* instructionData) { 825 ParsingContext* parsingContext = toParsingContext(data); 826 JNIEnv* env = parsingContext->env; 827 828 // Bail out if a previously called handler threw an exception. 829 if (env->ExceptionCheck()) return; 830 831 jstring javaTarget = internString(env, parsingContext, target); 832 if (env->ExceptionCheck()) return; 833 834 ScopedLocalRef<jstring> javaInstructionData(env, env->NewStringUTF(instructionData)); 835 if (env->ExceptionCheck()) return; 836 837 jobject javaParser = parsingContext->object; 838 env->CallVoidMethod(javaParser, processingInstructionMethod, javaTarget, javaInstructionData.get()); 839 } 840 841 /** 842 * Creates a new entity parser. 843 * 844 * @param object the Java ExpatParser instance 845 * @param parentParser pointer 846 * @param javaEncoding the character encoding name 847 * @param javaContext that was provided to handleExternalEntity 848 * @returns the pointer to the C Expat entity parser 849 */ 850 static jlong ExpatParser_createEntityParser(JNIEnv* env, jobject, jlong parentParser, jstring javaContext) { 851 ScopedUtfChars context(env, javaContext); 852 if (context.c_str() == NULL) { 853 return 0; 854 } 855 856 XML_Parser parent = toXMLParser(parentParser); 857 XML_Parser entityParser = XML_ExternalEntityParserCreate(parent, context.c_str(), NULL); 858 if (entityParser == NULL) { 859 jniThrowOutOfMemoryError(env, NULL); 860 } 861 862 return fromXMLParser(entityParser); 863 } 864 865 /** 866 * Handles external entities. We ignore the "base" URI and keep track of it 867 * ourselves. 868 */ 869 static int handleExternalEntity(XML_Parser parser, const char* context, 870 const char*, const char* systemId, const char* publicId) { 871 ParsingContext* parsingContext = toParsingContext(parser); 872 jobject javaParser = parsingContext->object; 873 JNIEnv* env = parsingContext->env; 874 jobject object = parsingContext->object; 875 876 // Bail out if a previously called handler threw an exception. 877 if (env->ExceptionCheck()) { 878 return XML_STATUS_ERROR; 879 } 880 881 ScopedLocalRef<jstring> javaSystemId(env, env->NewStringUTF(systemId)); 882 if (env->ExceptionCheck()) { 883 return XML_STATUS_ERROR; 884 } 885 ScopedLocalRef<jstring> javaPublicId(env, env->NewStringUTF(publicId)); 886 if (env->ExceptionCheck()) { 887 return XML_STATUS_ERROR; 888 } 889 ScopedLocalRef<jstring> javaContext(env, env->NewStringUTF(context)); 890 if (env->ExceptionCheck()) { 891 return XML_STATUS_ERROR; 892 } 893 894 // Pass the wrapped parser and both strings to java. 895 env->CallVoidMethod(javaParser, handleExternalEntityMethod, javaContext.get(), 896 javaPublicId.get(), javaSystemId.get()); 897 898 /* 899 * Parsing the external entity leaves parsingContext->env and object set to 900 * NULL, so we need to restore both. 901 * 902 * TODO: consider restoring the original env and object instead of setting 903 * them to NULL in the append() functions. 904 */ 905 parsingContext->env = env; 906 parsingContext->object = object; 907 908 return env->ExceptionCheck() ? XML_STATUS_ERROR : XML_STATUS_OK; 909 } 910 911 /** 912 * Expat gives us 'base', but the Java API doesn't expect it, so we don't need it. 913 */ 914 static void unparsedEntityDecl(void* data, const char* name, const char* /*base*/, const char* systemId, const char* publicId, const char* notationName) { 915 ParsingContext* parsingContext = toParsingContext(data); 916 jobject javaParser = parsingContext->object; 917 JNIEnv* env = parsingContext->env; 918 919 // Bail out if a previously called handler threw an exception. 920 if (env->ExceptionCheck()) return; 921 922 ScopedLocalRef<jstring> javaName(env, env->NewStringUTF(name)); 923 if (env->ExceptionCheck()) return; 924 ScopedLocalRef<jstring> javaPublicId(env, env->NewStringUTF(publicId)); 925 if (env->ExceptionCheck()) return; 926 ScopedLocalRef<jstring> javaSystemId(env, env->NewStringUTF(systemId)); 927 if (env->ExceptionCheck()) return; 928 ScopedLocalRef<jstring> javaNotationName(env, env->NewStringUTF(notationName)); 929 if (env->ExceptionCheck()) return; 930 931 env->CallVoidMethod(javaParser, unparsedEntityDeclMethod, javaName.get(), javaPublicId.get(), javaSystemId.get(), javaNotationName.get()); 932 } 933 934 /** 935 * Expat gives us 'base', but the Java API doesn't expect it, so we don't need it. 936 */ 937 static void notationDecl(void* data, const char* name, const char* /*base*/, const char* systemId, const char* publicId) { 938 ParsingContext* parsingContext = toParsingContext(data); 939 jobject javaParser = parsingContext->object; 940 JNIEnv* env = parsingContext->env; 941 942 // Bail out if a previously called handler threw an exception. 943 if (env->ExceptionCheck()) return; 944 945 ScopedLocalRef<jstring> javaName(env, env->NewStringUTF(name)); 946 if (env->ExceptionCheck()) return; 947 ScopedLocalRef<jstring> javaPublicId(env, env->NewStringUTF(publicId)); 948 if (env->ExceptionCheck()) return; 949 ScopedLocalRef<jstring> javaSystemId(env, env->NewStringUTF(systemId)); 950 if (env->ExceptionCheck()) return; 951 952 env->CallVoidMethod(javaParser, notationDeclMethod, javaName.get(), javaPublicId.get(), javaSystemId.get()); 953 } 954 955 /** 956 * Creates a new Expat parser. Called from the Java ExpatParser constructor. 957 * 958 * @param object the Java ExpatParser instance 959 * @param javaEncoding the character encoding name 960 * @param processNamespaces true if the parser should handle namespaces 961 * @returns the pointer to the C Expat parser 962 */ 963 static jlong ExpatParser_initialize(JNIEnv* env, jobject object, jstring javaEncoding, 964 jboolean processNamespaces) { 965 // Allocate parsing context. 966 std::unique_ptr<ParsingContext> context(new ParsingContext(object)); 967 if (context.get() == NULL) { 968 jniThrowOutOfMemoryError(env, NULL); 969 return 0; 970 } 971 972 context->processNamespaces = processNamespaces; 973 974 // Create a parser. 975 XML_Parser parser; 976 ScopedUtfChars encoding(env, javaEncoding); 977 if (encoding.c_str() == NULL) { 978 return 0; 979 } 980 if (processNamespaces) { 981 // Use '|' to separate URIs from local names. 982 parser = XML_ParserCreateNS(encoding.c_str(), '|'); 983 } else { 984 parser = XML_ParserCreate(encoding.c_str()); 985 } 986 987 if (parser != NULL) { 988 if (processNamespaces) { 989 XML_SetNamespaceDeclHandler(parser, startNamespace, endNamespace); 990 XML_SetReturnNSTriplet(parser, 1); 991 } 992 993 XML_SetCdataSectionHandler(parser, startCdata, endCdata); 994 XML_SetCharacterDataHandler(parser, text); 995 XML_SetCommentHandler(parser, comment); 996 XML_SetDoctypeDeclHandler(parser, startDtd, endDtd); 997 XML_SetElementHandler(parser, startElement, endElement); 998 XML_SetExternalEntityRefHandler(parser, handleExternalEntity); 999 XML_SetNotationDeclHandler(parser, notationDecl); 1000 XML_SetProcessingInstructionHandler(parser, processingInstruction); 1001 XML_SetUnparsedEntityDeclHandler(parser, unparsedEntityDecl); 1002 XML_SetUserData(parser, context.release()); 1003 } else { 1004 jniThrowOutOfMemoryError(env, NULL); 1005 return 0; 1006 } 1007 1008 return fromXMLParser(parser); 1009 } 1010 1011 /** 1012 * Decodes the bytes as characters and parse the characters as XML. This 1013 * performs character decoding using the charset specified at XML_Parser 1014 * creation. For Java chars, that charset must be UTF-16 so that a Java char[] 1015 * can be reinterpreted as a UTF-16 encoded byte[]. appendBytes, appendChars 1016 * and appendString all call through this method. 1017 */ 1018 static void append(JNIEnv* env, jobject object, jlong pointer, 1019 const char* bytes, size_t byteOffset, size_t byteCount, jboolean isFinal) { 1020 XML_Parser parser = toXMLParser(pointer); 1021 ParsingContext* context = toParsingContext(parser); 1022 context->env = env; 1023 context->object = object; 1024 if (!XML_Parse(parser, bytes + byteOffset, byteCount, isFinal) && !env->ExceptionCheck()) { 1025 jniThrowExpatException(env, XML_GetErrorCode(parser)); 1026 } 1027 context->object = NULL; 1028 context->env = NULL; 1029 } 1030 1031 static void ExpatParser_appendBytes(JNIEnv* env, jobject object, jlong pointer, 1032 jbyteArray xml, jint byteOffset, jint byteCount) { 1033 ScopedByteArrayRO byteArray(env, xml); 1034 if (byteArray.get() == NULL) { 1035 return; 1036 } 1037 1038 const char* bytes = reinterpret_cast<const char*>(byteArray.get()); 1039 append(env, object, pointer, bytes, byteOffset, byteCount, XML_FALSE); 1040 } 1041 1042 static void ExpatParser_appendChars(JNIEnv* env, jobject object, jlong pointer, 1043 jcharArray xml, jint charOffset, jint charCount) { 1044 ScopedCharArrayRO charArray(env, xml); 1045 if (charArray.get() == NULL) { 1046 return; 1047 } 1048 1049 const char* bytes = reinterpret_cast<const char*>(charArray.get()); 1050 size_t byteOffset = 2 * charOffset; 1051 size_t byteCount = 2 * charCount; 1052 append(env, object, pointer, bytes, byteOffset, byteCount, XML_FALSE); 1053 } 1054 1055 static void ExpatParser_appendString(JNIEnv* env, jobject object, jlong pointer, jstring javaXml, jboolean isFinal) { 1056 ScopedStringChars xml(env, javaXml); 1057 if (xml.get() == NULL) { 1058 return; 1059 } 1060 const char* bytes = reinterpret_cast<const char*>(xml.get()); 1061 size_t byteCount = 2 * xml.size(); 1062 append(env, object, pointer, bytes, 0, byteCount, isFinal); 1063 } 1064 1065 /** 1066 * Releases parser only. 1067 */ 1068 static void ExpatParser_releaseParser(JNIEnv*, jobject, jlong address) { 1069 XML_ParserFree(toXMLParser(address)); 1070 } 1071 1072 /** 1073 * Cleans up after the parser. Called at garbage collection time. 1074 */ 1075 static void ExpatParser_release(JNIEnv* env, jobject, jlong address) { 1076 XML_Parser parser = toXMLParser(address); 1077 1078 ParsingContext* context = toParsingContext(parser); 1079 context->env = env; 1080 delete context; 1081 1082 XML_ParserFree(parser); 1083 } 1084 1085 static int ExpatParser_line(JNIEnv*, jobject, jlong address) { 1086 return XML_GetCurrentLineNumber(toXMLParser(address)); 1087 } 1088 1089 static int ExpatParser_column(JNIEnv*, jobject, jlong address) { 1090 return XML_GetCurrentColumnNumber(toXMLParser(address)); 1091 } 1092 1093 /** 1094 * Gets the URI of the attribute at the given index. 1095 * 1096 * @param attributePointer to the attribute array 1097 * @param index of the attribute 1098 * @returns interned Java string containing attribute's URI 1099 */ 1100 static jstring ExpatAttributes_getURI(JNIEnv* env, jobject, jlong address, 1101 jlong attributePointer, jint index) { 1102 ParsingContext* context = toParsingContext(toXMLParser(address)); 1103 return ExpatElementName(env, context, attributePointer, index).uri(); 1104 } 1105 1106 /** 1107 * Gets the local name of the attribute at the given index. 1108 * 1109 * @param attributePointer to the attribute array 1110 * @param index of the attribute 1111 * @returns interned Java string containing attribute's local name 1112 */ 1113 static jstring ExpatAttributes_getLocalName(JNIEnv* env, jobject, jlong address, 1114 jlong attributePointer, jint index) { 1115 ParsingContext* context = toParsingContext(toXMLParser(address)); 1116 return ExpatElementName(env, context, attributePointer, index).localName(); 1117 } 1118 1119 /** 1120 * Gets the qualified name of the attribute at the given index. 1121 * 1122 * @param attributePointer to the attribute array 1123 * @param index of the attribute 1124 * @returns interned Java string containing attribute's local name 1125 */ 1126 static jstring ExpatAttributes_getQName(JNIEnv* env, jobject, jlong address, 1127 jlong attributePointer, jint index) { 1128 ParsingContext* context = toParsingContext(toXMLParser(address)); 1129 return ExpatElementName(env, context, attributePointer, index).qName(); 1130 } 1131 1132 /** 1133 * Gets the value of the attribute at the given index. 1134 * 1135 * @param object Java ExpatParser instance 1136 * @param attributePointer to the attribute array 1137 * @param index of the attribute 1138 * @returns Java string containing attribute's value 1139 */ 1140 static jstring ExpatAttributes_getValueByIndex(JNIEnv* env, jobject, 1141 jlong attributePointer, jint index) { 1142 const char** attributes = toAttributes(attributePointer); 1143 const char* value = attributes[(index * 2) + 1]; 1144 return env->NewStringUTF(value); 1145 } 1146 1147 /** 1148 * Gets the index of the attribute with the given qualified name. 1149 * 1150 * @param attributePointer to the attribute array 1151 * @param qName to look for 1152 * @returns index of attribute with the given uri and local name or -1 if not 1153 * found 1154 */ 1155 static jint ExpatAttributes_getIndexForQName(JNIEnv* env, jobject, 1156 jlong attributePointer, jstring qName) { 1157 ScopedUtfChars qNameBytes(env, qName); 1158 if (qNameBytes.c_str() == NULL) { 1159 return -1; 1160 } 1161 1162 const char** attributes = toAttributes(attributePointer); 1163 int found = -1; 1164 for (int index = 0; attributes[index * 2]; ++index) { 1165 if (ExpatElementName(NULL, NULL, attributePointer, index).matchesQName(qNameBytes.c_str())) { 1166 found = index; 1167 break; 1168 } 1169 } 1170 1171 return found; 1172 } 1173 1174 /** 1175 * Gets the index of the attribute with the given URI and name. 1176 * 1177 * @param attributePointer to the attribute array 1178 * @param uri to look for 1179 * @param localName to look for 1180 * @returns index of attribute with the given uri and local name or -1 if not 1181 * found 1182 */ 1183 static jint ExpatAttributes_getIndex(JNIEnv* env, jobject, jlong attributePointer, 1184 jstring uri, jstring localName) { 1185 ScopedUtfChars uriBytes(env, uri); 1186 if (uriBytes.c_str() == NULL) { 1187 return -1; 1188 } 1189 1190 ScopedUtfChars localNameBytes(env, localName); 1191 if (localNameBytes.c_str() == NULL) { 1192 return -1; 1193 } 1194 1195 const char** attributes = toAttributes(attributePointer); 1196 for (int index = 0; attributes[index * 2]; ++index) { 1197 if (ExpatElementName(NULL, NULL, attributePointer, index).matches(uriBytes.c_str(), 1198 localNameBytes.c_str())) { 1199 return index; 1200 } 1201 } 1202 return -1; 1203 } 1204 1205 /** 1206 * Gets the value of the attribute with the given qualified name. 1207 * 1208 * @param attributePointer to the attribute array 1209 * @param uri to look for 1210 * @param localName to look for 1211 * @returns value of attribute with the given uri and local name or NULL if not 1212 * found 1213 */ 1214 static jstring ExpatAttributes_getValueForQName(JNIEnv* env, jobject clazz, 1215 jlong attributePointer, jstring qName) { 1216 jint index = ExpatAttributes_getIndexForQName(env, clazz, attributePointer, qName); 1217 return index == -1 ? NULL 1218 : ExpatAttributes_getValueByIndex(env, clazz, attributePointer, index); 1219 } 1220 1221 /** 1222 * Gets the value of the attribute with the given URI and name. 1223 * 1224 * @param attributePointer to the attribute array 1225 * @param uri to look for 1226 * @param localName to look for 1227 * @returns value of attribute with the given uri and local name or NULL if not 1228 * found 1229 */ 1230 static jstring ExpatAttributes_getValue(JNIEnv* env, jobject clazz, 1231 jlong attributePointer, jstring uri, jstring localName) { 1232 jint index = ExpatAttributes_getIndex(env, clazz, attributePointer, uri, localName); 1233 return index == -1 ? NULL 1234 : ExpatAttributes_getValueByIndex(env, clazz, attributePointer, index); 1235 } 1236 1237 /** 1238 * Clones an array of strings. Uses one contiguous block of memory so as to 1239 * maximize performance. 1240 * 1241 * @param address char** to clone 1242 * @param count number of attributes 1243 */ 1244 static jlong ExpatParser_cloneAttributes(JNIEnv* env, jobject, jlong address, jint count) { 1245 const char** source = reinterpret_cast<const char**>(static_cast<uintptr_t>(address)); 1246 count *= 2; 1247 1248 // Figure out how big the buffer needs to be. 1249 int arraySize = (count + 1) * sizeof(char*); 1250 int totalSize = arraySize; 1251 int stringLengths[count]; 1252 for (int i = 0; i < count; i++) { 1253 int length = strlen(source[i]); 1254 stringLengths[i] = length; 1255 totalSize += length + 1; 1256 } 1257 1258 char* buffer = new char[totalSize]; 1259 if (buffer == NULL) { 1260 jniThrowOutOfMemoryError(env, NULL); 1261 return 0; 1262 } 1263 1264 // Array is at the beginning of the buffer. 1265 char** clonedArray = reinterpret_cast<char**>(buffer); 1266 clonedArray[count] = NULL; // null terminate 1267 1268 // String data follows immediately after. 1269 char* destinationString = buffer + arraySize; 1270 for (int i = 0; i < count; i++) { 1271 const char* sourceString = source[i]; 1272 int stringLength = stringLengths[i]; 1273 memcpy(destinationString, sourceString, stringLength + 1); 1274 clonedArray[i] = destinationString; 1275 destinationString += stringLength + 1; 1276 } 1277 1278 return reinterpret_cast<uintptr_t>(buffer); 1279 } 1280 1281 /** 1282 * Frees cloned attributes. 1283 */ 1284 static void ExpatAttributes_freeAttributes(JNIEnv*, jobject, jlong pointer) { 1285 delete[] reinterpret_cast<char*>(static_cast<uintptr_t>(pointer)); 1286 } 1287 1288 /** 1289 * Called when we initialize our Java parser class. 1290 * 1291 * @param clazz Java ExpatParser class 1292 */ 1293 static void ExpatParser_staticInitialize(JNIEnv* env, jobject classObject, jstring empty) { 1294 jclass clazz = reinterpret_cast<jclass>(classObject); 1295 startElementMethod = env->GetMethodID(clazz, "startElement", 1296 "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;JI)V"); 1297 if (startElementMethod == NULL) return; 1298 1299 endElementMethod = env->GetMethodID(clazz, "endElement", 1300 "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V"); 1301 if (endElementMethod == NULL) return; 1302 1303 textMethod = env->GetMethodID(clazz, "text", "([CI)V"); 1304 if (textMethod == NULL) return; 1305 1306 commentMethod = env->GetMethodID(clazz, "comment", "([CI)V"); 1307 if (commentMethod == NULL) return; 1308 1309 startCdataMethod = env->GetMethodID(clazz, "startCdata", "()V"); 1310 if (startCdataMethod == NULL) return; 1311 1312 endCdataMethod = env->GetMethodID(clazz, "endCdata", "()V"); 1313 if (endCdataMethod == NULL) return; 1314 1315 startDtdMethod = env->GetMethodID(clazz, "startDtd", 1316 "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V"); 1317 if (startDtdMethod == NULL) return; 1318 1319 endDtdMethod = env->GetMethodID(clazz, "endDtd", "()V"); 1320 if (endDtdMethod == NULL) return; 1321 1322 startNamespaceMethod = env->GetMethodID(clazz, "startNamespace", 1323 "(Ljava/lang/String;Ljava/lang/String;)V"); 1324 if (startNamespaceMethod == NULL) return; 1325 1326 endNamespaceMethod = env->GetMethodID(clazz, "endNamespace", 1327 "(Ljava/lang/String;)V"); 1328 if (endNamespaceMethod == NULL) return; 1329 1330 processingInstructionMethod = env->GetMethodID(clazz, 1331 "processingInstruction", "(Ljava/lang/String;Ljava/lang/String;)V"); 1332 if (processingInstructionMethod == NULL) return; 1333 1334 handleExternalEntityMethod = env->GetMethodID(clazz, 1335 "handleExternalEntity", 1336 "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V"); 1337 if (handleExternalEntityMethod == NULL) return; 1338 1339 notationDeclMethod = env->GetMethodID(clazz, "notationDecl", 1340 "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V"); 1341 if (notationDeclMethod == NULL) return; 1342 1343 unparsedEntityDeclMethod = env->GetMethodID(clazz, "unparsedEntityDecl", 1344 "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V"); 1345 if (unparsedEntityDeclMethod == NULL) return; 1346 1347 internMethod = env->GetMethodID(JniConstants::stringClass, "intern", "()Ljava/lang/String;"); 1348 if (internMethod == NULL) return; 1349 1350 // Reference to "". 1351 emptyString = reinterpret_cast<jstring>(env->NewGlobalRef(empty)); 1352 } 1353 1354 static JNINativeMethod parserMethods[] = { 1355 NATIVE_METHOD(ExpatParser, appendString, "(JLjava/lang/String;Z)V"), 1356 NATIVE_METHOD(ExpatParser, appendBytes, "(J[BII)V"), 1357 NATIVE_METHOD(ExpatParser, appendChars, "(J[CII)V"), 1358 NATIVE_METHOD(ExpatParser, cloneAttributes, "(JI)J"), 1359 NATIVE_METHOD(ExpatParser, column, "(J)I"), 1360 NATIVE_METHOD(ExpatParser, createEntityParser, "(JLjava/lang/String;)J"), 1361 NATIVE_METHOD(ExpatParser, initialize, "(Ljava/lang/String;Z)J"), 1362 NATIVE_METHOD(ExpatParser, line, "(J)I"), 1363 NATIVE_METHOD(ExpatParser, release, "(J)V"), 1364 NATIVE_METHOD(ExpatParser, releaseParser, "(J)V"), 1365 NATIVE_METHOD(ExpatParser, staticInitialize, "(Ljava/lang/String;)V"), 1366 }; 1367 1368 static JNINativeMethod attributeMethods[] = { 1369 NATIVE_METHOD(ExpatAttributes, freeAttributes, "(J)V"), 1370 NATIVE_METHOD(ExpatAttributes, getIndexForQName, "(JLjava/lang/String;)I"), 1371 NATIVE_METHOD(ExpatAttributes, getIndex, "(JLjava/lang/String;Ljava/lang/String;)I"), 1372 NATIVE_METHOD(ExpatAttributes, getLocalName, "(JJI)Ljava/lang/String;"), 1373 NATIVE_METHOD(ExpatAttributes, getQName, "(JJI)Ljava/lang/String;"), 1374 NATIVE_METHOD(ExpatAttributes, getURI, "(JJI)Ljava/lang/String;"), 1375 NATIVE_METHOD(ExpatAttributes, getValueByIndex, "(JI)Ljava/lang/String;"), 1376 NATIVE_METHOD(ExpatAttributes, getValueForQName, "(JLjava/lang/String;)Ljava/lang/String;"), 1377 NATIVE_METHOD(ExpatAttributes, getValue, "(JLjava/lang/String;Ljava/lang/String;)Ljava/lang/String;"), 1378 }; 1379 void register_org_apache_harmony_xml_ExpatParser(JNIEnv* env) { 1380 jniRegisterNativeMethods(env, "org/apache/harmony/xml/ExpatParser", parserMethods, NELEM(parserMethods)); 1381 jniRegisterNativeMethods(env, "org/apache/harmony/xml/ExpatAttributes", attributeMethods, NELEM(attributeMethods)); 1382 } 1383