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