• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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  */
43 struct InternedString {
InternedStringInternedString44     InternedString() : interned(NULL), bytes(NULL) {
45     }
46 
~InternedStringInternedString47     ~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  */
64 class StringStack {
65 public:
StringStack()66     StringStack() : array(new jstring[DEFAULT_CAPACITY]), capacity(DEFAULT_CAPACITY), size(0) {
67     }
68 
~StringStack()69     ~StringStack() {
70         delete[] array;
71     }
72 
push(JNIEnv * env,jstring s)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 
pop()91     jstring pop() {
92         return (size == 0) ? NULL : array[--size];
93     }
94 
95 private:
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  */
106 struct ParsingContext {
ParsingContextParsingContext107     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.
~ParsingContextParsingContext115     ~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 
ensureCapacityParsingContext137     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 
156 private:
freeBufferParsingContext157     void freeBuffer() {
158         if (buffer != NULL) {
159             env->DeleteGlobalRef(buffer);
160             buffer = NULL;
161             bufferSize = -1;
162         }
163     }
164 
165 public:
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 
178 private:
179     /** The size of our buffer in jchars. */
180     int bufferSize;
181 
182 public:
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 
toParsingContext(void * data)199 static ParsingContext* toParsingContext(void* data) {
200     return reinterpret_cast<ParsingContext*>(data);
201 }
202 
toParsingContext(XML_Parser parser)203 static ParsingContext* toParsingContext(XML_Parser parser) {
204     return reinterpret_cast<ParsingContext*>(XML_GetUserData(parser));
205 }
206 
toXMLParser(jlong address)207 static XML_Parser toXMLParser(jlong address) {
208   return reinterpret_cast<XML_Parser>(address);
209 }
210 
fromXMLParser(XML_Parser parser)211 static jlong fromXMLParser(XML_Parser parser) {
212   return reinterpret_cast<uintptr_t>(parser);
213 }
214 
215 static jmethodID commentMethod;
216 static jmethodID endCdataMethod;
217 static jmethodID endDtdMethod;
218 static jmethodID endElementMethod;
219 static jmethodID endNamespaceMethod;
220 static jmethodID handleExternalEntityMethod;
221 static jmethodID internMethod;
222 static jmethodID notationDeclMethod;
223 static jmethodID processingInstructionMethod;
224 static jmethodID startCdataMethod;
225 static jmethodID startDtdMethod;
226 static jmethodID startElementMethod;
227 static jmethodID startNamespaceMethod;
228 static jmethodID textMethod;
229 static jmethodID unparsedEntityDeclMethod;
230 static 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  */
hashString(const char * s)240 static 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  */
newInternedString(JNIEnv * env,const char * bytes,int hash)258 static 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  */
newInternedStringBucket(InternedString * entry)308 static 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  */
expandInternedStringBucket(InternedString ** existingBucket,InternedString * entry)325 static 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  */
findInternedString(InternedString ** bucket,const char * s,int hash)352 static 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  */
internString(JNIEnv * env,ParsingContext * parsingContext,const char * s)367 static 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 
jniThrowExpatException(JNIEnv * env,XML_Error error)420 static 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  */
fillBuffer(ParsingContext * parsingContext,const char * utf8,int byteCount)431 static 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  */
bufferAndInvoke(jmethodID method,void * data,const char * text,size_t length)459 static 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 
toAttributes(jlong attributePointer)475 static 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  */
482 class ExpatElementName {
483 public:
ExpatElementName(JNIEnv * env,ParsingContext * parsingContext,jlong attributePointer,jint index)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 
ExpatElementName(JNIEnv * env,ParsingContext * parsingContext,const char * s)490     ExpatElementName(JNIEnv* env, ParsingContext* parsingContext, const char* s) {
491         init(env, parsingContext, s);
492     }
493 
~ExpatElementName()494     ~ExpatElementName() {
495         free(mCopy);
496     }
497 
498     /**
499      * Returns the namespace URI, like "http://www.w3.org/1999/xhtml".
500      * Possibly empty.
501      */
uri()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      */
localName()511     jstring localName() {
512         return internString(mEnv, mParsingContext, mLocalName);
513     }
514 
515     /**
516      * Returns the namespace prefix, like "html". Possibly empty.
517      */
qName()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      */
matches(const char * uri,const char * localName)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      */
matchesQName(const char * qName)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 
558 private:
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      */
init(JNIEnv * env,ParsingContext * parsingContext,const char * s)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  */
startElement(void * data,const char * elementName,const char ** attributes)612 static 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  */
endElement(void * data,const char *)653 static 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  */
text(void * data,const char * characters,int length)678 static void text(void* data, const char* characters, int length) {
679     bufferAndInvoke(textMethod, data, characters, length);
680 }
681 
682 /**
683  * Called by Expat when it encounters a comment. Delegates to the same method
684  * on the Java parser.
685 
686  * @param data parsing context
687  * @param comment 0-terminated
688  */
comment(void * data,const char * comment)689 static void comment(void* data, const char* comment) {
690     bufferAndInvoke(commentMethod, data, comment, strlen(comment));
691 }
692 
693 /**
694  * Called by Expat at the beginning of a namespace mapping.
695  *
696  * @param data parsing context
697  * @param prefix null-terminated namespace prefix used in the XML
698  * @param uri of the namespace
699  */
startNamespace(void * data,const char * prefix,const char * uri)700 static 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  */
endNamespace(void * data,const char *)732 static 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  */
startCdata(void * data)750 static 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  */
endCdata(void * data)766 static 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  */
startDtd(void * data,const char * name,const char * systemId,const char * publicId,int)781 static 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  */
endDtd(void * data)808 static 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  */
processingInstruction(void * data,const char * target,const char * instructionData)826 static 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  */
ExpatParser_createEntityParser(JNIEnv * env,jobject,jlong parentParser,jstring javaContext)852 static 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  */
handleExternalEntity(XML_Parser parser,const char * context,const char *,const char * systemId,const char * publicId)871 static 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  */
unparsedEntityDecl(void * data,const char * name,const char *,const char * systemId,const char * publicId,const char * notationName)916 static 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  */
notationDecl(void * data,const char * name,const char *,const char * systemId,const char * publicId)939 static 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  */
ExpatParser_initialize(JNIEnv * env,jobject object,jstring javaEncoding,jboolean processNamespaces)965 static 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  */
append(JNIEnv * env,jobject object,jlong pointer,const char * bytes,size_t byteOffset,size_t byteCount,jboolean isFinal)1020 static 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 
ExpatParser_appendBytes(JNIEnv * env,jobject object,jlong pointer,jbyteArray xml,jint byteOffset,jint byteCount)1033 static 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 
ExpatParser_appendChars(JNIEnv * env,jobject object,jlong pointer,jcharArray xml,jint charOffset,jint charCount)1044 static 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 
ExpatParser_appendString(JNIEnv * env,jobject object,jlong pointer,jstring javaXml,jboolean isFinal)1057 static 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  */
ExpatParser_releaseParser(JNIEnv *,jobject,jlong address)1070 static 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  */
ExpatParser_release(JNIEnv * env,jobject,jlong address)1077 static 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 
ExpatParser_line(JNIEnv *,jobject,jlong address)1087 static int ExpatParser_line(JNIEnv*, jobject, jlong address) {
1088   return XML_GetCurrentLineNumber(toXMLParser(address));
1089 }
1090 
ExpatParser_column(JNIEnv *,jobject,jlong address)1091 static 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  */
ExpatAttributes_getURI(JNIEnv * env,jobject,jlong address,jlong attributePointer,jint index)1102 static 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  */
ExpatAttributes_getLocalName(JNIEnv * env,jobject,jlong address,jlong attributePointer,jint index)1115 static 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  */
ExpatAttributes_getQName(JNIEnv * env,jobject,jlong address,jlong attributePointer,jint index)1128 static 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  */
ExpatAttributes_getValueByIndex(JNIEnv * env,jobject,jlong attributePointer,jint index)1142 static 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  */
ExpatAttributes_getIndexForQName(JNIEnv * env,jobject,jlong attributePointer,jstring qName)1157 static 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  */
ExpatAttributes_getIndex(JNIEnv * env,jobject,jlong attributePointer,jstring uri,jstring localName)1185 static 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  */
ExpatAttributes_getValueForQName(JNIEnv * env,jobject clazz,jlong attributePointer,jstring qName)1216 static 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  */
ExpatAttributes_getValue(JNIEnv * env,jobject clazz,jlong attributePointer,jstring uri,jstring localName)1232 static 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  */
ExpatParser_cloneAttributes(JNIEnv * env,jobject,jlong address,jint count)1246 static 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  */
ExpatAttributes_freeAttributes(JNIEnv *,jobject,jlong pointer)1286 static 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  */
ExpatParser_staticInitialize(JNIEnv * env,jobject classObject,jstring empty)1295 static 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 
1356 static 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 
1370 static 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 };
register_org_apache_harmony_xml_ExpatParser(JNIEnv * env)1381 void 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