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