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