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