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