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             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  * @returns number of UTF-16 characters which were copied
    414  */
    415 static size_t fillBuffer(ParsingContext* parsingContext, const char* utf8, int byteCount) {
    416     JNIEnv* env = parsingContext->env;
    417 
    418     // Grow buffer if necessary (the length in bytes is always >= the length in chars).
    419     jcharArray javaChars = parsingContext->ensureCapacity(byteCount);
    420     if (javaChars == NULL) return -1;
    421 
    422     // Decode UTF-8 characters into our char[].
    423     ScopedCharArrayRW chars(env, javaChars);
    424     if (chars.get() == NULL) {
    425         return -1;
    426     }
    427     UErrorCode status = U_ZERO_ERROR;
    428     UnicodeString utf16(UnicodeString::fromUTF8(StringPiece(utf8, byteCount)));
    429     return utf16.extract(chars.get(), byteCount, status);
    430 }
    431 
    432 /**
    433  * Buffers the given text and passes it to the given method.
    434  *
    435  * @param method to pass the characters and length to with signature
    436  *  (char[], int)
    437  * @param data parsing context
    438  * @param text to copy into the buffer
    439  * @param length of text to copy (in bytes)
    440  */
    441 static void bufferAndInvoke(jmethodID method, void* data, const char* text, size_t length) {
    442     ParsingContext* parsingContext = toParsingContext(data);
    443     JNIEnv* env = parsingContext->env;
    444 
    445     // Bail out if a previously called handler threw an exception.
    446     if (env->ExceptionCheck()) return;
    447 
    448     // Buffer the element name.
    449     size_t utf16length = fillBuffer(parsingContext, text, length);
    450 
    451     // Invoke given method.
    452     jobject javaParser = parsingContext->object;
    453     jcharArray buffer = parsingContext->buffer;
    454     env->CallVoidMethod(javaParser, method, buffer, utf16length);
    455 }
    456 
    457 static const char** toAttributes(jint attributePointer) {
    458     return reinterpret_cast<const char**>(static_cast<uintptr_t>(attributePointer));
    459 }
    460 
    461 /**
    462  * The component parts of an attribute or element name.
    463  */
    464 class ExpatElementName {
    465 public:
    466     ExpatElementName(JNIEnv* env, ParsingContext* parsingContext, jint attributePointer, jint index) {
    467         const char** attributes = toAttributes(attributePointer);
    468         const char* name = attributes[index * 2];
    469         init(env, parsingContext, name);
    470     }
    471 
    472     ExpatElementName(JNIEnv* env, ParsingContext* parsingContext, const char* s) {
    473         init(env, parsingContext, s);
    474     }
    475 
    476     ~ExpatElementName() {
    477         free(mCopy);
    478     }
    479 
    480     /**
    481      * Returns the namespace URI, like "http://www.w3.org/1999/xhtml".
    482      * Possibly empty.
    483      */
    484     jstring uri() {
    485         return internString(mEnv, mParsingContext, mUri);
    486     }
    487 
    488     /**
    489      * Returns the element or attribute local name, like "h1". Never empty. When
    490      * namespace processing is disabled, this may contain a prefix, yielding a
    491      * local name like "html:h1". In such cases, the qName will always be empty.
    492      */
    493     jstring localName() {
    494         return internString(mEnv, mParsingContext, mLocalName);
    495     }
    496 
    497     /**
    498      * Returns the namespace prefix, like "html". Possibly empty.
    499      */
    500     jstring qName() {
    501         if (*mPrefix == 0) {
    502             return localName();
    503         }
    504 
    505         // return prefix + ":" + localName
    506         LocalArray<1024> qName(strlen(mPrefix) + 1 + strlen(mLocalName) + 1);
    507         snprintf(&qName[0], qName.size(), "%s:%s", mPrefix, mLocalName);
    508         return internString(mEnv, mParsingContext, &qName[0]);
    509     }
    510 
    511     /**
    512      * Returns true if this expat name has the same URI and local name.
    513      */
    514     bool matches(const char* uri, const char* localName) {
    515         return strcmp(uri, mUri) == 0 && strcmp(localName, mLocalName) == 0;
    516     }
    517 
    518     /**
    519      * Returns true if this expat name has the same qualified name.
    520      */
    521     bool matchesQName(const char* qName) {
    522         const char* lastColon = strrchr(qName, ':');
    523 
    524         // Compare local names only if either:
    525         //  - the input qualified name doesn't have a colon (like "h1")
    526         //  - this element doesn't have a prefix. Such is the case when it
    527         //    doesn't belong to a namespace, or when this parser's namespace
    528         //    processing is disabled. In the latter case, this element's local
    529         //    name may still contain a colon (like "html:h1").
    530         if (lastColon == NULL || *mPrefix == 0) {
    531             return strcmp(qName, mLocalName) == 0;
    532         }
    533 
    534         // Otherwise compare both prefix and local name
    535         size_t prefixLength = lastColon - qName;
    536         return strlen(mPrefix) == prefixLength
    537             && strncmp(qName, mPrefix, prefixLength) == 0
    538             && strcmp(lastColon + 1, mLocalName) == 0;
    539     }
    540 
    541 private:
    542     JNIEnv* mEnv;
    543     ParsingContext* mParsingContext;
    544     char* mCopy;
    545     const char* mUri;
    546     const char* mLocalName;
    547     const char* mPrefix;
    548 
    549     /**
    550      * Decodes an Expat-encoded name of one of these three forms:
    551      *     "uri|localName|prefix" (example: "http://www.w3.org/1999/xhtml|h1|html")
    552      *     "uri|localName" (example: "http://www.w3.org/1999/xhtml|h1")
    553      *     "localName" (example: "h1")
    554      */
    555     void init(JNIEnv* env, ParsingContext* parsingContext, const char* s) {
    556         mEnv = env;
    557         mParsingContext = parsingContext;
    558         mCopy = strdup(s);
    559 
    560         // split the input into up to 3 parts: a|b|c
    561         char* context = NULL;
    562         char* a = strtok_r(mCopy, "|", &context);
    563         char* b = strtok_r(NULL, "|", &context);
    564         char* c = strtok_r(NULL, "|", &context);
    565 
    566         if (c != NULL) { // input of the form "uri|localName|prefix"
    567             mUri = a;
    568             mLocalName = b;
    569             mPrefix = c;
    570         } else if (b != NULL) { // input of the form "uri|localName"
    571             mUri = a;
    572             mLocalName = b;
    573             mPrefix = "";
    574         } else { // input of the form "localName"
    575             mLocalName = a;
    576             mUri = "";
    577             mPrefix = "";
    578         }
    579     }
    580 
    581     // Disallow copy and assignment.
    582     ExpatElementName(const ExpatElementName&);
    583     void operator=(const ExpatElementName&);
    584 };
    585 
    586 /**
    587  * Called by Expat at the start of an element. Delegates to the same method
    588  * on the Java parser.
    589  *
    590  * @param data parsing context
    591  * @param elementName "uri|localName" or "localName" for the current element
    592  * @param attributes alternating attribute names and values. Like element
    593  * names, attribute names follow the format "uri|localName" or "localName".
    594  */
    595 static void startElement(void* data, const char* elementName, const char** attributes) {
    596     ParsingContext* parsingContext = toParsingContext(data);
    597     JNIEnv* env = parsingContext->env;
    598 
    599     // Bail out if a previously called handler threw an exception.
    600     if (env->ExceptionCheck()) return;
    601 
    602     // Count the number of attributes.
    603     int count = 0;
    604     while (attributes[count * 2]) count++;
    605 
    606     // Make the attributes available for the duration of this call.
    607     parsingContext->attributes = attributes;
    608     parsingContext->attributeCount = count;
    609 
    610     jobject javaParser = parsingContext->object;
    611 
    612     ExpatElementName e(env, parsingContext, elementName);
    613     jstring uri = parsingContext->processNamespaces ? e.uri() : emptyString;
    614     jstring localName = parsingContext->processNamespaces ? e.localName() : emptyString;
    615     jstring qName = e.qName();
    616 
    617     parsingContext->stringStack.push(env, qName);
    618     parsingContext->stringStack.push(env, uri);
    619     parsingContext->stringStack.push(env, localName);
    620 
    621     env->CallVoidMethod(javaParser, startElementMethod, uri, localName, qName, attributes, count);
    622 
    623     parsingContext->attributes = NULL;
    624     parsingContext->attributeCount = -1;
    625 }
    626 
    627 /**
    628  * Called by Expat at the end of an element. Delegates to the same method
    629  * on the Java parser.
    630  *
    631  * @param data parsing context
    632  * @param elementName "uri|localName" or "localName" for the current element;
    633  *         we assume that this matches the last data on our stack.
    634  */
    635 static void endElement(void* data, const char* /*elementName*/) {
    636     ParsingContext* parsingContext = toParsingContext(data);
    637     JNIEnv* env = parsingContext->env;
    638 
    639     // Bail out if a previously called handler threw an exception.
    640     if (env->ExceptionCheck()) return;
    641 
    642     jobject javaParser = parsingContext->object;
    643 
    644     jstring localName = parsingContext->stringStack.pop();
    645     jstring uri = parsingContext->stringStack.pop();
    646     jstring qName = parsingContext->stringStack.pop();
    647 
    648     env->CallVoidMethod(javaParser, endElementMethod, uri, localName, qName);
    649 }
    650 
    651 /**
    652  * Called by Expat when it encounters text. Delegates to the same method
    653  * on the Java parser. This may be called mutiple times with incremental pieces
    654  * of the same contiguous block of text.
    655  *
    656  * @param data parsing context
    657  * @param characters buffer containing encountered text
    658  * @param length number of characters in the buffer
    659  */
    660 static void text(void* data, const char* characters, int length) {
    661     bufferAndInvoke(textMethod, data, characters, length);
    662 }
    663 
    664 /**
    665  * Called by Expat when it encounters a comment. Delegates to the same method
    666  * on the Java parser.
    667 
    668  * @param data parsing context
    669  * @param comment 0-terminated
    670  */
    671 static void comment(void* data, const char* comment) {
    672     bufferAndInvoke(commentMethod, data, comment, strlen(comment));
    673 }
    674 
    675 /**
    676  * Called by Expat at the beginning of a namespace mapping.
    677  *
    678  * @param data parsing context
    679  * @param prefix null-terminated namespace prefix used in the XML
    680  * @param uri of the namespace
    681  */
    682 static void startNamespace(void* data, const char* prefix, const char* uri) {
    683     ParsingContext* parsingContext = toParsingContext(data);
    684     JNIEnv* env = parsingContext->env;
    685 
    686     // Bail out if a previously called handler threw an exception.
    687     if (env->ExceptionCheck()) return;
    688 
    689     jstring internedPrefix = emptyString;
    690     if (prefix != NULL) {
    691         internedPrefix = internString(env, parsingContext, prefix);
    692         if (env->ExceptionCheck()) return;
    693     }
    694 
    695     jstring internedUri = emptyString;
    696     if (uri != NULL) {
    697         internedUri = internString(env, parsingContext, uri);
    698         if (env->ExceptionCheck()) return;
    699     }
    700 
    701     parsingContext->stringStack.push(env, internedPrefix);
    702 
    703     jobject javaParser = parsingContext->object;
    704     env->CallVoidMethod(javaParser, startNamespaceMethod, internedPrefix, internedUri);
    705 }
    706 
    707 /**
    708  * Called by Expat at the end of a namespace mapping.
    709  *
    710  * @param data parsing context
    711  * @param prefix null-terminated namespace prefix used in the XML;
    712  *         we assume this is the same as the last prefix on the stack.
    713  */
    714 static void endNamespace(void* data, const char* /*prefix*/) {
    715     ParsingContext* parsingContext = toParsingContext(data);
    716     JNIEnv* env = parsingContext->env;
    717 
    718     // Bail out if a previously called handler threw an exception.
    719     if (env->ExceptionCheck()) return;
    720 
    721     jstring internedPrefix = parsingContext->stringStack.pop();
    722 
    723     jobject javaParser = parsingContext->object;
    724     env->CallVoidMethod(javaParser, endNamespaceMethod, internedPrefix);
    725 }
    726 
    727 /**
    728  * Called by Expat at the beginning of a CDATA section.
    729  *
    730  * @param data parsing context
    731  */
    732 static void startCdata(void* data) {
    733     ParsingContext* parsingContext = toParsingContext(data);
    734     JNIEnv* env = parsingContext->env;
    735 
    736     // Bail out if a previously called handler threw an exception.
    737     if (env->ExceptionCheck()) return;
    738 
    739     jobject javaParser = parsingContext->object;
    740     env->CallVoidMethod(javaParser, startCdataMethod);
    741 }
    742 
    743 /**
    744  * Called by Expat at the end of a CDATA section.
    745  *
    746  * @param data parsing context
    747  */
    748 static void endCdata(void* data) {
    749     ParsingContext* parsingContext = toParsingContext(data);
    750     JNIEnv* env = parsingContext->env;
    751 
    752     // Bail out if a previously called handler threw an exception.
    753     if (env->ExceptionCheck()) return;
    754 
    755     jobject javaParser = parsingContext->object;
    756     env->CallVoidMethod(javaParser, endCdataMethod);
    757 }
    758 
    759 /**
    760  * Called by Expat at the beginning of a DOCTYPE section.
    761  * Expat gives us 'hasInternalSubset', but the Java API doesn't expect it, so we don't need it.
    762  */
    763 static void startDtd(void* data, const char* name,
    764         const char* systemId, const char* publicId, int /*hasInternalSubset*/) {
    765     ParsingContext* parsingContext = toParsingContext(data);
    766     JNIEnv* env = parsingContext->env;
    767 
    768     // Bail out if a previously called handler threw an exception.
    769     if (env->ExceptionCheck()) return;
    770 
    771     jstring javaName = internString(env, parsingContext, name);
    772     if (env->ExceptionCheck()) return;
    773 
    774     jstring javaPublicId = internString(env, parsingContext, publicId);
    775     if (env->ExceptionCheck()) return;
    776 
    777     jstring javaSystemId = internString(env, parsingContext, systemId);
    778     if (env->ExceptionCheck()) return;
    779 
    780     jobject javaParser = parsingContext->object;
    781     env->CallVoidMethod(javaParser, startDtdMethod, javaName, javaPublicId,
    782         javaSystemId);
    783 }
    784 
    785 /**
    786  * Called by Expat at the end of a DOCTYPE section.
    787  *
    788  * @param data parsing context
    789  */
    790 static void endDtd(void* data) {
    791     ParsingContext* parsingContext = toParsingContext(data);
    792     JNIEnv* env = parsingContext->env;
    793 
    794     // Bail out if a previously called handler threw an exception.
    795     if (env->ExceptionCheck()) return;
    796 
    797     jobject javaParser = parsingContext->object;
    798     env->CallVoidMethod(javaParser, endDtdMethod);
    799 }
    800 
    801 /**
    802  * Called by Expat when it encounters processing instructions.
    803  *
    804  * @param data parsing context
    805  * @param target of the instruction
    806  * @param instructionData
    807  */
    808 static void processingInstruction(void* data, const char* target, const char* instructionData) {
    809     ParsingContext* parsingContext = toParsingContext(data);
    810     JNIEnv* env = parsingContext->env;
    811 
    812     // Bail out if a previously called handler threw an exception.
    813     if (env->ExceptionCheck()) return;
    814 
    815     jstring javaTarget = internString(env, parsingContext, target);
    816     if (env->ExceptionCheck()) return;
    817 
    818     ScopedLocalRef<jstring> javaInstructionData(env, env->NewStringUTF(instructionData));
    819     if (env->ExceptionCheck()) return;
    820 
    821     jobject javaParser = parsingContext->object;
    822     env->CallVoidMethod(javaParser, processingInstructionMethod, javaTarget, javaInstructionData.get());
    823 }
    824 
    825 /**
    826  * Creates a new entity parser.
    827  *
    828  * @param object the Java ExpatParser instance
    829  * @param parentParser pointer
    830  * @param javaEncoding the character encoding name
    831  * @param javaContext that was provided to handleExternalEntity
    832  * @returns the pointer to the C Expat entity parser
    833  */
    834 static jint ExpatParser_createEntityParser(JNIEnv* env, jobject, jint parentParser, jstring javaContext) {
    835     ScopedUtfChars context(env, javaContext);
    836     if (context.c_str() == NULL) {
    837         return 0;
    838     }
    839 
    840     XML_Parser parent = (XML_Parser) parentParser;
    841     XML_Parser entityParser = XML_ExternalEntityParserCreate(parent, context.c_str(), NULL);
    842     if (entityParser == NULL) {
    843         jniThrowOutOfMemoryError(env, NULL);
    844     }
    845 
    846     return (jint) entityParser;
    847 }
    848 
    849 /**
    850  * Handles external entities. We ignore the "base" URI and keep track of it
    851  * ourselves.
    852  */
    853 static int handleExternalEntity(XML_Parser parser, const char* context,
    854         const char*, const char* systemId, const char* publicId) {
    855     ParsingContext* parsingContext = toParsingContext(parser);
    856     jobject javaParser = parsingContext->object;
    857     JNIEnv* env = parsingContext->env;
    858     jobject object = parsingContext->object;
    859 
    860     // Bail out if a previously called handler threw an exception.
    861     if (env->ExceptionCheck()) {
    862         return XML_STATUS_ERROR;
    863     }
    864 
    865     ScopedLocalRef<jstring> javaSystemId(env, env->NewStringUTF(systemId));
    866     if (env->ExceptionCheck()) {
    867         return XML_STATUS_ERROR;
    868     }
    869     ScopedLocalRef<jstring> javaPublicId(env, env->NewStringUTF(publicId));
    870     if (env->ExceptionCheck()) {
    871         return XML_STATUS_ERROR;
    872     }
    873     ScopedLocalRef<jstring> javaContext(env, env->NewStringUTF(context));
    874     if (env->ExceptionCheck()) {
    875         return XML_STATUS_ERROR;
    876     }
    877 
    878     // Pass the wrapped parser and both strings to java.
    879     env->CallVoidMethod(javaParser, handleExternalEntityMethod, javaContext.get(),
    880             javaPublicId.get(), javaSystemId.get());
    881 
    882     /*
    883      * Parsing the external entity leaves parsingContext->env and object set to
    884      * NULL, so we need to restore both.
    885      *
    886      * TODO: consider restoring the original env and object instead of setting
    887      * them to NULL in the append() functions.
    888      */
    889     parsingContext->env = env;
    890     parsingContext->object = object;
    891 
    892     return env->ExceptionCheck() ? XML_STATUS_ERROR : XML_STATUS_OK;
    893 }
    894 
    895 /**
    896  * Expat gives us 'base', but the Java API doesn't expect it, so we don't need it.
    897  */
    898 static void unparsedEntityDecl(void* data, const char* name, const char* /*base*/, const char* systemId, const char* publicId, const char* notationName) {
    899     ParsingContext* parsingContext = toParsingContext(data);
    900     jobject javaParser = parsingContext->object;
    901     JNIEnv* env = parsingContext->env;
    902 
    903     // Bail out if a previously called handler threw an exception.
    904     if (env->ExceptionCheck()) return;
    905 
    906     ScopedLocalRef<jstring> javaName(env, env->NewStringUTF(name));
    907     if (env->ExceptionCheck()) return;
    908     ScopedLocalRef<jstring> javaPublicId(env, env->NewStringUTF(publicId));
    909     if (env->ExceptionCheck()) return;
    910     ScopedLocalRef<jstring> javaSystemId(env, env->NewStringUTF(systemId));
    911     if (env->ExceptionCheck()) return;
    912     ScopedLocalRef<jstring> javaNotationName(env, env->NewStringUTF(notationName));
    913     if (env->ExceptionCheck()) return;
    914 
    915     env->CallVoidMethod(javaParser, unparsedEntityDeclMethod, javaName.get(), javaPublicId.get(), javaSystemId.get(), javaNotationName.get());
    916 }
    917 
    918 /**
    919  * Expat gives us 'base', but the Java API doesn't expect it, so we don't need it.
    920  */
    921 static void notationDecl(void* data, const char* name, const char* /*base*/, const char* systemId, const char* publicId) {
    922     ParsingContext* parsingContext = toParsingContext(data);
    923     jobject javaParser = parsingContext->object;
    924     JNIEnv* env = parsingContext->env;
    925 
    926     // Bail out if a previously called handler threw an exception.
    927     if (env->ExceptionCheck()) return;
    928 
    929     ScopedLocalRef<jstring> javaName(env, env->NewStringUTF(name));
    930     if (env->ExceptionCheck()) return;
    931     ScopedLocalRef<jstring> javaPublicId(env, env->NewStringUTF(publicId));
    932     if (env->ExceptionCheck()) return;
    933     ScopedLocalRef<jstring> javaSystemId(env, env->NewStringUTF(systemId));
    934     if (env->ExceptionCheck()) return;
    935 
    936     env->CallVoidMethod(javaParser, notationDeclMethod, javaName.get(), javaPublicId.get(), javaSystemId.get());
    937 }
    938 
    939 /**
    940  * Creates a new Expat parser. Called from the Java ExpatParser constructor.
    941  *
    942  * @param object the Java ExpatParser instance
    943  * @param javaEncoding the character encoding name
    944  * @param processNamespaces true if the parser should handle namespaces
    945  * @returns the pointer to the C Expat parser
    946  */
    947 static jint ExpatParser_initialize(JNIEnv* env, jobject object, jstring javaEncoding,
    948         jboolean processNamespaces) {
    949     // Allocate parsing context.
    950     UniquePtr<ParsingContext> context(new ParsingContext(object));
    951     if (context.get() == NULL) {
    952         jniThrowOutOfMemoryError(env, NULL);
    953         return 0;
    954     }
    955 
    956     context->processNamespaces = (bool) processNamespaces;
    957 
    958     // Create a parser.
    959     XML_Parser parser;
    960     ScopedUtfChars encoding(env, javaEncoding);
    961     if (encoding.c_str() == NULL) {
    962         return 0;
    963     }
    964     if (processNamespaces) {
    965         // Use '|' to separate URIs from local names.
    966         parser = XML_ParserCreateNS(encoding.c_str(), '|');
    967     } else {
    968         parser = XML_ParserCreate(encoding.c_str());
    969     }
    970 
    971     if (parser != NULL) {
    972         if (processNamespaces) {
    973             XML_SetNamespaceDeclHandler(parser, startNamespace, endNamespace);
    974             XML_SetReturnNSTriplet(parser, 1);
    975         }
    976 
    977         XML_SetCdataSectionHandler(parser, startCdata, endCdata);
    978         XML_SetCharacterDataHandler(parser, text);
    979         XML_SetCommentHandler(parser, comment);
    980         XML_SetDoctypeDeclHandler(parser, startDtd, endDtd);
    981         XML_SetElementHandler(parser, startElement, endElement);
    982         XML_SetExternalEntityRefHandler(parser, handleExternalEntity);
    983         XML_SetNotationDeclHandler(parser, notationDecl);
    984         XML_SetProcessingInstructionHandler(parser, processingInstruction);
    985         XML_SetUnparsedEntityDeclHandler(parser, unparsedEntityDecl);
    986         XML_SetUserData(parser, context.release());
    987     } else {
    988         jniThrowOutOfMemoryError(env, NULL);
    989         return 0;
    990     }
    991 
    992     return (jint) parser;
    993 }
    994 
    995 /**
    996  * Decodes the bytes as characters and parse the characters as XML. This
    997  * performs character decoding using the charset specified at XML_Parser
    998  * creation. For Java chars, that charset must be UTF-16 so that a Java char[]
    999  * can be reinterpreted as a UTF-16 encoded byte[]. appendBytes, appendChars
   1000  * and appendString all call through this method.
   1001  */
   1002 static void append(JNIEnv* env, jobject object, jint pointer,
   1003         const char* bytes, size_t byteOffset, size_t byteCount, jboolean isFinal) {
   1004     XML_Parser parser = (XML_Parser) pointer;
   1005     ParsingContext* context = toParsingContext(parser);
   1006     context->env = env;
   1007     context->object = object;
   1008     if (!XML_Parse(parser, bytes + byteOffset, byteCount, isFinal) && !env->ExceptionCheck()) {
   1009         jniThrowExpatException(env, XML_GetErrorCode(parser));
   1010     }
   1011     context->object = NULL;
   1012     context->env = NULL;
   1013 }
   1014 
   1015 static void ExpatParser_appendBytes(JNIEnv* env, jobject object, jint pointer,
   1016         jbyteArray xml, jint byteOffset, jint byteCount) {
   1017     ScopedByteArrayRO byteArray(env, xml);
   1018     if (byteArray.get() == NULL) {
   1019         return;
   1020     }
   1021 
   1022     const char* bytes = reinterpret_cast<const char*>(byteArray.get());
   1023     append(env, object, pointer, bytes, byteOffset, byteCount, XML_FALSE);
   1024 }
   1025 
   1026 static void ExpatParser_appendChars(JNIEnv* env, jobject object, jint pointer,
   1027         jcharArray xml, jint charOffset, jint charCount) {
   1028     ScopedCharArrayRO charArray(env, xml);
   1029     if (charArray.get() == NULL) {
   1030         return;
   1031     }
   1032 
   1033     const char* bytes = reinterpret_cast<const char*>(charArray.get());
   1034     size_t byteOffset = 2 * charOffset;
   1035     size_t byteCount = 2 * charCount;
   1036     append(env, object, pointer, bytes, byteOffset, byteCount, XML_FALSE);
   1037 }
   1038 
   1039 static void ExpatParser_appendString(JNIEnv* env, jobject object, jint pointer, jstring javaXml, jboolean isFinal) {
   1040     ScopedStringChars xml(env, javaXml);
   1041     if (xml.get() == NULL) {
   1042         return;
   1043     }
   1044     const char* bytes = reinterpret_cast<const char*>(xml.get());
   1045     size_t byteCount = 2 * xml.size();
   1046     append(env, object, pointer, bytes, 0, byteCount, isFinal);
   1047 }
   1048 
   1049 /**
   1050  * Releases parser only.
   1051  *
   1052  * @param object the Java ExpatParser instance
   1053  * @param i pointer to the C expat parser
   1054  */
   1055 static void ExpatParser_releaseParser(JNIEnv*, jobject, jint i) {
   1056     XML_Parser parser = (XML_Parser) i;
   1057     XML_ParserFree(parser);
   1058 }
   1059 
   1060 /**
   1061  * Cleans up after the parser. Called at garbage collection time.
   1062  *
   1063  * @param object the Java ExpatParser instance
   1064  * @param i pointer to the C expat parser
   1065  */
   1066 static void ExpatParser_release(JNIEnv* env, jobject, jint i) {
   1067     XML_Parser parser = (XML_Parser) i;
   1068 
   1069     ParsingContext* context = toParsingContext(parser);
   1070     context->env = env;
   1071     delete context;
   1072 
   1073     XML_ParserFree(parser);
   1074 }
   1075 
   1076 /**
   1077  * Gets the current line.
   1078  *
   1079  * @param object the Java ExpatParser instance
   1080  * @param pointer to the C expat parser
   1081  * @returns current line number
   1082  */
   1083 static int ExpatParser_line(JNIEnv*, jobject, jint pointer) {
   1084     XML_Parser parser = (XML_Parser) pointer;
   1085     return XML_GetCurrentLineNumber(parser);
   1086 }
   1087 
   1088 /**
   1089  * Gets the current column.
   1090  *
   1091  * @param object the Java ExpatParser instance
   1092  * @param pointer to the C expat parser
   1093  * @returns current column number
   1094  */
   1095 static int ExpatParser_column(JNIEnv*, jobject, jint pointer) {
   1096     XML_Parser parser = (XML_Parser) pointer;
   1097     return XML_GetCurrentColumnNumber(parser);
   1098 }
   1099 
   1100 /**
   1101  * Gets the URI of the attribute at the given index.
   1102  *
   1103  * @param object Java ExpatParser instance
   1104  * @param pointer to the C expat parser
   1105  * @param attributePointer to the attribute array
   1106  * @param index of the attribute
   1107  * @returns interned Java string containing attribute's URI
   1108  */
   1109 static jstring ExpatAttributes_getURI(JNIEnv* env, jobject, jint pointer,
   1110         jint attributePointer, jint index) {
   1111     XML_Parser parser = (XML_Parser) pointer;
   1112     ParsingContext* context = toParsingContext(parser);
   1113     return ExpatElementName(env, context, attributePointer, index).uri();
   1114 }
   1115 
   1116 /**
   1117  * Gets the local name of the attribute at the given index.
   1118  *
   1119  * @param object Java ExpatParser instance
   1120  * @param pointer to the C expat parser
   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_getLocalName(JNIEnv* env, jobject, jint pointer,
   1126         jint attributePointer, jint index) {
   1127     XML_Parser parser = (XML_Parser) pointer;
   1128     ParsingContext* context = toParsingContext(parser);
   1129     return ExpatElementName(env, context, attributePointer, index).localName();
   1130 }
   1131 
   1132 /**
   1133  * Gets the qualified name of the attribute at the given index.
   1134  *
   1135  * @param object Java ExpatParser instance
   1136  * @param pointer to the C expat parser
   1137  * @param attributePointer to the attribute array
   1138  * @param index of the attribute
   1139  * @returns interned Java string containing attribute's local name
   1140  */
   1141 static jstring ExpatAttributes_getQName(JNIEnv* env, jobject, jint pointer,
   1142         jint attributePointer, jint index) {
   1143     XML_Parser parser = (XML_Parser) pointer;
   1144     ParsingContext* context = toParsingContext(parser);
   1145     return ExpatElementName(env, context, attributePointer, index).qName();
   1146 }
   1147 
   1148 /**
   1149  * Gets the value of the attribute at the given index.
   1150  *
   1151  * @param object Java ExpatParser instance
   1152  * @param attributePointer to the attribute array
   1153  * @param index of the attribute
   1154  * @returns Java string containing attribute's value
   1155  */
   1156 static jstring ExpatAttributes_getValueByIndex(JNIEnv* env, jobject,
   1157         jint attributePointer, jint index) {
   1158     const char** attributes = toAttributes(attributePointer);
   1159     const char* value = attributes[(index * 2) + 1];
   1160     return env->NewStringUTF(value);
   1161 }
   1162 
   1163 /**
   1164  * Gets the index of the attribute with the given qualified name.
   1165  *
   1166  * @param attributePointer to the attribute array
   1167  * @param qName to look for
   1168  * @returns index of attribute with the given uri and local name or -1 if not
   1169  *  found
   1170  */
   1171 static jint ExpatAttributes_getIndexForQName(JNIEnv* env, jobject,
   1172         jint attributePointer, jstring qName) {
   1173     ScopedUtfChars qNameBytes(env, qName);
   1174     if (qNameBytes.c_str() == NULL) {
   1175         return -1;
   1176     }
   1177 
   1178     const char** attributes = toAttributes(attributePointer);
   1179     int found = -1;
   1180     for (int index = 0; attributes[index * 2]; ++index) {
   1181         if (ExpatElementName(NULL, NULL, attributePointer, index).matchesQName(qNameBytes.c_str())) {
   1182             found = index;
   1183             break;
   1184         }
   1185     }
   1186 
   1187     return found;
   1188 }
   1189 
   1190 /**
   1191  * Gets the index of the attribute with the given URI and name.
   1192  *
   1193  * @param attributePointer to the attribute array
   1194  * @param uri to look for
   1195  * @param localName to look for
   1196  * @returns index of attribute with the given uri and local name or -1 if not
   1197  *  found
   1198  */
   1199 static jint ExpatAttributes_getIndex(JNIEnv* env, jobject, jint attributePointer,
   1200         jstring uri, jstring localName) {
   1201     ScopedUtfChars uriBytes(env, uri);
   1202     if (uriBytes.c_str() == NULL) {
   1203         return -1;
   1204     }
   1205 
   1206     ScopedUtfChars localNameBytes(env, localName);
   1207     if (localNameBytes.c_str() == NULL) {
   1208         return -1;
   1209     }
   1210 
   1211     const char** attributes = toAttributes(attributePointer);
   1212     for (int index = 0; attributes[index * 2]; ++index) {
   1213         if (ExpatElementName(NULL, NULL, attributePointer, index).matches(uriBytes.c_str(),
   1214                 localNameBytes.c_str())) {
   1215             return index;
   1216         }
   1217     }
   1218     return -1;
   1219 }
   1220 
   1221 /**
   1222  * Gets the value of the attribute with the given qualified name.
   1223  *
   1224  * @param attributePointer to the attribute array
   1225  * @param uri to look for
   1226  * @param localName to look for
   1227  * @returns value of attribute with the given uri and local name or NULL if not
   1228  *  found
   1229  */
   1230 static jstring ExpatAttributes_getValueForQName(JNIEnv* env, jobject clazz,
   1231         jint attributePointer, jstring qName) {
   1232     jint index = ExpatAttributes_getIndexForQName(env, clazz, attributePointer, qName);
   1233     return index == -1 ? NULL
   1234             : ExpatAttributes_getValueByIndex(env, clazz, attributePointer, index);
   1235 }
   1236 
   1237 /**
   1238  * Gets the value of the attribute with the given URI and name.
   1239  *
   1240  * @param attributePointer to the attribute array
   1241  * @param uri to look for
   1242  * @param localName to look for
   1243  * @returns value of attribute with the given uri and local name or NULL if not
   1244  *  found
   1245  */
   1246 static jstring ExpatAttributes_getValue(JNIEnv* env, jobject clazz,
   1247         jint attributePointer, jstring uri, jstring localName) {
   1248     jint index = ExpatAttributes_getIndex(env, clazz, attributePointer, uri, localName);
   1249     return index == -1 ? NULL
   1250             : ExpatAttributes_getValueByIndex(env, clazz, attributePointer, index);
   1251 }
   1252 
   1253 /**
   1254  * Clones an array of strings. Uses one contiguous block of memory so as to
   1255  * maximize performance.
   1256  *
   1257  * @param address char** to clone
   1258  * @param count number of attributes
   1259  */
   1260 static jint ExpatParser_cloneAttributes(JNIEnv* env, jobject, jint address, jint count) {
   1261     const char** source = reinterpret_cast<const char**>(static_cast<uintptr_t>(address));
   1262     count *= 2;
   1263 
   1264     // Figure out how big the buffer needs to be.
   1265     int arraySize = (count + 1) * sizeof(char*);
   1266     int totalSize = arraySize;
   1267     int stringLengths[count];
   1268     for (int i = 0; i < count; i++) {
   1269         int length = strlen(source[i]);
   1270         stringLengths[i] = length;
   1271         totalSize += length + 1;
   1272     }
   1273 
   1274     char* buffer = new char[totalSize];
   1275     if (buffer == NULL) {
   1276         jniThrowOutOfMemoryError(env, NULL);
   1277         return 0;
   1278     }
   1279 
   1280     // Array is at the beginning of the buffer.
   1281     char** clonedArray = reinterpret_cast<char**>(buffer);
   1282     clonedArray[count] = NULL; // null terminate
   1283 
   1284     // String data follows immediately after.
   1285     char* destinationString = buffer + arraySize;
   1286     for (int i = 0; i < count; i++) {
   1287         const char* sourceString = source[i];
   1288         int stringLength = stringLengths[i];
   1289         memcpy(destinationString, sourceString, stringLength + 1);
   1290         clonedArray[i] = destinationString;
   1291         destinationString += stringLength + 1;
   1292     }
   1293 
   1294     return static_cast<jint>(reinterpret_cast<uintptr_t>(buffer));
   1295 }
   1296 
   1297 /**
   1298  * Frees cloned attributes.
   1299  */
   1300 static void ExpatAttributes_freeAttributes(JNIEnv*, jobject, jint pointer) {
   1301     delete[] reinterpret_cast<char*>(static_cast<uintptr_t>(pointer));
   1302 }
   1303 
   1304 /**
   1305  * Called when we initialize our Java parser class.
   1306  *
   1307  * @param clazz Java ExpatParser class
   1308  */
   1309 static void ExpatParser_staticInitialize(JNIEnv* env, jobject classObject, jstring empty) {
   1310     jclass clazz = reinterpret_cast<jclass>(classObject);
   1311     startElementMethod = env->GetMethodID(clazz, "startElement",
   1312         "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;II)V");
   1313     if (startElementMethod == NULL) return;
   1314 
   1315     endElementMethod = env->GetMethodID(clazz, "endElement",
   1316         "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
   1317     if (endElementMethod == NULL) return;
   1318 
   1319     textMethod = env->GetMethodID(clazz, "text", "([CI)V");
   1320     if (textMethod == NULL) return;
   1321 
   1322     commentMethod = env->GetMethodID(clazz, "comment", "([CI)V");
   1323     if (commentMethod == NULL) return;
   1324 
   1325     startCdataMethod = env->GetMethodID(clazz, "startCdata", "()V");
   1326     if (startCdataMethod == NULL) return;
   1327 
   1328     endCdataMethod = env->GetMethodID(clazz, "endCdata", "()V");
   1329     if (endCdataMethod == NULL) return;
   1330 
   1331     startDtdMethod = env->GetMethodID(clazz, "startDtd",
   1332         "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
   1333     if (startDtdMethod == NULL) return;
   1334 
   1335     endDtdMethod = env->GetMethodID(clazz, "endDtd", "()V");
   1336     if (endDtdMethod == NULL) return;
   1337 
   1338     startNamespaceMethod = env->GetMethodID(clazz, "startNamespace",
   1339         "(Ljava/lang/String;Ljava/lang/String;)V");
   1340     if (startNamespaceMethod == NULL) return;
   1341 
   1342     endNamespaceMethod = env->GetMethodID(clazz, "endNamespace",
   1343         "(Ljava/lang/String;)V");
   1344     if (endNamespaceMethod == NULL) return;
   1345 
   1346     processingInstructionMethod = env->GetMethodID(clazz,
   1347         "processingInstruction", "(Ljava/lang/String;Ljava/lang/String;)V");
   1348     if (processingInstructionMethod == NULL) return;
   1349 
   1350     handleExternalEntityMethod = env->GetMethodID(clazz,
   1351         "handleExternalEntity",
   1352         "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
   1353     if (handleExternalEntityMethod == NULL) return;
   1354 
   1355     notationDeclMethod = env->GetMethodID(clazz, "notationDecl",
   1356             "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
   1357     if (notationDeclMethod == NULL) return;
   1358 
   1359     unparsedEntityDeclMethod = env->GetMethodID(clazz, "unparsedEntityDecl",
   1360             "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
   1361     if (unparsedEntityDeclMethod == NULL) return;
   1362 
   1363     internMethod = env->GetMethodID(JniConstants::stringClass, "intern", "()Ljava/lang/String;");
   1364     if (internMethod == NULL) return;
   1365 
   1366     // Reference to "".
   1367     emptyString = (jstring) env->NewGlobalRef(empty);
   1368 }
   1369 
   1370 static JNINativeMethod parserMethods[] = {
   1371     NATIVE_METHOD(ExpatParser, appendString, "(ILjava/lang/String;Z)V"),
   1372     NATIVE_METHOD(ExpatParser, appendBytes, "(I[BII)V"),
   1373     NATIVE_METHOD(ExpatParser, appendChars, "(I[CII)V"),
   1374     NATIVE_METHOD(ExpatParser, cloneAttributes, "(II)I"),
   1375     NATIVE_METHOD(ExpatParser, column, "(I)I"),
   1376     NATIVE_METHOD(ExpatParser, createEntityParser, "(ILjava/lang/String;)I"),
   1377     NATIVE_METHOD(ExpatParser, initialize, "(Ljava/lang/String;Z)I"),
   1378     NATIVE_METHOD(ExpatParser, line, "(I)I"),
   1379     NATIVE_METHOD(ExpatParser, release, "(I)V"),
   1380     NATIVE_METHOD(ExpatParser, releaseParser, "(I)V"),
   1381     NATIVE_METHOD(ExpatParser, staticInitialize, "(Ljava/lang/String;)V"),
   1382 };
   1383 
   1384 static JNINativeMethod attributeMethods[] = {
   1385     NATIVE_METHOD(ExpatAttributes, freeAttributes, "(I)V"),
   1386     NATIVE_METHOD(ExpatAttributes, getIndexForQName, "(ILjava/lang/String;)I"),
   1387     NATIVE_METHOD(ExpatAttributes, getIndex, "(ILjava/lang/String;Ljava/lang/String;)I"),
   1388     NATIVE_METHOD(ExpatAttributes, getLocalName, "(III)Ljava/lang/String;"),
   1389     NATIVE_METHOD(ExpatAttributes, getQName, "(III)Ljava/lang/String;"),
   1390     NATIVE_METHOD(ExpatAttributes, getURI, "(III)Ljava/lang/String;"),
   1391     NATIVE_METHOD(ExpatAttributes, getValueByIndex, "(II)Ljava/lang/String;"),
   1392     NATIVE_METHOD(ExpatAttributes, getValueForQName, "(ILjava/lang/String;)Ljava/lang/String;"),
   1393     NATIVE_METHOD(ExpatAttributes, getValue, "(ILjava/lang/String;Ljava/lang/String;)Ljava/lang/String;"),
   1394 };
   1395 void register_org_apache_harmony_xml_ExpatParser(JNIEnv* env) {
   1396     jniRegisterNativeMethods(env, "org/apache/harmony/xml/ExpatParser", parserMethods, NELEM(parserMethods));
   1397     jniRegisterNativeMethods(env, "org/apache/harmony/xml/ExpatAttributes", attributeMethods, NELEM(attributeMethods));
   1398 }
   1399