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