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