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