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