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 "LocalArray.h"
     22 #include "ScopedJavaUnicodeString.h"
     23 #include "ScopedLocalRef.h"
     24 #include "ScopedPrimitiveArray.h"
     25 #include "ScopedUtfChars.h"
     26 #include "UniquePtr.h"
     27 #include "jni.h"
     28 #include "utils/Log.h"
     29 
     30 #include <string.h>
     31 #include <utils/misc.h>
     32 #include <expat.h>
     33 #include <cutils/jstring.h>
     34 
     35 #define BUCKET_COUNT 128
     36 
     37 static void throw_OutOfMemoryError(JNIEnv* env) {
     38     jniThrowException(env, "java/lang/OutOfMemoryError", "Out of memory.");
     39 }
     40 
     41 /**
     42  * Wrapper around an interned string.
     43  */
     44 struct InternedString {
     45     InternedString() : interned(NULL), bytes(NULL) {
     46     }
     47 
     48     ~InternedString() {
     49         delete[] bytes;
     50     }
     51 
     52     /** The interned string itself. */
     53     jstring interned;
     54 
     55     /** UTF-8 equivalent of the interned string. */
     56     const char* bytes;
     57 
     58     /** Hash code of the interned string. */
     59     int hash;
     60 };
     61 
     62 /**
     63  * Keeps track of strings between start and end events.
     64  */
     65 class StringStack {
     66 public:
     67     StringStack() : array(new jstring[DEFAULT_CAPACITY]), capacity(DEFAULT_CAPACITY), size(0) {
     68     }
     69 
     70     ~StringStack() {
     71         delete[] array;
     72     }
     73 
     74     void push(JNIEnv* env, jstring s) {
     75         if (size == capacity) {
     76             int newCapacity = capacity * 2;
     77             jstring* newArray = new jstring[newCapacity];
     78             if (newArray == NULL) {
     79                 throw_OutOfMemoryError(env);
     80                 return;
     81             }
     82             memcpy(newArray, array, capacity * sizeof(jstring));
     83 
     84             array = newArray;
     85             capacity = newCapacity;
     86         }
     87 
     88         array[size++] = s;
     89     }
     90 
     91     jstring pop() {
     92         return (size == 0) ? NULL : array[--size];
     93     }
     94 
     95 private:
     96     enum { DEFAULT_CAPACITY = 10 };
     97 
     98     jstring* array;
     99     int capacity;
    100     int size;
    101 };
    102 
    103 /**
    104  * Data passed to parser handler method by the parser.
    105  */
    106 struct ParsingContext {
    107     ParsingContext(jobject object) : env(NULL), object(object), buffer(NULL), bufferSize(-1) {
    108         for (int i = 0; i < BUCKET_COUNT; i++) {
    109             internedStrings[i] = NULL;
    110         }
    111     }
    112 
    113     // Warning: 'env' must be valid on entry.
    114     ~ParsingContext() {
    115         freeBuffer();
    116 
    117         // Free interned string cache.
    118         for (int i = 0; i < BUCKET_COUNT; i++) {
    119             if (internedStrings[i]) {
    120                 InternedString** bucket = internedStrings[i];
    121                 InternedString* current;
    122                 while ((current = *(bucket++)) != NULL) {
    123                     // Free the interned string reference.
    124                     env->DeleteGlobalRef(current->interned);
    125 
    126                     // Free the bucket.
    127                     delete current;
    128                 }
    129 
    130                 // Free the buckets.
    131                 delete[] internedStrings[i];
    132             }
    133         }
    134     }
    135 
    136     jcharArray ensureCapacity(int length) {
    137         if (bufferSize < length) {
    138             // Free the existing char[].
    139             freeBuffer();
    140 
    141             // Allocate a new char[].
    142             jcharArray javaBuffer = env->NewCharArray(length);
    143             if (javaBuffer == NULL) return NULL;
    144 
    145             // Create a global reference.
    146             javaBuffer = (jcharArray) env->NewGlobalRef(javaBuffer);
    147             if (javaBuffer == NULL) return NULL;
    148 
    149             buffer = javaBuffer;
    150             bufferSize = length;
    151         }
    152         return buffer;
    153     }
    154 
    155 private:
    156     void freeBuffer() {
    157         if (buffer != NULL) {
    158             env->DeleteGlobalRef(buffer);
    159             buffer = NULL;
    160             bufferSize = -1;
    161         }
    162     }
    163 
    164 public:
    165     /**
    166      * The JNI environment for the current thread. This should only be used
    167      * to keep a reference to the env for use in Expat callbacks.
    168      */
    169     JNIEnv* env;
    170 
    171     /** The Java parser object. */
    172     jobject object;
    173 
    174     /** Buffer for text events. */
    175     jcharArray buffer;
    176 
    177 private:
    178     /** The size of our buffer in jchars. */
    179     int bufferSize;
    180 
    181 public:
    182     /** Current attributes. */
    183     const char** attributes;
    184 
    185     /** Number of attributes. */
    186     int attributeCount;
    187 
    188     /** True if namespace support is enabled. */
    189     bool processNamespaces;
    190 
    191     /** Keep track of names. */
    192     StringStack stringStack;
    193 
    194     /** Cache of interned strings. */
    195     InternedString** internedStrings[BUCKET_COUNT];
    196 };
    197 
    198 static ParsingContext* toParsingContext(void* data) {
    199     return reinterpret_cast<ParsingContext*>(data);
    200 }
    201 
    202 static ParsingContext* toParsingContext(XML_Parser parser) {
    203     return reinterpret_cast<ParsingContext*>(XML_GetUserData(parser));
    204 }
    205 
    206 static jmethodID commentMethod;
    207 static jmethodID endCdataMethod;
    208 static jmethodID endDtdMethod;
    209 static jmethodID endElementMethod;
    210 static jmethodID endNamespaceMethod;
    211 static jmethodID handleExternalEntityMethod;
    212 static jmethodID internMethod;
    213 static jmethodID notationDeclMethod;
    214 static jmethodID processingInstructionMethod;
    215 static jmethodID startCdataMethod;
    216 static jmethodID startDtdMethod;
    217 static jmethodID startElementMethod;
    218 static jmethodID startNamespaceMethod;
    219 static jmethodID textMethod;
    220 static jmethodID unparsedEntityDeclMethod;
    221 static jstring emptyString;
    222 
    223 /**
    224  * Calculates a hash code for a null-terminated string. This is *not* equivalent
    225  * to Java's String.hashCode(). This hashes the bytes while String.hashCode()
    226  * hashes UTF-16 chars.
    227  *
    228  * @param s null-terminated string to hash
    229  * @returns hash code
    230  */
    231 static int hashString(const char* s) {
    232     int hash = 0;
    233     if (s) {
    234         while (*s) {
    235             hash = hash * 31 + *s++;
    236         }
    237     }
    238     return hash;
    239 }
    240 
    241 /**
    242  * Creates a new interned string wrapper. Looks up the interned string
    243  * representing the given UTF-8 bytes.
    244  *
    245  * @param bytes null-terminated string to intern
    246  * @param hash of bytes
    247  * @returns wrapper of interned Java string
    248  */
    249 static InternedString* newInternedString(JNIEnv* env, const char* bytes, int hash) {
    250     // Allocate a new wrapper.
    251     UniquePtr<InternedString> wrapper(new InternedString);
    252     if (wrapper.get() == NULL) {
    253         throw_OutOfMemoryError(env);
    254         return NULL;
    255     }
    256 
    257     // Create a copy of the UTF-8 bytes.
    258     // TODO: sometimes we already know the length. Reuse it if so.
    259     char* copy = new char[strlen(bytes) + 1];
    260     if (copy == NULL) {
    261         throw_OutOfMemoryError(env);
    262         return NULL;
    263     }
    264     strcpy(copy, bytes);
    265     wrapper->bytes = copy;
    266 
    267     // Save the hash.
    268     wrapper->hash = hash;
    269 
    270     // To intern a string, we must first create a new string and then call
    271     // intern() on it. We then keep a global reference to the interned string.
    272     ScopedLocalRef<jstring> newString(env, env->NewStringUTF(bytes));
    273     if (env->ExceptionCheck()) {
    274         return NULL;
    275     }
    276 
    277     // Call intern().
    278     ScopedLocalRef<jstring> interned(env,
    279             reinterpret_cast<jstring>(env->CallObjectMethod(newString.get(), internMethod)));
    280     if (env->ExceptionCheck()) {
    281         return NULL;
    282     }
    283 
    284     // Create a global reference to the interned string.
    285     wrapper->interned = (jstring) env->NewGlobalRef(interned.get());
    286     if (env->ExceptionCheck()) {
    287         return NULL;
    288     }
    289 
    290     return wrapper.release();
    291 }
    292 
    293 /**
    294  * Allocates a new bucket with one entry.
    295  *
    296  * @param entry to store in the bucket
    297  * @returns a reference to the bucket
    298  */
    299 static InternedString** newInternedStringBucket(InternedString* entry) {
    300     InternedString** bucket = new InternedString*[2];
    301     if (bucket != NULL) {
    302         bucket[0] = entry;
    303         bucket[1] = NULL;
    304     }
    305     return bucket;
    306 }
    307 
    308 /**
    309  * Expands an interned string bucket and adds the given entry. Frees the
    310  * provided bucket and returns a new one.
    311  *
    312  * @param existingBucket the bucket to replace
    313  * @param entry to add to the bucket
    314  * @returns a reference to the newly-allocated bucket containing the given entry
    315  */
    316 static InternedString** expandInternedStringBucket(
    317         InternedString** existingBucket, InternedString* entry) {
    318     // Determine the size of the existing bucket.
    319     int size = 0;
    320     while (existingBucket[size]) size++;
    321 
    322     // Allocate the new bucket with enough space for one more entry and
    323     // a null terminator.
    324     InternedString** newBucket = new InternedString*[size + 2];
    325     if (newBucket == NULL) return NULL;
    326 
    327     memcpy(newBucket, existingBucket, size * sizeof(InternedString*));
    328     newBucket[size] = entry;
    329     newBucket[size + 1] = NULL;
    330 
    331     return newBucket;
    332 }
    333 
    334 /**
    335  * Returns an interned string for the given UTF-8 string.
    336  *
    337  * @param bucket to search for s
    338  * @param s null-terminated string to find
    339  * @param hash of s
    340  * @returns interned Java string equivalent of s or null if not found
    341  */
    342 static jstring findInternedString(InternedString** bucket, const char* s, int hash) {
    343     InternedString* current;
    344     while ((current = *(bucket++)) != NULL) {
    345         if (current->hash != hash) continue;
    346         if (!strcmp(s, current->bytes)) return current->interned;
    347     }
    348     return NULL;
    349 }
    350 
    351 /**
    352  * Returns an interned string for the given UTF-8 string.
    353  *
    354  * @param s null-terminated string to intern
    355  * @returns interned Java string equivelent of s or NULL if s is null
    356  */
    357 static jstring internString(JNIEnv* env, ParsingContext* parsingContext, const char* s) {
    358     if (s == NULL) return NULL;
    359 
    360     int hash = hashString(s);
    361     int bucketIndex = hash & (BUCKET_COUNT - 1);
    362 
    363     InternedString*** buckets = parsingContext->internedStrings;
    364     InternedString** bucket = buckets[bucketIndex];
    365     InternedString* internedString;
    366 
    367     if (bucket) {
    368         // We have a bucket already. Look for the given string.
    369         jstring found = findInternedString(bucket, s, hash);
    370         if (found) {
    371             // We found it!
    372             return found;
    373         }
    374 
    375         // We didn't find it. :(
    376         // Create a new entry.
    377         internedString = newInternedString(env, s, hash);
    378         if (internedString == NULL) return NULL;
    379 
    380         // Expand the bucket.
    381         bucket = expandInternedStringBucket(bucket, internedString);
    382         if (bucket == NULL) {
    383             throw_OutOfMemoryError(env);
    384             return NULL;
    385         }
    386 
    387         buckets[bucketIndex] = bucket;
    388 
    389         return internedString->interned;
    390     } else {
    391         // We don't even have a bucket yet. Create an entry.
    392         internedString = newInternedString(env, s, hash);
    393         if (internedString == NULL) return NULL;
    394 
    395         // Create a new bucket with one entry.
    396         bucket = newInternedStringBucket(internedString);
    397         if (bucket == NULL) {
    398             throw_OutOfMemoryError(env);
    399             return NULL;
    400         }
    401 
    402         buckets[bucketIndex] = bucket;
    403 
    404         return internedString->interned;
    405     }
    406 }
    407 
    408 static void jniThrowExpatException(JNIEnv* env, XML_Error error) {
    409     const char* message = XML_ErrorString(error);
    410     jniThrowException(env, "org/apache/harmony/xml/ExpatException", message);
    411 }
    412 
    413 /**
    414  * Copies UTF-8 characters into the buffer. Returns the number of Java chars
    415  * which were buffered.
    416  *
    417  * @param characters to copy into the buffer
    418  * @param length of characters to copy (in bytes)
    419  * @returns number of UTF-16 characters which were copied
    420  */
    421 static size_t fillBuffer(ParsingContext* parsingContext, const char* characters, int length) {
    422     JNIEnv* env = parsingContext->env;
    423 
    424     // Grow buffer if necessary.
    425     jcharArray buffer = parsingContext->ensureCapacity(length);
    426     if (buffer == NULL) return -1;
    427 
    428     // Decode UTF-8 characters into our buffer.
    429     ScopedCharArrayRW nativeBuffer(env, buffer);
    430     if (nativeBuffer.get() == NULL) {
    431         return -1;
    432     }
    433 
    434     size_t utf16length;
    435     strcpylen8to16(nativeBuffer.get(), characters, length, &utf16length);
    436     return utf16length;
    437 }
    438 
    439 /**
    440  * Buffers the given text and passes it to the given method.
    441  *
    442  * @param method to pass the characters and length to with signature
    443  *  (char[], int)
    444  * @param data parsing context
    445  * @param text to copy into the buffer
    446  * @param length of text to copy (in bytes)
    447  */
    448 static void bufferAndInvoke(jmethodID method, void* data, const char* text, size_t length) {
    449     ParsingContext* parsingContext = toParsingContext(data);
    450     JNIEnv* env = parsingContext->env;
    451 
    452     // Bail out if a previously called handler threw an exception.
    453     if (env->ExceptionCheck()) return;
    454 
    455     // Buffer the element name.
    456     size_t utf16length = fillBuffer(parsingContext, text, length);
    457 
    458     // Invoke given method.
    459     jobject javaParser = parsingContext->object;
    460     jcharArray buffer = parsingContext->buffer;
    461     env->CallVoidMethod(javaParser, method, buffer, utf16length);
    462 }
    463 
    464 static const char** toAttributes(jint attributePointer) {
    465     return reinterpret_cast<const char**>(static_cast<uintptr_t>(attributePointer));
    466 }
    467 
    468 /**
    469  * The component parts of an attribute or element name.
    470  */
    471 class ExpatElementName {
    472 public:
    473     ExpatElementName(JNIEnv* env, ParsingContext* parsingContext, jint attributePointer, jint index) {
    474         const char** attributes = toAttributes(attributePointer);
    475         const char* name = attributes[index * 2];
    476         init(env, parsingContext, name);
    477     }
    478 
    479     ExpatElementName(JNIEnv* env, ParsingContext* parsingContext, const char* s) {
    480         init(env, parsingContext, s);
    481     }
    482 
    483     ~ExpatElementName() {
    484         free(mCopy);
    485     }
    486 
    487     /**
    488      * Returns the namespace URI, like "http://www.w3.org/1999/xhtml".
    489      * Possibly empty.
    490      */
    491     jstring uri() {
    492         return internString(mEnv, mParsingContext, mUri);
    493     }
    494 
    495     /**
    496      * Returns the element or attribute local name, like "h1". Never empty. When
    497      * namespace processing is disabled, this may contain a prefix, yielding a
    498      * local name like "html:h1". In such cases, the qName will always be empty.
    499      */
    500     jstring localName() {
    501         return internString(mEnv, mParsingContext, mLocalName);
    502     }
    503 
    504     /**
    505      * Returns the namespace prefix, like "html". Possibly empty.
    506      */
    507     jstring qName() {
    508         if (*mPrefix == 0) {
    509             return localName();
    510         }
    511 
    512         // return prefix + ":" + localName
    513         LocalArray<1024> qName(strlen(mPrefix) + 1 + strlen(mLocalName) + 1);
    514         snprintf(&qName[0], qName.size(), "%s:%s", mPrefix, mLocalName);
    515         return internString(mEnv, mParsingContext, &qName[0]);
    516     }
    517 
    518     /**
    519      * Returns true if this expat name has the same URI and local name.
    520      */
    521     bool matches(const char* uri, const char* localName) {
    522         return strcmp(uri, mUri) == 0 && strcmp(localName, mLocalName) == 0;
    523     }
    524 
    525     /**
    526      * Returns true if this expat name has the same qualified name.
    527      */
    528     bool matchesQName(const char* qName) {
    529         const char* lastColon = strrchr(qName, ':');
    530 
    531         // Compare local names only if either:
    532         //  - the input qualified name doesn't have a colon (like "h1")
    533         //  - this element doesn't have a prefix. Such is the case when it
    534         //    doesn't belong to a namespace, or when this parser's namespace
    535         //    processing is disabled. In the latter case, this element's local
    536         //    name may still contain a colon (like "html:h1").
    537         if (lastColon == NULL || *mPrefix == 0) {
    538             return strcmp(qName, mLocalName) == 0;
    539         }
    540 
    541         // Otherwise compare both prefix and local name
    542         size_t prefixLength = lastColon - qName;
    543         return strlen(mPrefix) == prefixLength
    544             && strncmp(qName, mPrefix, prefixLength) == 0
    545             && strcmp(lastColon + 1, mLocalName) == 0;
    546     }
    547 
    548 private:
    549     JNIEnv* mEnv;
    550     ParsingContext* mParsingContext;
    551     char* mCopy;
    552     const char* mUri;
    553     const char* mLocalName;
    554     const char* mPrefix;
    555 
    556     /**
    557      * Decodes an Expat-encoded name of one of these three forms:
    558      *     "uri|localName|prefix" (example: "http://www.w3.org/1999/xhtml|h1|html")
    559      *     "uri|localName" (example: "http://www.w3.org/1999/xhtml|h1")
    560      *     "localName" (example: "h1")
    561      */
    562     void init(JNIEnv* env, ParsingContext* parsingContext, const char* s) {
    563         mEnv = env;
    564         mParsingContext = parsingContext;
    565         mCopy = strdup(s);
    566 
    567         // split the input into up to 3 parts: a|b|c
    568         char* context = NULL;
    569         char* a = strtok_r(mCopy, "|", &context);
    570         char* b = strtok_r(NULL, "|", &context);
    571         char* c = strtok_r(NULL, "|", &context);
    572 
    573         if (c != NULL) { // input of the form "uri|localName|prefix"
    574             mUri = a;
    575             mLocalName = b;
    576             mPrefix = c;
    577         } else if (b != NULL) { // input of the form "uri|localName"
    578             mUri = a;
    579             mLocalName = b;
    580             mPrefix = "";
    581         } else { // input of the form "localName"
    582             mLocalName = a;
    583             mUri = "";
    584             mPrefix = "";
    585         }
    586     }
    587 
    588     // Disallow copy and assignment.
    589     ExpatElementName(const ExpatElementName&);
    590     void operator=(const ExpatElementName&);
    591 };
    592 
    593 /**
    594  * Called by Expat at the start of an element. Delegates to the same method
    595  * on the Java parser.
    596  *
    597  * @param data parsing context
    598  * @param elementName "uri|localName" or "localName" for the current element
    599  * @param attributes alternating attribute names and values. Like element
    600  * names, attribute names follow the format "uri|localName" or "localName".
    601  */
    602 static void startElement(void* data, const char* elementName, const char** attributes) {
    603     ParsingContext* parsingContext = toParsingContext(data);
    604     JNIEnv* env = parsingContext->env;
    605 
    606     // Bail out if a previously called handler threw an exception.
    607     if (env->ExceptionCheck()) return;
    608 
    609     // Count the number of attributes.
    610     int count = 0;
    611     while (attributes[count << 1]) count++;
    612 
    613     // Make the attributes available for the duration of this call.
    614     parsingContext->attributes = attributes;
    615     parsingContext->attributeCount = count;
    616 
    617     jobject javaParser = parsingContext->object;
    618 
    619     ExpatElementName e(env, parsingContext, elementName);
    620     jstring uri = parsingContext->processNamespaces ? e.uri() : emptyString;
    621     jstring localName = parsingContext->processNamespaces ? e.localName() : emptyString;
    622     jstring qName = e.qName();
    623 
    624     parsingContext->stringStack.push(env, qName);
    625     parsingContext->stringStack.push(env, uri);
    626     parsingContext->stringStack.push(env, localName);
    627 
    628     env->CallVoidMethod(javaParser, startElementMethod, uri, localName, qName, attributes, count);
    629 
    630     parsingContext->attributes = NULL;
    631     parsingContext->attributeCount = -1;
    632 }
    633 
    634 /**
    635  * Called by Expat at the end of an element. Delegates to the same method
    636  * on the Java parser.
    637  *
    638  * @param data parsing context
    639  * @param elementName "uri|localName" or "localName" for the current element;
    640  *         we assume that this matches the last data on our stack.
    641  */
    642 static void endElement(void* data, const char* /*elementName*/) {
    643     ParsingContext* parsingContext = toParsingContext(data);
    644     JNIEnv* env = parsingContext->env;
    645 
    646     // Bail out if a previously called handler threw an exception.
    647     if (env->ExceptionCheck()) return;
    648 
    649     jobject javaParser = parsingContext->object;
    650 
    651     jstring localName = parsingContext->stringStack.pop();
    652     jstring uri = parsingContext->stringStack.pop();
    653     jstring qName = parsingContext->stringStack.pop();
    654 
    655     env->CallVoidMethod(javaParser, endElementMethod, uri, localName, qName);
    656 }
    657 
    658 /**
    659  * Called by Expat when it encounters text. Delegates to the same method
    660  * on the Java parser. This may be called mutiple times with incremental pieces
    661  * of the same contiguous block of text.
    662  *
    663  * @param data parsing context
    664  * @param characters buffer containing encountered text
    665  * @param length number of characters in the buffer
    666  */
    667 static void text(void* data, const char* characters, int length) {
    668     bufferAndInvoke(textMethod, data, characters, length);
    669 }
    670 
    671 /**
    672  * Called by Expat when it encounters a comment. Delegates to the same method
    673  * on the Java parser.
    674 
    675  * @param data parsing context
    676  * @param comment 0-terminated
    677  */
    678 static void comment(void* data, const char* comment) {
    679     bufferAndInvoke(commentMethod, data, comment, strlen(comment));
    680 }
    681 
    682 /**
    683  * Called by Expat at the beginning of a namespace mapping.
    684  *
    685  * @param data parsing context
    686  * @param prefix null-terminated namespace prefix used in the XML
    687  * @param uri of the namespace
    688  */
    689 static void startNamespace(void* data, const char* prefix, const char* uri) {
    690     ParsingContext* parsingContext = toParsingContext(data);
    691     JNIEnv* env = parsingContext->env;
    692 
    693     // Bail out if a previously called handler threw an exception.
    694     if (env->ExceptionCheck()) return;
    695 
    696     jstring internedPrefix = emptyString;
    697     if (prefix != NULL) {
    698         internedPrefix = internString(env, parsingContext, prefix);
    699         if (env->ExceptionCheck()) return;
    700     }
    701 
    702     jstring internedUri = emptyString;
    703     if (uri != NULL) {
    704         internedUri = internString(env, parsingContext, uri);
    705         if (env->ExceptionCheck()) return;
    706     }
    707 
    708     parsingContext->stringStack.push(env, internedPrefix);
    709 
    710     jobject javaParser = parsingContext->object;
    711     env->CallVoidMethod(javaParser, startNamespaceMethod, internedPrefix, internedUri);
    712 }
    713 
    714 /**
    715  * Called by Expat at the end of a namespace mapping.
    716  *
    717  * @param data parsing context
    718  * @param prefix null-terminated namespace prefix used in the XML;
    719  *         we assume this is the same as the last prefix on the stack.
    720  */
    721 static void endNamespace(void* data, const char* /*prefix*/) {
    722     ParsingContext* parsingContext = toParsingContext(data);
    723     JNIEnv* env = parsingContext->env;
    724 
    725     // Bail out if a previously called handler threw an exception.
    726     if (env->ExceptionCheck()) return;
    727 
    728     jstring internedPrefix = parsingContext->stringStack.pop();
    729 
    730     jobject javaParser = parsingContext->object;
    731     env->CallVoidMethod(javaParser, endNamespaceMethod, internedPrefix);
    732 }
    733 
    734 /**
    735  * Called by Expat at the beginning of a CDATA section.
    736  *
    737  * @param data parsing context
    738  */
    739 static void startCdata(void* data) {
    740     ParsingContext* parsingContext = toParsingContext(data);
    741     JNIEnv* env = parsingContext->env;
    742 
    743     // Bail out if a previously called handler threw an exception.
    744     if (env->ExceptionCheck()) return;
    745 
    746     jobject javaParser = parsingContext->object;
    747     env->CallVoidMethod(javaParser, startCdataMethod);
    748 }
    749 
    750 /**
    751  * Called by Expat at the end of a CDATA section.
    752  *
    753  * @param data parsing context
    754  */
    755 static void endCdata(void* data) {
    756     ParsingContext* parsingContext = toParsingContext(data);
    757     JNIEnv* env = parsingContext->env;
    758 
    759     // Bail out if a previously called handler threw an exception.
    760     if (env->ExceptionCheck()) return;
    761 
    762     jobject javaParser = parsingContext->object;
    763     env->CallVoidMethod(javaParser, endCdataMethod);
    764 }
    765 
    766 /**
    767  * Called by Expat at the beginning of a DOCTYPE section.
    768  * Expat gives us 'hasInternalSubset', but the Java API doesn't expect it, so we don't need it.
    769  */
    770 static void startDtd(void* data, const char* name,
    771         const char* systemId, const char* publicId, int /*hasInternalSubset*/) {
    772     ParsingContext* parsingContext = toParsingContext(data);
    773     JNIEnv* env = parsingContext->env;
    774 
    775     // Bail out if a previously called handler threw an exception.
    776     if (env->ExceptionCheck()) return;
    777 
    778     jstring javaName = internString(env, parsingContext, name);
    779     if (env->ExceptionCheck()) return;
    780 
    781     jstring javaPublicId = internString(env, parsingContext, publicId);
    782     if (env->ExceptionCheck()) return;
    783 
    784     jstring javaSystemId = internString(env, parsingContext, systemId);
    785     if (env->ExceptionCheck()) return;
    786 
    787     jobject javaParser = parsingContext->object;
    788     env->CallVoidMethod(javaParser, startDtdMethod, javaName, javaPublicId,
    789         javaSystemId);
    790 }
    791 
    792 /**
    793  * Called by Expat at the end of a DOCTYPE section.
    794  *
    795  * @param data parsing context
    796  */
    797 static void endDtd(void* data) {
    798     ParsingContext* parsingContext = toParsingContext(data);
    799     JNIEnv* env = parsingContext->env;
    800 
    801     // Bail out if a previously called handler threw an exception.
    802     if (env->ExceptionCheck()) return;
    803 
    804     jobject javaParser = parsingContext->object;
    805     env->CallVoidMethod(javaParser, endDtdMethod);
    806 }
    807 
    808 /**
    809  * Called by Expat when it encounters processing instructions.
    810  *
    811  * @param data parsing context
    812  * @param target of the instruction
    813  * @param instructionData
    814  */
    815 static void processingInstruction(void* data, const char* target, const char* instructionData) {
    816     ParsingContext* parsingContext = toParsingContext(data);
    817     JNIEnv* env = parsingContext->env;
    818 
    819     // Bail out if a previously called handler threw an exception.
    820     if (env->ExceptionCheck()) return;
    821 
    822     jstring javaTarget = internString(env, parsingContext, target);
    823     if (env->ExceptionCheck()) return;
    824 
    825     ScopedLocalRef<jstring> javaInstructionData(env, env->NewStringUTF(instructionData));
    826     if (env->ExceptionCheck()) return;
    827 
    828     jobject javaParser = parsingContext->object;
    829     env->CallVoidMethod(javaParser, processingInstructionMethod, javaTarget, javaInstructionData.get());
    830 }
    831 
    832 /**
    833  * Creates a new entity parser.
    834  *
    835  * @param object the Java ExpatParser instance
    836  * @param parentParser pointer
    837  * @param javaEncoding the character encoding name
    838  * @param javaContext that was provided to handleExternalEntity
    839  * @returns the pointer to the C Expat entity parser
    840  */
    841 static jint ExpatParser_createEntityParser(JNIEnv* env, jobject, jint parentParser, jstring javaContext) {
    842     ScopedUtfChars context(env, javaContext);
    843     if (context.c_str() == NULL) {
    844         return 0;
    845     }
    846 
    847     XML_Parser parent = (XML_Parser) parentParser;
    848     XML_Parser entityParser = XML_ExternalEntityParserCreate(parent, context.c_str(), NULL);
    849     if (entityParser == NULL) {
    850         throw_OutOfMemoryError(env);
    851     }
    852 
    853     return (jint) entityParser;
    854 }
    855 
    856 /**
    857  * Handles external entities. We ignore the "base" URI and keep track of it
    858  * ourselves.
    859  */
    860 static int handleExternalEntity(XML_Parser parser, const char* context,
    861         const char*, const char* systemId, const char* publicId) {
    862     ParsingContext* parsingContext = toParsingContext(parser);
    863     jobject javaParser = parsingContext->object;
    864     JNIEnv* env = parsingContext->env;
    865     jobject object = parsingContext->object;
    866 
    867     // Bail out if a previously called handler threw an exception.
    868     if (env->ExceptionCheck()) {
    869         return XML_STATUS_ERROR;
    870     }
    871 
    872     ScopedLocalRef<jstring> javaSystemId(env, env->NewStringUTF(systemId));
    873     if (env->ExceptionCheck()) {
    874         return XML_STATUS_ERROR;
    875     }
    876     ScopedLocalRef<jstring> javaPublicId(env, env->NewStringUTF(publicId));
    877     if (env->ExceptionCheck()) {
    878         return XML_STATUS_ERROR;
    879     }
    880     ScopedLocalRef<jstring> javaContext(env, env->NewStringUTF(context));
    881     if (env->ExceptionCheck()) {
    882         return XML_STATUS_ERROR;
    883     }
    884 
    885     // Pass the wrapped parser and both strings to java.
    886     env->CallVoidMethod(javaParser, handleExternalEntityMethod, javaContext.get(),
    887             javaPublicId.get(), javaSystemId.get());
    888 
    889     /*
    890      * Parsing the external entity leaves parsingContext->env and object set to
    891      * NULL, so we need to restore both.
    892      *
    893      * TODO: consider restoring the original env and object instead of setting
    894      * them to NULL in the append() functions.
    895      */
    896     parsingContext->env = env;
    897     parsingContext->object = object;
    898 
    899     return env->ExceptionCheck() ? XML_STATUS_ERROR : XML_STATUS_OK;
    900 }
    901 
    902 /**
    903  * Expat gives us 'base', but the Java API doesn't expect it, so we don't need it.
    904  */
    905 static void unparsedEntityDecl(void* data, const char* name, const char* /*base*/, const char* systemId, const char* publicId, const char* notationName) {
    906     ParsingContext* parsingContext = toParsingContext(data);
    907     jobject javaParser = parsingContext->object;
    908     JNIEnv* env = parsingContext->env;
    909 
    910     // Bail out if a previously called handler threw an exception.
    911     if (env->ExceptionCheck()) return;
    912 
    913     ScopedLocalRef<jstring> javaName(env, env->NewStringUTF(name));
    914     if (env->ExceptionCheck()) return;
    915     ScopedLocalRef<jstring> javaPublicId(env, env->NewStringUTF(publicId));
    916     if (env->ExceptionCheck()) return;
    917     ScopedLocalRef<jstring> javaSystemId(env, env->NewStringUTF(systemId));
    918     if (env->ExceptionCheck()) return;
    919     ScopedLocalRef<jstring> javaNotationName(env, env->NewStringUTF(notationName));
    920     if (env->ExceptionCheck()) return;
    921 
    922     env->CallVoidMethod(javaParser, unparsedEntityDeclMethod, javaName.get(), javaPublicId.get(), javaSystemId.get(), javaNotationName.get());
    923 }
    924 
    925 /**
    926  * Expat gives us 'base', but the Java API doesn't expect it, so we don't need it.
    927  */
    928 static void notationDecl(void* data, const char* name, const char* /*base*/, const char* systemId, const char* publicId) {
    929     ParsingContext* parsingContext = toParsingContext(data);
    930     jobject javaParser = parsingContext->object;
    931     JNIEnv* env = parsingContext->env;
    932 
    933     // Bail out if a previously called handler threw an exception.
    934     if (env->ExceptionCheck()) return;
    935 
    936     ScopedLocalRef<jstring> javaName(env, env->NewStringUTF(name));
    937     if (env->ExceptionCheck()) return;
    938     ScopedLocalRef<jstring> javaPublicId(env, env->NewStringUTF(publicId));
    939     if (env->ExceptionCheck()) return;
    940     ScopedLocalRef<jstring> javaSystemId(env, env->NewStringUTF(systemId));
    941     if (env->ExceptionCheck()) return;
    942 
    943     env->CallVoidMethod(javaParser, notationDeclMethod, javaName.get(), javaPublicId.get(), javaSystemId.get());
    944 }
    945 
    946 /**
    947  * Creates a new Expat parser. Called from the Java ExpatParser constructor.
    948  *
    949  * @param object the Java ExpatParser instance
    950  * @param javaEncoding the character encoding name
    951  * @param processNamespaces true if the parser should handle namespaces
    952  * @returns the pointer to the C Expat parser
    953  */
    954 static jint ExpatParser_initialize(JNIEnv* env, jobject object, jstring javaEncoding,
    955         jboolean processNamespaces) {
    956     // Allocate parsing context.
    957     UniquePtr<ParsingContext> context(new ParsingContext(object));
    958     if (context.get() == NULL) {
    959         throw_OutOfMemoryError(env);
    960         return 0;
    961     }
    962 
    963     context->processNamespaces = (bool) processNamespaces;
    964 
    965     // Create a parser.
    966     XML_Parser parser;
    967     ScopedUtfChars encoding(env, javaEncoding);
    968     if (encoding.c_str() == NULL) {
    969         return 0;
    970     }
    971     if (processNamespaces) {
    972         // Use '|' to separate URIs from local names.
    973         parser = XML_ParserCreateNS(encoding.c_str(), '|');
    974     } else {
    975         parser = XML_ParserCreate(encoding.c_str());
    976     }
    977 
    978     if (parser != NULL) {
    979         if (processNamespaces) {
    980             XML_SetNamespaceDeclHandler(parser, startNamespace, endNamespace);
    981             XML_SetReturnNSTriplet(parser, 1);
    982         }
    983 
    984         XML_SetCdataSectionHandler(parser, startCdata, endCdata);
    985         XML_SetCharacterDataHandler(parser, text);
    986         XML_SetCommentHandler(parser, comment);
    987         XML_SetDoctypeDeclHandler(parser, startDtd, endDtd);
    988         XML_SetElementHandler(parser, startElement, endElement);
    989         XML_SetExternalEntityRefHandler(parser, handleExternalEntity);
    990         XML_SetNotationDeclHandler(parser, notationDecl);
    991         XML_SetProcessingInstructionHandler(parser, processingInstruction);
    992         XML_SetUnparsedEntityDeclHandler(parser, unparsedEntityDecl);
    993         XML_SetUserData(parser, context.release());
    994     } else {
    995         throw_OutOfMemoryError(env);
    996         return 0;
    997     }
    998 
    999     return (jint) parser;
   1000 }
   1001 
   1002 /**
   1003  * Expat decides for itself what character encoding it's looking at. The interface is in terms of
   1004  * bytes, which may point to UTF-8, UTF-16, ISO-8859-1, or US-ASCII. appendBytes, appendCharacters,
   1005  * and appendString thus all call through to this method, strange though that appears.
   1006  */
   1007 static void append(JNIEnv* env, jobject object, jint pointer,
   1008         const char* bytes, size_t byteOffset, size_t byteCount, jboolean isFinal) {
   1009     XML_Parser parser = (XML_Parser) pointer;
   1010     ParsingContext* context = toParsingContext(parser);
   1011     context->env = env;
   1012     context->object = object;
   1013     if (!XML_Parse(parser, bytes + byteOffset, byteCount, isFinal) && !env->ExceptionCheck()) {
   1014         jniThrowExpatException(env, XML_GetErrorCode(parser));
   1015     }
   1016     context->object = NULL;
   1017     context->env = NULL;
   1018 }
   1019 
   1020 static void ExpatParser_appendBytes(JNIEnv* env, jobject object, jint pointer,
   1021         jbyteArray xml, jint byteOffset, jint byteCount) {
   1022     ScopedByteArrayRO byteArray(env, xml);
   1023     if (byteArray.get() == NULL) {
   1024         return;
   1025     }
   1026 
   1027     const char* bytes = reinterpret_cast<const char*>(byteArray.get());
   1028     append(env, object, pointer, bytes, byteOffset, byteCount, XML_FALSE);
   1029 }
   1030 
   1031 static void ExpatParser_appendChars(JNIEnv* env, jobject object, jint pointer,
   1032         jcharArray xml, jint charOffset, jint charCount) {
   1033     ScopedCharArrayRO charArray(env, xml);
   1034     if (charArray.get() == NULL) {
   1035         return;
   1036     }
   1037 
   1038     const char* bytes = reinterpret_cast<const char*>(charArray.get());
   1039     size_t byteOffset = 2 * charOffset;
   1040     size_t byteCount = 2 * charCount;
   1041     append(env, object, pointer, bytes, byteOffset, byteCount, XML_FALSE);
   1042 }
   1043 
   1044 static void ExpatParser_appendString(JNIEnv* env, jobject object, jint pointer,
   1045         jstring javaXml, jboolean isFinal) {
   1046     ScopedJavaUnicodeString xml(env, javaXml);
   1047     const char* bytes = reinterpret_cast<const char*>(xml.unicodeString().getBuffer());
   1048     size_t byteCount = 2 * xml.unicodeString().length();
   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 << 1) + 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 <<= 1;
   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         throw_OutOfMemoryError(env);
   1280         return NULL;
   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