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