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 "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