blob: a6ec12588d8571c12557e8dc154c4600f685bde4 [file] [log] [blame]
The Android Open Source Projectdd828f42008-12-17 18:03:55 -08001/*
The Android Open Source Projectfdb27042008-10-21 07:00:00 -07002 * 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
Elliott Hughes845ce3c2009-11-13 17:07:00 -080019#include "JNIHelp.h"
Elliott Hughesa9f5c162010-06-16 16:32:18 -070020#include "JniConstants.h"
Elliott Hughes845ce3c2009-11-13 17:07:00 -080021#include "LocalArray.h"
Elliott Hughes4e3714f2010-06-22 14:32:47 -070022#include "ScopedJavaUnicodeString.h"
Elliott Hughes8044bf62010-05-17 22:34:46 -070023#include "ScopedLocalRef.h"
Elliott Hughes99c59bf2010-05-17 16:22:04 -070024#include "ScopedPrimitiveArray.h"
Elliott Hughes05960872010-05-26 17:45:07 -070025#include "ScopedUtfChars.h"
Elliott Hughes8044bf62010-05-17 22:34:46 -070026#include "UniquePtr.h"
The Android Open Source Projectfdb27042008-10-21 07:00:00 -070027#include "jni.h"
28#include "utils/Log.h"
The Android Open Source Projectfdb27042008-10-21 07:00:00 -070029
30#include <string.h>
31#include <utils/misc.h>
32#include <expat.h>
33#include <cutils/jstring.h>
34
35#define BUCKET_COUNT 128
36
Elliott Hughes4eafbcc2010-06-21 16:52:06 -070037static void throw_OutOfMemoryError(JNIEnv* env) {
38 jniThrowException(env, "java/lang/OutOfMemoryError", "Out of memory.");
39}
40
The Android Open Source Projectfdb27042008-10-21 07:00:00 -070041/**
42 * Wrapper around an interned string.
43 */
Elliott Hughesc9b92b42009-12-09 16:05:29 -080044struct InternedString {
Elliott Hughes4eafbcc2010-06-21 16:52:06 -070045 InternedString() : interned(NULL), bytes(NULL) {
46 }
47
48 ~InternedString() {
49 delete[] bytes;
50 }
The Android Open Source Projectfdb27042008-10-21 07:00:00 -070051
52 /** The interned string itself. */
53 jstring interned;
54
55 /** UTF-8 equivalent of the interned string. */
56 const char* bytes;
57
58 /** Hash code of the interned string. */
Elliott Hughes99c59bf2010-05-17 16:22:04 -070059 int hash;
Elliott Hughesc9b92b42009-12-09 16:05:29 -080060};
The Android Open Source Projectfdb27042008-10-21 07:00:00 -070061
62/**
63 * Keeps track of strings between start and end events.
64 */
Elliott Hughes4eafbcc2010-06-21 16:52:06 -070065class StringStack {
66public:
67 StringStack() : array(new jstring[DEFAULT_CAPACITY]), capacity(DEFAULT_CAPACITY), size(0) {
68 }
69
70 ~StringStack() {
71 delete[] array;
72 }
73
74 void push(JNIEnv* env, jstring s) {
75 if (size == capacity) {
76 int newCapacity = capacity * 2;
77 jstring* newArray = new jstring[newCapacity];
78 if (newArray == NULL) {
79 throw_OutOfMemoryError(env);
80 return;
81 }
82 memcpy(newArray, array, capacity * sizeof(jstring));
83
84 array = newArray;
85 capacity = newCapacity;
86 }
87
88 array[size++] = s;
89 }
90
91 jstring pop() {
92 return (size == 0) ? NULL : array[--size];
93 }
94
95private:
96 enum { DEFAULT_CAPACITY = 10 };
The Android Open Source Projectfdb27042008-10-21 07:00:00 -070097
98 jstring* array;
99 int capacity;
100 int size;
Elliott Hughesc9b92b42009-12-09 16:05:29 -0800101};
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700102
103/**
104 * Data passed to parser handler method by the parser.
105 */
Elliott Hughesc9b92b42009-12-09 16:05:29 -0800106struct ParsingContext {
Elliott Hughes4eafbcc2010-06-21 16:52:06 -0700107 ParsingContext(jobject object) : env(NULL), object(object), buffer(NULL), bufferSize(-1) {
108 for (int i = 0; i < BUCKET_COUNT; i++) {
109 internedStrings[i] = NULL;
110 }
111 }
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700112
Elliott Hughes4eafbcc2010-06-21 16:52:06 -0700113 // Warning: 'env' must be valid on entry.
114 ~ParsingContext() {
115 freeBuffer();
116
117 // Free interned string cache.
118 for (int i = 0; i < BUCKET_COUNT; i++) {
119 if (internedStrings[i]) {
120 InternedString** bucket = internedStrings[i];
121 InternedString* current;
122 while ((current = *(bucket++)) != NULL) {
123 // Free the interned string reference.
124 env->DeleteGlobalRef(current->interned);
125
126 // Free the bucket.
127 delete current;
128 }
129
130 // Free the buckets.
131 delete[] internedStrings[i];
132 }
133 }
134 }
135
136 jcharArray ensureCapacity(int length) {
137 if (bufferSize < length) {
138 // Free the existing char[].
139 freeBuffer();
140
141 // Allocate a new char[].
142 jcharArray javaBuffer = env->NewCharArray(length);
143 if (javaBuffer == NULL) return NULL;
144
145 // Create a global reference.
146 javaBuffer = (jcharArray) env->NewGlobalRef(javaBuffer);
147 if (javaBuffer == NULL) return NULL;
148
149 buffer = javaBuffer;
150 bufferSize = length;
151 }
152 return buffer;
153 }
154
155private:
156 void freeBuffer() {
157 if (buffer != NULL) {
158 env->DeleteGlobalRef(buffer);
159 buffer = NULL;
160 bufferSize = -1;
161 }
162 }
163
164public:
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700165 /**
166 * The JNI environment for the current thread. This should only be used
167 * to keep a reference to the env for use in Expat callbacks.
168 */
169 JNIEnv* env;
170
171 /** The Java parser object. */
172 jobject object;
173
174 /** Buffer for text events. */
175 jcharArray buffer;
176
Elliott Hughes4eafbcc2010-06-21 16:52:06 -0700177private:
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700178 /** The size of our buffer in jchars. */
179 int bufferSize;
180
Elliott Hughes4eafbcc2010-06-21 16:52:06 -0700181public:
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700182 /** Current attributes. */
183 const char** attributes;
184
185 /** Number of attributes. */
186 int attributeCount;
187
188 /** True if namespace support is enabled. */
189 bool processNamespaces;
190
191 /** Keep track of names. */
192 StringStack stringStack;
193
194 /** Cache of interned strings. */
195 InternedString** internedStrings[BUCKET_COUNT];
Elliott Hughesc9b92b42009-12-09 16:05:29 -0800196};
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700197
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700198static jmethodID commentMethod;
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700199static jmethodID endCdataMethod;
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700200static jmethodID endDtdMethod;
Elliott Hughesc9b92b42009-12-09 16:05:29 -0800201static jmethodID endElementMethod;
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700202static jmethodID endNamespaceMethod;
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700203static jmethodID handleExternalEntityMethod;
204static jmethodID internMethod;
Elliott Hughesc9b92b42009-12-09 16:05:29 -0800205static jmethodID notationDeclMethod;
206static jmethodID processingInstructionMethod;
207static jmethodID startCdataMethod;
208static jmethodID startDtdMethod;
209static jmethodID startElementMethod;
210static jmethodID startNamespaceMethod;
211static jmethodID textMethod;
212static jmethodID unparsedEntityDeclMethod;
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700213static jstring emptyString;
214
215/**
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700216 * Calculates a hash code for a null-terminated string. This is *not* equivalent
217 * to Java's String.hashCode(). This hashes the bytes while String.hashCode()
218 * hashes UTF-16 chars.
219 *
220 * @param s null-terminated string to hash
221 * @returns hash code
222 */
223static int hashString(const char* s) {
224 int hash = 0;
225 if (s) {
226 while (*s) {
227 hash = hash * 31 + *s++;
228 }
229 }
230 return hash;
231}
232
233/**
234 * Creates a new interned string wrapper. Looks up the interned string
235 * representing the given UTF-8 bytes.
236 *
237 * @param bytes null-terminated string to intern
238 * @param hash of bytes
239 * @returns wrapper of interned Java string
240 */
Elliott Hughes4e3714f2010-06-22 14:32:47 -0700241static InternedString* newInternedString(JNIEnv* env, const char* bytes, int hash) {
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700242 // Allocate a new wrapper.
Elliott Hughes8044bf62010-05-17 22:34:46 -0700243 UniquePtr<InternedString> wrapper(new InternedString);
244 if (wrapper.get() == NULL) {
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700245 throw_OutOfMemoryError(env);
246 return NULL;
247 }
248
249 // Create a copy of the UTF-8 bytes.
250 // TODO: sometimes we already know the length. Reuse it if so.
Elliott Hughes4eafbcc2010-06-21 16:52:06 -0700251 char* copy = new char[strlen(bytes) + 1];
252 if (copy == NULL) {
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700253 throw_OutOfMemoryError(env);
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700254 return NULL;
255 }
Elliott Hughes4eafbcc2010-06-21 16:52:06 -0700256 strcpy(copy, bytes);
257 wrapper->bytes = copy;
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700258
259 // Save the hash.
260 wrapper->hash = hash;
261
262 // To intern a string, we must first create a new string and then call
263 // intern() on it. We then keep a global reference to the interned string.
Elliott Hughes8044bf62010-05-17 22:34:46 -0700264 ScopedLocalRef<jstring> newString(env, env->NewStringUTF(bytes));
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700265 if (env->ExceptionCheck()) {
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700266 return NULL;
267 }
268
269 // Call intern().
Elliott Hughes8044bf62010-05-17 22:34:46 -0700270 ScopedLocalRef<jstring> interned(env,
271 reinterpret_cast<jstring>(env->CallObjectMethod(newString.get(), internMethod)));
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700272 if (env->ExceptionCheck()) {
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700273 return NULL;
274 }
275
276 // Create a global reference to the interned string.
Elliott Hughes8044bf62010-05-17 22:34:46 -0700277 wrapper->interned = (jstring) env->NewGlobalRef(interned.get());
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700278 if (env->ExceptionCheck()) {
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700279 return NULL;
280 }
281
Elliott Hughes8044bf62010-05-17 22:34:46 -0700282 return wrapper.release();
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700283}
284
285/**
286 * Allocates a new bucket with one entry.
287 *
288 * @param entry to store in the bucket
289 * @returns a reference to the bucket
290 */
291static InternedString** newInternedStringBucket(InternedString* entry) {
Elliott Hughes4eafbcc2010-06-21 16:52:06 -0700292 InternedString** bucket = new InternedString*[2];
293 if (bucket != NULL) {
294 bucket[0] = entry;
295 bucket[1] = NULL;
296 }
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700297 return bucket;
298}
299
300/**
301 * Expands an interned string bucket and adds the given entry. Frees the
302 * provided bucket and returns a new one.
303 *
304 * @param existingBucket the bucket to replace
305 * @param entry to add to the bucket
306 * @returns a reference to the newly-allocated bucket containing the given entry
307 */
308static InternedString** expandInternedStringBucket(
309 InternedString** existingBucket, InternedString* entry) {
310 // Determine the size of the existing bucket.
311 int size = 0;
312 while (existingBucket[size]) size++;
313
314 // Allocate the new bucket with enough space for one more entry and
315 // a null terminator.
Elliott Hughes4eafbcc2010-06-21 16:52:06 -0700316 InternedString** newBucket = new InternedString*[size + 2];
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700317 if (newBucket == NULL) return NULL;
318
Elliott Hughes4eafbcc2010-06-21 16:52:06 -0700319 memcpy(newBucket, existingBucket, size * sizeof(InternedString*));
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700320 newBucket[size] = entry;
321 newBucket[size + 1] = NULL;
322
323 return newBucket;
324}
325
326/**
327 * Returns an interned string for the given UTF-8 string.
328 *
329 * @param bucket to search for s
330 * @param s null-terminated string to find
331 * @param hash of s
332 * @returns interned Java string equivalent of s or null if not found
333 */
Elliott Hughes4eafbcc2010-06-21 16:52:06 -0700334static jstring findInternedString(InternedString** bucket, const char* s, int hash) {
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700335 InternedString* current;
336 while ((current = *(bucket++)) != NULL) {
337 if (current->hash != hash) continue;
338 if (!strcmp(s, current->bytes)) return current->interned;
339 }
340 return NULL;
341}
342
343/**
344 * Returns an interned string for the given UTF-8 string.
345 *
346 * @param s null-terminated string to intern
347 * @returns interned Java string equivelent of s or NULL if s is null
348 */
Elliott Hughes4eafbcc2010-06-21 16:52:06 -0700349static jstring internString(JNIEnv* env, ParsingContext* parsingContext, const char* s) {
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700350 if (s == NULL) return NULL;
351
352 int hash = hashString(s);
353 int bucketIndex = hash & (BUCKET_COUNT - 1);
354
355 InternedString*** buckets = parsingContext->internedStrings;
356 InternedString** bucket = buckets[bucketIndex];
357 InternedString* internedString;
358
359 if (bucket) {
360 // We have a bucket already. Look for the given string.
361 jstring found = findInternedString(bucket, s, hash);
362 if (found) {
363 // We found it!
364 return found;
365 }
366
367 // We didn't find it. :(
368 // Create a new entry.
Elliott Hughes4e3714f2010-06-22 14:32:47 -0700369 internedString = newInternedString(env, s, hash);
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700370 if (internedString == NULL) return NULL;
371
372 // Expand the bucket.
373 bucket = expandInternedStringBucket(bucket, internedString);
374 if (bucket == NULL) {
375 throw_OutOfMemoryError(env);
376 return NULL;
377 }
378
379 buckets[bucketIndex] = bucket;
380
381 return internedString->interned;
382 } else {
383 // We don't even have a bucket yet. Create an entry.
Elliott Hughes4e3714f2010-06-22 14:32:47 -0700384 internedString = newInternedString(env, s, hash);
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700385 if (internedString == NULL) return NULL;
386
387 // Create a new bucket with one entry.
388 bucket = newInternedStringBucket(internedString);
389 if (bucket == NULL) {
390 throw_OutOfMemoryError(env);
391 return NULL;
392 }
393
394 buckets[bucketIndex] = bucket;
395
396 return internedString->interned;
397 }
398}
399
Elliott Hughes738f9502009-10-01 11:20:29 -0700400static void jniThrowExpatException(JNIEnv* env, XML_Error error) {
401 const char* message = XML_ErrorString(error);
402 jniThrowException(env, "org/apache/harmony/xml/ExpatException", message);
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700403}
404
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700405/**
406 * Copies UTF-8 characters into the buffer. Returns the number of Java chars
407 * which were buffered.
408 *
409 * @param characters to copy into the buffer
410 * @param length of characters to copy (in bytes)
411 * @returns number of UTF-16 characters which were copied
412 */
Elliott Hughesebca53a2010-05-20 20:54:45 -0700413static size_t fillBuffer(ParsingContext* parsingContext, const char* characters, int length) {
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700414 JNIEnv* env = parsingContext->env;
415
416 // Grow buffer if necessary.
Elliott Hughes4eafbcc2010-06-21 16:52:06 -0700417 jcharArray buffer = parsingContext->ensureCapacity(length);
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700418 if (buffer == NULL) return -1;
419
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700420 // Decode UTF-8 characters into our buffer.
Elliott Hughesebca53a2010-05-20 20:54:45 -0700421 ScopedCharArrayRW nativeBuffer(env, buffer);
Elliott Hughes64101122010-07-12 09:53:54 -0700422 if (nativeBuffer.get() == NULL) {
423 return -1;
424 }
425
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700426 size_t utf16length;
Elliott Hughesebca53a2010-05-20 20:54:45 -0700427 strcpylen8to16((char16_t*) nativeBuffer.get(), characters, length, &utf16length);
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700428 return utf16length;
429}
430
431/**
432 * Buffers the given text and passes it to the given method.
433 *
434 * @param method to pass the characters and length to with signature
435 * (char[], int)
436 * @param data parsing context
437 * @param text to copy into the buffer
438 * @param length of text to copy (in bytes)
439 */
Elliott Hughes4eafbcc2010-06-21 16:52:06 -0700440static void bufferAndInvoke(jmethodID method, void* data, const char* text, size_t length) {
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700441 ParsingContext* parsingContext = (ParsingContext*) data;
442 JNIEnv* env = parsingContext->env;
443
444 // Bail out if a previously called handler threw an exception.
445 if (env->ExceptionCheck()) return;
446
447 // Buffer the element name.
448 size_t utf16length = fillBuffer(parsingContext, text, length);
449
450 // Invoke given method.
451 jobject javaParser = parsingContext->object;
452 jcharArray buffer = parsingContext->buffer;
453 env->CallVoidMethod(javaParser, method, buffer, utf16length);
454}
455
456/**
Jesse Wilsondf36e8c2010-01-23 00:49:53 -0800457 * The component parts of an attribute or element name.
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700458 */
Jesse Wilsondf36e8c2010-01-23 00:49:53 -0800459class ExpatElementName {
460public:
461 ExpatElementName(JNIEnv* env, ParsingContext* parsingContext, jint attributePointer, jint index) {
462 const char** attributes = (const char**) attributePointer;
463 const char* name = attributes[index * 2];
464 init(env, parsingContext, name);
465 }
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700466
Jesse Wilsondf36e8c2010-01-23 00:49:53 -0800467 ExpatElementName(JNIEnv* env, ParsingContext* parsingContext, const char* s) {
468 init(env, parsingContext, s);
469 }
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700470
Jesse Wilsondf36e8c2010-01-23 00:49:53 -0800471 ~ExpatElementName() {
472 free(mCopy);
473 }
474
475 /**
476 * Returns the namespace URI, like "http://www.w3.org/1999/xhtml".
477 * Possibly empty.
478 */
479 jstring uri() {
480 return internString(mEnv, mParsingContext, mUri);
481 }
482
483 /**
Jesse Wilsona1440512010-01-29 17:06:44 -0800484 * Returns the element or attribute local name, like "h1". Never empty. When
485 * namespace processing is disabled, this may contain a prefix, yielding a
486 * local name like "html:h1". In such cases, the qName will always be empty.
Jesse Wilsondf36e8c2010-01-23 00:49:53 -0800487 */
488 jstring localName() {
489 return internString(mEnv, mParsingContext, mLocalName);
490 }
491
492 /**
493 * Returns the namespace prefix, like "html". Possibly empty.
494 */
495 jstring qName() {
496 if (*mPrefix == 0) {
497 return localName();
498 }
499
500 // return prefix + ":" + localName
501 LocalArray<1024> qName(strlen(mPrefix) + 1 + strlen(mLocalName) + 1);
502 snprintf(&qName[0], qName.size(), "%s:%s", mPrefix, mLocalName);
503 return internString(mEnv, mParsingContext, &qName[0]);
504 }
505
506 /**
507 * Returns true if this expat name has the same URI and local name.
508 */
509 bool matches(const char* uri, const char* localName) {
510 return strcmp(uri, mUri) == 0 && strcmp(localName, mLocalName) == 0;
511 }
512
513 /**
514 * Returns true if this expat name has the same qualified name.
515 */
516 bool matchesQName(const char* qName) {
Brian Carlstrombf87c562010-05-27 23:09:59 -0700517 const char* lastColon = strrchr(qName, ':');
Jesse Wilsondf36e8c2010-01-23 00:49:53 -0800518
Jesse Wilsona1440512010-01-29 17:06:44 -0800519 // Compare local names only if either:
520 // - the input qualified name doesn't have a colon (like "h1")
521 // - this element doesn't have a prefix. Such is the case when it
522 // doesn't belong to a namespace, or when this parser's namespace
523 // processing is disabled. In the latter case, this element's local
524 // name may still contain a colon (like "html:h1").
525 if (lastColon == NULL || *mPrefix == 0) {
526 return strcmp(qName, mLocalName) == 0;
Jesse Wilsondf36e8c2010-01-23 00:49:53 -0800527 }
528
Jesse Wilsona1440512010-01-29 17:06:44 -0800529 // Otherwise compare both prefix and local name
Jesse Wilsondf36e8c2010-01-23 00:49:53 -0800530 size_t prefixLength = lastColon - qName;
531 return strlen(mPrefix) == prefixLength
532 && strncmp(qName, mPrefix, prefixLength) == 0
533 && strcmp(lastColon + 1, mLocalName) == 0;
534 }
535
536private:
537 JNIEnv* mEnv;
538 ParsingContext* mParsingContext;
539 char* mCopy;
540 const char* mUri;
541 const char* mLocalName;
542 const char* mPrefix;
543
544 /**
545 * Decodes an Expat-encoded name of one of these three forms:
546 * "uri|localName|prefix" (example: "http://www.w3.org/1999/xhtml|h1|html")
547 * "uri|localName" (example: "http://www.w3.org/1999/xhtml|h1")
548 * "localName" (example: "h1")
549 */
550 void init(JNIEnv* env, ParsingContext* parsingContext, const char* s) {
551 mEnv = env;
552 mParsingContext = parsingContext;
553 mCopy = strdup(s);
554
555 // split the input into up to 3 parts: a|b|c
556 char* context = NULL;
557 char* a = strtok_r(mCopy, "|", &context);
558 char* b = strtok_r(NULL, "|", &context);
559 char* c = strtok_r(NULL, "|", &context);
560
561 if (c != NULL) { // input of the form "uri|localName|prefix"
562 mUri = a;
563 mLocalName = b;
564 mPrefix = c;
565 } else if (b != NULL) { // input of the form "uri|localName"
566 mUri = a;
567 mLocalName = b;
568 mPrefix = "";
569 } else { // input of the form "localName"
570 mLocalName = a;
571 mUri = "";
572 mPrefix = "";
573 }
574 }
Elliott Hughes7ca6fd02010-03-29 20:51:48 -0700575
576 // Disallow copy and assignment.
577 ExpatElementName(const ExpatElementName&);
578 void operator=(const ExpatElementName&);
Jesse Wilsondf36e8c2010-01-23 00:49:53 -0800579};
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700580
581/**
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700582 * Called by Expat at the start of an element. Delegates to the same method
583 * on the Java parser.
584 *
585 * @param data parsing context
586 * @param elementName "uri|localName" or "localName" for the current element
587 * @param attributes alternating attribute names and values. Like element
588 * names, attribute names follow the format "uri|localName" or "localName".
589 */
Elliott Hughes4eafbcc2010-06-21 16:52:06 -0700590static void startElement(void* data, const char* elementName, const char** attributes) {
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700591 ParsingContext* parsingContext = (ParsingContext*) data;
592 JNIEnv* env = parsingContext->env;
593
594 // Bail out if a previously called handler threw an exception.
595 if (env->ExceptionCheck()) return;
596
597 // Count the number of attributes.
598 int count = 0;
599 while (attributes[count << 1]) count++;
600
601 // Make the attributes available for the duration of this call.
602 parsingContext->attributes = attributes;
603 parsingContext->attributeCount = count;
604
605 jobject javaParser = parsingContext->object;
606
Jesse Wilsondf36e8c2010-01-23 00:49:53 -0800607 ExpatElementName e(env, parsingContext, elementName);
Jesse Wilson4a5c3fd2010-03-19 12:51:43 -0700608 jstring uri = parsingContext->processNamespaces ? e.uri() : emptyString;
609 jstring localName = parsingContext->processNamespaces ? e.localName() : emptyString;
Jesse Wilsondf36e8c2010-01-23 00:49:53 -0800610 jstring qName = e.qName();
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700611
Elliott Hughes4eafbcc2010-06-21 16:52:06 -0700612 parsingContext->stringStack.push(env, qName);
613 parsingContext->stringStack.push(env, uri);
614 parsingContext->stringStack.push(env, localName);
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700615
Elliott Hughes4eafbcc2010-06-21 16:52:06 -0700616 env->CallVoidMethod(javaParser, startElementMethod, uri, localName, qName, attributes, count);
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700617
618 parsingContext->attributes = NULL;
619 parsingContext->attributeCount = -1;
620}
621
622/**
623 * Called by Expat at the end of an element. Delegates to the same method
624 * on the Java parser.
625 *
626 * @param data parsing context
Elliott Hughes4e3714f2010-06-22 14:32:47 -0700627 * @param elementName "uri|localName" or "localName" for the current element;
628 * we assume that this matches the last data on our stack.
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700629 */
Elliott Hughes4e3714f2010-06-22 14:32:47 -0700630static void endElement(void* data, const char* /*elementName*/) {
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700631 ParsingContext* parsingContext = (ParsingContext*) data;
632 JNIEnv* env = parsingContext->env;
633
634 // Bail out if a previously called handler threw an exception.
635 if (env->ExceptionCheck()) return;
636
637 jobject javaParser = parsingContext->object;
638
Elliott Hughes4eafbcc2010-06-21 16:52:06 -0700639 jstring localName = parsingContext->stringStack.pop();
640 jstring uri = parsingContext->stringStack.pop();
641 jstring qName = parsingContext->stringStack.pop();
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700642
Jesse Wilsondf36e8c2010-01-23 00:49:53 -0800643 env->CallVoidMethod(javaParser, endElementMethod, uri, localName, qName);
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700644}
645
646/**
647 * Called by Expat when it encounters text. Delegates to the same method
648 * on the Java parser. This may be called mutiple times with incremental pieces
649 * of the same contiguous block of text.
650 *
651 * @param data parsing context
652 * @param characters buffer containing encountered text
653 * @param length number of characters in the buffer
654 */
655static void text(void* data, const char* characters, int length) {
656 bufferAndInvoke(textMethod, data, characters, length);
657}
658
659/**
660 * Called by Expat when it encounters a comment. Delegates to the same method
661 * on the Java parser.
662
663 * @param data parsing context
664 * @param comment 0-terminated
665 */
666static void comment(void* data, const char* comment) {
667 bufferAndInvoke(commentMethod, data, comment, strlen(comment));
668}
669
670/**
671 * Called by Expat at the beginning of a namespace mapping.
672 *
673 * @param data parsing context
674 * @param prefix null-terminated namespace prefix used in the XML
675 * @param uri of the namespace
676 */
677static void startNamespace(void* data, const char* prefix, const char* uri) {
678 ParsingContext* parsingContext = (ParsingContext*) data;
679 JNIEnv* env = parsingContext->env;
680
681 // Bail out if a previously called handler threw an exception.
682 if (env->ExceptionCheck()) return;
683
684 jstring internedPrefix = emptyString;
685 if (prefix != NULL) {
686 internedPrefix = internString(env, parsingContext, prefix);
687 if (env->ExceptionCheck()) return;
688 }
689
690 jstring internedUri = emptyString;
691 if (uri != NULL) {
692 internedUri = internString(env, parsingContext, uri);
693 if (env->ExceptionCheck()) return;
694 }
695
Elliott Hughes4eafbcc2010-06-21 16:52:06 -0700696 parsingContext->stringStack.push(env, internedPrefix);
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700697
698 jobject javaParser = parsingContext->object;
Elliott Hughes4eafbcc2010-06-21 16:52:06 -0700699 env->CallVoidMethod(javaParser, startNamespaceMethod, internedPrefix, internedUri);
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700700}
701
702/**
703 * Called by Expat at the end of a namespace mapping.
704 *
705 * @param data parsing context
Elliott Hughes4e3714f2010-06-22 14:32:47 -0700706 * @param prefix null-terminated namespace prefix used in the XML;
707 * we assume this is the same as the last prefix on the stack.
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700708 */
Elliott Hughes4e3714f2010-06-22 14:32:47 -0700709static void endNamespace(void* data, const char* /*prefix*/) {
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700710 ParsingContext* parsingContext = (ParsingContext*) data;
711 JNIEnv* env = parsingContext->env;
712
713 // Bail out if a previously called handler threw an exception.
714 if (env->ExceptionCheck()) return;
715
Elliott Hughes4eafbcc2010-06-21 16:52:06 -0700716 jstring internedPrefix = parsingContext->stringStack.pop();
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700717
718 jobject javaParser = parsingContext->object;
719 env->CallVoidMethod(javaParser, endNamespaceMethod, internedPrefix);
720}
721
722/**
723 * Called by Expat at the beginning of a CDATA section.
724 *
725 * @param data parsing context
726 */
727static void startCdata(void* data) {
728 ParsingContext* parsingContext = (ParsingContext*) data;
729 JNIEnv* env = parsingContext->env;
730
731 // Bail out if a previously called handler threw an exception.
732 if (env->ExceptionCheck()) return;
733
734 jobject javaParser = parsingContext->object;
735 env->CallVoidMethod(javaParser, startCdataMethod);
736}
737
738/**
739 * Called by Expat at the end of a CDATA section.
740 *
741 * @param data parsing context
742 */
743static void endCdata(void* data) {
744 ParsingContext* parsingContext = (ParsingContext*) data;
745 JNIEnv* env = parsingContext->env;
746
747 // Bail out if a previously called handler threw an exception.
748 if (env->ExceptionCheck()) return;
749
750 jobject javaParser = parsingContext->object;
751 env->CallVoidMethod(javaParser, endCdataMethod);
752}
753
754/**
755 * Called by Expat at the beginning of a DOCTYPE section.
Elliott Hughes4e3714f2010-06-22 14:32:47 -0700756 * Expat gives us 'hasInternalSubset', but the Java API doesn't expect it, so we don't need it.
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700757 */
758static void startDtd(void* data, const char* name,
Elliott Hughes4e3714f2010-06-22 14:32:47 -0700759 const char* systemId, const char* publicId, int /*hasInternalSubset*/) {
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700760 ParsingContext* parsingContext = (ParsingContext*) data;
761 JNIEnv* env = parsingContext->env;
762
763 // Bail out if a previously called handler threw an exception.
764 if (env->ExceptionCheck()) return;
765
766 jstring javaName = internString(env, parsingContext, name);
767 if (env->ExceptionCheck()) return;
768
769 jstring javaPublicId = internString(env, parsingContext, publicId);
770 if (env->ExceptionCheck()) return;
771
772 jstring javaSystemId = internString(env, parsingContext, systemId);
773 if (env->ExceptionCheck()) return;
774
775 jobject javaParser = parsingContext->object;
776 env->CallVoidMethod(javaParser, startDtdMethod, javaName, javaPublicId,
777 javaSystemId);
778}
779
780/**
781 * Called by Expat at the end of a DOCTYPE section.
782 *
783 * @param data parsing context
784 */
785static void endDtd(void* data) {
786 ParsingContext* parsingContext = (ParsingContext*) data;
787 JNIEnv* env = parsingContext->env;
788
789 // Bail out if a previously called handler threw an exception.
790 if (env->ExceptionCheck()) return;
791
792 jobject javaParser = parsingContext->object;
793 env->CallVoidMethod(javaParser, endDtdMethod);
794}
795
796/**
797 * Called by Expat when it encounters processing instructions.
798 *
799 * @param data parsing context
800 * @param target of the instruction
801 * @param instructionData
802 */
Elliott Hughes8044bf62010-05-17 22:34:46 -0700803static void processingInstruction(void* data, const char* target, const char* instructionData) {
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700804 ParsingContext* parsingContext = (ParsingContext*) data;
805 JNIEnv* env = parsingContext->env;
806
807 // Bail out if a previously called handler threw an exception.
808 if (env->ExceptionCheck()) return;
809
810 jstring javaTarget = internString(env, parsingContext, target);
811 if (env->ExceptionCheck()) return;
812
Elliott Hughes8044bf62010-05-17 22:34:46 -0700813 ScopedLocalRef<jstring> javaInstructionData(env, env->NewStringUTF(instructionData));
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700814 if (env->ExceptionCheck()) return;
815
816 jobject javaParser = parsingContext->object;
Elliott Hughes8044bf62010-05-17 22:34:46 -0700817 env->CallVoidMethod(javaParser, processingInstructionMethod, javaTarget, javaInstructionData.get());
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700818}
819
820/**
821 * Creates a new entity parser.
822 *
823 * @param object the Java ExpatParser instance
824 * @param parentParser pointer
825 * @param javaEncoding the character encoding name
826 * @param javaContext that was provided to handleExternalEntity
827 * @returns the pointer to the C Expat entity parser
828 */
Elliott Hughes4e3714f2010-06-22 14:32:47 -0700829static jint createEntityParser(JNIEnv* env, jobject, jint parentParser, jstring javaContext) {
Elliott Hughes05960872010-05-26 17:45:07 -0700830 ScopedUtfChars context(env, javaContext);
831 if (context.c_str() == NULL) {
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700832 return 0;
833 }
834
835 XML_Parser parent = (XML_Parser) parentParser;
Elliott Hughes05960872010-05-26 17:45:07 -0700836 XML_Parser entityParser = XML_ExternalEntityParserCreate(parent, context.c_str(), NULL);
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700837 if (entityParser == NULL) {
838 throw_OutOfMemoryError(env);
839 }
840
841 return (jint) entityParser;
842}
843
844/**
845 * Handles external entities. We ignore the "base" URI and keep track of it
846 * ourselves.
847 */
848static int handleExternalEntity(XML_Parser parser, const char* context,
Brian Carlstrom44e0e562010-05-06 23:44:16 -0700849 const char*, const char* systemId, const char* publicId) {
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700850 ParsingContext* parsingContext = (ParsingContext*) XML_GetUserData(parser);
851 jobject javaParser = parsingContext->object;
852 JNIEnv* env = parsingContext->env;
853 jobject object = parsingContext->object;
854
855 // Bail out if a previously called handler threw an exception.
856 if (env->ExceptionCheck()) {
857 return XML_STATUS_ERROR;
858 }
859
Elliott Hughes8044bf62010-05-17 22:34:46 -0700860 ScopedLocalRef<jstring> javaSystemId(env, env->NewStringUTF(systemId));
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700861 if (env->ExceptionCheck()) {
862 return XML_STATUS_ERROR;
863 }
Elliott Hughes8044bf62010-05-17 22:34:46 -0700864 ScopedLocalRef<jstring> javaPublicId(env, env->NewStringUTF(publicId));
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700865 if (env->ExceptionCheck()) {
866 return XML_STATUS_ERROR;
867 }
Elliott Hughes8044bf62010-05-17 22:34:46 -0700868 ScopedLocalRef<jstring> javaContext(env, env->NewStringUTF(context));
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700869 if (env->ExceptionCheck()) {
870 return XML_STATUS_ERROR;
871 }
872
873 // Pass the wrapped parser and both strings to java.
Elliott Hughes8044bf62010-05-17 22:34:46 -0700874 env->CallVoidMethod(javaParser, handleExternalEntityMethod, javaContext.get(),
875 javaPublicId.get(), javaSystemId.get());
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700876
877 /*
878 * Parsing the external entity leaves parsingContext->env and object set to
879 * NULL, so we need to restore both.
880 *
881 * TODO: consider restoring the original env and object instead of setting
882 * them to NULL in the append() functions.
883 */
884 parsingContext->env = env;
885 parsingContext->object = object;
886
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700887 return env->ExceptionCheck() ? XML_STATUS_ERROR : XML_STATUS_OK;
888}
889
Elliott Hughes4e3714f2010-06-22 14:32:47 -0700890/**
891 * Expat gives us 'base', but the Java API doesn't expect it, so we don't need it.
892 */
893static void unparsedEntityDecl(void* data, const char* name, const char* /*base*/, const char* systemId, const char* publicId, const char* notationName) {
Elliott Hughesc9b92b42009-12-09 16:05:29 -0800894 ParsingContext* parsingContext = reinterpret_cast<ParsingContext*>(data);
895 jobject javaParser = parsingContext->object;
896 JNIEnv* env = parsingContext->env;
Elliott Hughes99c59bf2010-05-17 16:22:04 -0700897
Elliott Hughesc9b92b42009-12-09 16:05:29 -0800898 // Bail out if a previously called handler threw an exception.
899 if (env->ExceptionCheck()) return;
Elliott Hughes99c59bf2010-05-17 16:22:04 -0700900
Elliott Hughes8044bf62010-05-17 22:34:46 -0700901 ScopedLocalRef<jstring> javaName(env, env->NewStringUTF(name));
Elliott Hughesc9b92b42009-12-09 16:05:29 -0800902 if (env->ExceptionCheck()) return;
Elliott Hughes8044bf62010-05-17 22:34:46 -0700903 ScopedLocalRef<jstring> javaPublicId(env, env->NewStringUTF(publicId));
Elliott Hughesc9b92b42009-12-09 16:05:29 -0800904 if (env->ExceptionCheck()) return;
Elliott Hughes8044bf62010-05-17 22:34:46 -0700905 ScopedLocalRef<jstring> javaSystemId(env, env->NewStringUTF(systemId));
Elliott Hughesc9b92b42009-12-09 16:05:29 -0800906 if (env->ExceptionCheck()) return;
Elliott Hughes8044bf62010-05-17 22:34:46 -0700907 ScopedLocalRef<jstring> javaNotationName(env, env->NewStringUTF(notationName));
Elliott Hughesc9b92b42009-12-09 16:05:29 -0800908 if (env->ExceptionCheck()) return;
Elliott Hughes99c59bf2010-05-17 16:22:04 -0700909
Elliott Hughes8044bf62010-05-17 22:34:46 -0700910 env->CallVoidMethod(javaParser, unparsedEntityDeclMethod, javaName.get(), javaPublicId.get(), javaSystemId.get(), javaNotationName.get());
Elliott Hughesc9b92b42009-12-09 16:05:29 -0800911}
912
Elliott Hughes4e3714f2010-06-22 14:32:47 -0700913/**
914 * Expat gives us 'base', but the Java API doesn't expect it, so we don't need it.
915 */
916static void notationDecl(void* data, const char* name, const char* /*base*/, const char* systemId, const char* publicId) {
Elliott Hughesc9b92b42009-12-09 16:05:29 -0800917 ParsingContext* parsingContext = reinterpret_cast<ParsingContext*>(data);
918 jobject javaParser = parsingContext->object;
919 JNIEnv* env = parsingContext->env;
Elliott Hughes99c59bf2010-05-17 16:22:04 -0700920
Elliott Hughesc9b92b42009-12-09 16:05:29 -0800921 // Bail out if a previously called handler threw an exception.
922 if (env->ExceptionCheck()) return;
Elliott Hughes99c59bf2010-05-17 16:22:04 -0700923
Elliott Hughes8044bf62010-05-17 22:34:46 -0700924 ScopedLocalRef<jstring> javaName(env, env->NewStringUTF(name));
Elliott Hughesc9b92b42009-12-09 16:05:29 -0800925 if (env->ExceptionCheck()) return;
Elliott Hughes8044bf62010-05-17 22:34:46 -0700926 ScopedLocalRef<jstring> javaPublicId(env, env->NewStringUTF(publicId));
Elliott Hughesc9b92b42009-12-09 16:05:29 -0800927 if (env->ExceptionCheck()) return;
Elliott Hughes8044bf62010-05-17 22:34:46 -0700928 ScopedLocalRef<jstring> javaSystemId(env, env->NewStringUTF(systemId));
Elliott Hughesc9b92b42009-12-09 16:05:29 -0800929 if (env->ExceptionCheck()) return;
Elliott Hughes99c59bf2010-05-17 16:22:04 -0700930
Elliott Hughes8044bf62010-05-17 22:34:46 -0700931 env->CallVoidMethod(javaParser, notationDeclMethod, javaName.get(), javaPublicId.get(), javaSystemId.get());
Elliott Hughesc9b92b42009-12-09 16:05:29 -0800932}
933
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700934/**
Jesse Wilsona1440512010-01-29 17:06:44 -0800935 * Creates a new Expat parser. Called from the Java ExpatParser constructor.
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700936 *
937 * @param object the Java ExpatParser instance
938 * @param javaEncoding the character encoding name
939 * @param processNamespaces true if the parser should handle namespaces
940 * @returns the pointer to the C Expat parser
941 */
942static jint initialize(JNIEnv* env, jobject object, jstring javaEncoding,
943 jboolean processNamespaces) {
944 // Allocate parsing context.
Elliott Hughes4eafbcc2010-06-21 16:52:06 -0700945 UniquePtr<ParsingContext> context(new ParsingContext(object));
946 if (context.get() == NULL) {
947 throw_OutOfMemoryError(env);
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700948 return 0;
949 }
950
951 context->processNamespaces = (bool) processNamespaces;
952
953 // Create a parser.
954 XML_Parser parser;
Elliott Hughes05960872010-05-26 17:45:07 -0700955 ScopedUtfChars encoding(env, javaEncoding);
956 if (encoding.c_str() == NULL) {
957 return 0;
958 }
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700959 if (processNamespaces) {
960 // Use '|' to separate URIs from local names.
Elliott Hughes05960872010-05-26 17:45:07 -0700961 parser = XML_ParserCreateNS(encoding.c_str(), '|');
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700962 } else {
Elliott Hughes05960872010-05-26 17:45:07 -0700963 parser = XML_ParserCreate(encoding.c_str());
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700964 }
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700965
966 if (parser != NULL) {
967 if (processNamespaces) {
968 XML_SetNamespaceDeclHandler(parser, startNamespace, endNamespace);
Jesse Wilsondf36e8c2010-01-23 00:49:53 -0800969 XML_SetReturnNSTriplet(parser, 1);
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700970 }
971
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700972 XML_SetCdataSectionHandler(parser, startCdata, endCdata);
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700973 XML_SetCharacterDataHandler(parser, text);
Elliott Hughesc9b92b42009-12-09 16:05:29 -0800974 XML_SetCommentHandler(parser, comment);
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700975 XML_SetDoctypeDeclHandler(parser, startDtd, endDtd);
Elliott Hughesc9b92b42009-12-09 16:05:29 -0800976 XML_SetElementHandler(parser, startElement, endElement);
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700977 XML_SetExternalEntityRefHandler(parser, handleExternalEntity);
Elliott Hughesc9b92b42009-12-09 16:05:29 -0800978 XML_SetNotationDeclHandler(parser, notationDecl);
979 XML_SetProcessingInstructionHandler(parser, processingInstruction);
980 XML_SetUnparsedEntityDeclHandler(parser, unparsedEntityDecl);
Elliott Hughes4eafbcc2010-06-21 16:52:06 -0700981 XML_SetUserData(parser, context.release());
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700982 } else {
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700983 throw_OutOfMemoryError(env);
984 return 0;
985 }
986
987 return (jint) parser;
988}
989
990/**
Elliott Hughes99c59bf2010-05-17 16:22:04 -0700991 * Expat decides for itself what character encoding it's looking at. The interface is in terms of
992 * bytes, which may point to UTF-8, UTF-16, ISO-8859-1, or US-ASCII. appendBytes, appendCharacters,
993 * and appendString thus all call through to this method, strange though that appears.
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700994 */
Elliott Hughes99c59bf2010-05-17 16:22:04 -0700995static void append(JNIEnv* env, jobject object, jint pointer,
996 const char* bytes, size_t byteOffset, size_t byteCount, jboolean isFinal) {
The Android Open Source Projectfdb27042008-10-21 07:00:00 -0700997 XML_Parser parser = (XML_Parser) pointer;
998 ParsingContext* context = (ParsingContext*) XML_GetUserData(parser);
999 context->env = env;
1000 context->object = object;
Elliott Hughes05960872010-05-26 17:45:07 -07001001 if (!XML_Parse(parser, bytes + byteOffset, byteCount, isFinal) && !env->ExceptionCheck()) {
Elliott Hughes738f9502009-10-01 11:20:29 -07001002 jniThrowExpatException(env, XML_GetErrorCode(parser));
The Android Open Source Projectfdb27042008-10-21 07:00:00 -07001003 }
The Android Open Source Projectfdb27042008-10-21 07:00:00 -07001004 context->object = NULL;
1005 context->env = NULL;
1006}
1007
The Android Open Source Projectfdb27042008-10-21 07:00:00 -07001008static void appendBytes(JNIEnv* env, jobject object, jint pointer,
Elliott Hughes99c59bf2010-05-17 16:22:04 -07001009 jbyteArray xml, jint byteOffset, jint byteCount) {
Elliott Hughesebca53a2010-05-20 20:54:45 -07001010 ScopedByteArrayRO byteArray(env, xml);
Elliott Hughes64101122010-07-12 09:53:54 -07001011 if (byteArray.get() == NULL) {
1012 return;
1013 }
1014
Elliott Hughes99c59bf2010-05-17 16:22:04 -07001015 const char* bytes = reinterpret_cast<const char*>(byteArray.get());
1016 append(env, object, pointer, bytes, byteOffset, byteCount, XML_FALSE);
1017}
The Android Open Source Projectfdb27042008-10-21 07:00:00 -07001018
Elliott Hughes99c59bf2010-05-17 16:22:04 -07001019static void appendCharacters(JNIEnv* env, jobject object, jint pointer,
1020 jcharArray xml, jint charOffset, jint charCount) {
Elliott Hughesebca53a2010-05-20 20:54:45 -07001021 ScopedCharArrayRO charArray(env, xml);
Elliott Hughes64101122010-07-12 09:53:54 -07001022 if (charArray.get() == NULL) {
1023 return;
1024 }
1025
Elliott Hughes99c59bf2010-05-17 16:22:04 -07001026 const char* bytes = reinterpret_cast<const char*>(charArray.get());
1027 size_t byteOffset = 2 * charOffset;
1028 size_t byteCount = 2 * charCount;
1029 append(env, object, pointer, bytes, byteOffset, byteCount, XML_FALSE);
1030}
The Android Open Source Projectfdb27042008-10-21 07:00:00 -07001031
Elliott Hughes4e3714f2010-06-22 14:32:47 -07001032static void appendString(JNIEnv* env, jobject object, jint pointer,
1033 jstring javaXml, jboolean isFinal) {
1034 ScopedJavaUnicodeString xml(env, javaXml);
1035 const char* bytes = reinterpret_cast<const char*>(xml.unicodeString().getBuffer());
1036 size_t byteCount = 2 * xml.unicodeString().length();
Elliott Hughes99c59bf2010-05-17 16:22:04 -07001037 append(env, object, pointer, bytes, 0, byteCount, isFinal);
The Android Open Source Projectfdb27042008-10-21 07:00:00 -07001038}
1039
1040/**
1041 * Releases parser only.
1042 *
1043 * @param object the Java ExpatParser instance
1044 * @param i pointer to the C expat parser
1045 */
Brian Carlstrom44e0e562010-05-06 23:44:16 -07001046static void releaseParser(JNIEnv*, jobject, jint i) {
The Android Open Source Projectfdb27042008-10-21 07:00:00 -07001047 XML_Parser parser = (XML_Parser) i;
1048 XML_ParserFree(parser);
1049}
1050
1051/**
1052 * Cleans up after the parser. Called at garbage collection time.
1053 *
1054 * @param object the Java ExpatParser instance
1055 * @param i pointer to the C expat parser
1056 */
Brian Carlstrom44e0e562010-05-06 23:44:16 -07001057static void release(JNIEnv* env, jobject, jint i) {
The Android Open Source Projectfdb27042008-10-21 07:00:00 -07001058 XML_Parser parser = (XML_Parser) i;
1059
1060 ParsingContext* context = (ParsingContext*) XML_GetUserData(parser);
Elliott Hughes4eafbcc2010-06-21 16:52:06 -07001061 context->env = env;
1062 delete context;
The Android Open Source Projectfdb27042008-10-21 07:00:00 -07001063
1064 XML_ParserFree(parser);
1065}
1066
1067/**
1068 * Gets the current line.
1069 *
1070 * @param object the Java ExpatParser instance
1071 * @param pointer to the C expat parser
1072 * @returns current line number
1073 */
Brian Carlstrom44e0e562010-05-06 23:44:16 -07001074static int line(JNIEnv*, jobject, jint pointer) {
The Android Open Source Projectfdb27042008-10-21 07:00:00 -07001075 XML_Parser parser = (XML_Parser) pointer;
1076 return XML_GetCurrentLineNumber(parser);
1077}
1078
1079/**
1080 * Gets the current column.
1081 *
1082 * @param object the Java ExpatParser instance
1083 * @param pointer to the C expat parser
1084 * @returns current column number
1085 */
Brian Carlstrom44e0e562010-05-06 23:44:16 -07001086static int column(JNIEnv*, jobject, jint pointer) {
The Android Open Source Projectfdb27042008-10-21 07:00:00 -07001087 XML_Parser parser = (XML_Parser) pointer;
1088 return XML_GetCurrentColumnNumber(parser);
1089}
1090
1091/**
1092 * Gets the URI of the attribute at the given index.
1093 *
1094 * @param object Java ExpatParser instance
1095 * @param pointer to the C expat parser
1096 * @param attributePointer to the attribute array
1097 * @param index of the attribute
1098 * @returns interned Java string containing attribute's URI
1099 */
Brian Carlstrom44e0e562010-05-06 23:44:16 -07001100static jstring getAttributeURI(JNIEnv* env, jobject, jint pointer,
The Android Open Source Projectfdb27042008-10-21 07:00:00 -07001101 jint attributePointer, jint index) {
1102 XML_Parser parser = (XML_Parser) pointer;
1103 ParsingContext* context = (ParsingContext*) XML_GetUserData(parser);
Jesse Wilsondf36e8c2010-01-23 00:49:53 -08001104 return ExpatElementName(env, context, attributePointer, index).uri();
The Android Open Source Projectfdb27042008-10-21 07:00:00 -07001105}
1106
1107/**
1108 * Gets the local name of the attribute at the given index.
1109 *
1110 * @param object Java ExpatParser instance
1111 * @param pointer to the C expat parser
1112 * @param attributePointer to the attribute array
1113 * @param index of the attribute
1114 * @returns interned Java string containing attribute's local name
1115 */
Brian Carlstrom44e0e562010-05-06 23:44:16 -07001116static jstring getAttributeLocalName(JNIEnv* env, jobject, jint pointer,
The Android Open Source Projectfdb27042008-10-21 07:00:00 -07001117 jint attributePointer, jint index) {
1118 XML_Parser parser = (XML_Parser) pointer;
1119 ParsingContext* context = (ParsingContext*) XML_GetUserData(parser);
Jesse Wilsondf36e8c2010-01-23 00:49:53 -08001120 return ExpatElementName(env, context, attributePointer, index).localName();
The Android Open Source Projectfdb27042008-10-21 07:00:00 -07001121}
1122
1123/**
1124 * Gets the qualified name of the attribute at the given index.
1125 *
1126 * @param object Java ExpatParser instance
1127 * @param pointer to the C expat parser
1128 * @param attributePointer to the attribute array
1129 * @param index of the attribute
1130 * @returns interned Java string containing attribute's local name
1131 */
Brian Carlstrom44e0e562010-05-06 23:44:16 -07001132static jstring getAttributeQName(JNIEnv* env, jobject, jint pointer,
The Android Open Source Projectfdb27042008-10-21 07:00:00 -07001133 jint attributePointer, jint index) {
1134 XML_Parser parser = (XML_Parser) pointer;
1135 ParsingContext* context = (ParsingContext*) XML_GetUserData(parser);
Jesse Wilsondf36e8c2010-01-23 00:49:53 -08001136 return ExpatElementName(env, context, attributePointer, index).qName();
The Android Open Source Projectfdb27042008-10-21 07:00:00 -07001137}
1138
1139/**
1140 * Gets the value of the attribute at the given index.
1141 *
1142 * @param object Java ExpatParser instance
1143 * @param attributePointer to the attribute array
1144 * @param index of the attribute
1145 * @returns Java string containing attribute's value
1146 */
Elliott Hughes05960872010-05-26 17:45:07 -07001147static jstring getAttributeValueByIndex(JNIEnv* env, jobject, jint attributePointer, jint index) {
The Android Open Source Projectfdb27042008-10-21 07:00:00 -07001148 const char** attributes = (const char**) attributePointer;
1149 const char* value = attributes[(index << 1) + 1];
1150 return env->NewStringUTF(value);
1151}
1152
1153/**
The Android Open Source Projectfdb27042008-10-21 07:00:00 -07001154 * Gets the index of the attribute with the given qualified name.
1155 *
1156 * @param attributePointer to the attribute array
1157 * @param qName to look for
1158 * @returns index of attribute with the given uri and local name or -1 if not
1159 * found
1160 */
Elliott Hughes05960872010-05-26 17:45:07 -07001161static jint getAttributeIndexForQName(JNIEnv* env, jobject, jint attributePointer, jstring qName) {
The Android Open Source Projectfdb27042008-10-21 07:00:00 -07001162 const char** attributes = (const char**) attributePointer;
The Android Open Source Projectfdb27042008-10-21 07:00:00 -07001163
Elliott Hughes05960872010-05-26 17:45:07 -07001164 ScopedUtfChars qNameBytes(env, qName);
1165 if (qNameBytes.c_str() == NULL) {
Jesse Wilsondf36e8c2010-01-23 00:49:53 -08001166 return -1;
1167 }
1168
1169 int found = -1;
1170 for (int index = 0; attributes[index * 2]; ++index) {
Elliott Hughes05960872010-05-26 17:45:07 -07001171 if (ExpatElementName(NULL, NULL, attributePointer, index).matchesQName(qNameBytes.c_str())) {
Jesse Wilsondf36e8c2010-01-23 00:49:53 -08001172 found = index;
1173 break;
1174 }
1175 }
1176
The Android Open Source Projectfdb27042008-10-21 07:00:00 -07001177 return found;
1178}
1179
1180/**
1181 * Gets the index of the attribute with the given URI and name.
1182 *
1183 * @param attributePointer to the attribute array
1184 * @param uri to look for
1185 * @param localName to look for
1186 * @returns index of attribute with the given uri and local name or -1 if not
1187 * found
1188 */
Elliott Hughes05960872010-05-26 17:45:07 -07001189static jint getAttributeIndex(JNIEnv* env, jobject, jint attributePointer,
1190 jstring uri, jstring localName) {
1191 ScopedUtfChars uriBytes(env, uri);
1192 if (uriBytes.c_str() == NULL) {
1193 return -1;
1194 }
1195
1196 ScopedUtfChars localNameBytes(env, localName);
1197 if (localNameBytes.c_str() == NULL) {
1198 return -1;
1199 }
1200
The Android Open Source Projectfdb27042008-10-21 07:00:00 -07001201 const char** attributes = (const char**) attributePointer;
Jesse Wilsondf36e8c2010-01-23 00:49:53 -08001202 for (int index = 0; attributes[index * 2]; ++index) {
Elliott Hughes05960872010-05-26 17:45:07 -07001203 if (ExpatElementName(NULL, NULL, attributePointer, index).matches(uriBytes.c_str(),
1204 localNameBytes.c_str())) {
1205 return index;
Jesse Wilsondf36e8c2010-01-23 00:49:53 -08001206 }
1207 }
Elliott Hughes05960872010-05-26 17:45:07 -07001208 return -1;
The Android Open Source Projectfdb27042008-10-21 07:00:00 -07001209}
1210
1211/**
1212 * Gets the value of the attribute with the given qualified name.
1213 *
1214 * @param attributePointer to the attribute array
1215 * @param uri to look for
1216 * @param localName to look for
1217 * @returns value of attribute with the given uri and local name or NULL if not
1218 * found
1219 */
1220static jstring getAttributeValueForQName(JNIEnv* env, jobject clazz,
1221 jint attributePointer, jstring qName) {
Elliott Hughes05960872010-05-26 17:45:07 -07001222 jint index = getAttributeIndexForQName(env, clazz, attributePointer, qName);
1223 return index == -1 ? NULL : getAttributeValueByIndex(env, clazz, attributePointer, index);
The Android Open Source Projectfdb27042008-10-21 07:00:00 -07001224}
1225
1226/**
1227 * Gets the value of the attribute with the given URI and name.
1228 *
1229 * @param attributePointer to the attribute array
1230 * @param uri to look for
1231 * @param localName to look for
1232 * @returns value of attribute with the given uri and local name or NULL if not
1233 * found
1234 */
1235static jstring getAttributeValue(JNIEnv* env, jobject clazz,
1236 jint attributePointer, jstring uri, jstring localName) {
Elliott Hughes05960872010-05-26 17:45:07 -07001237 jint index = getAttributeIndex(env, clazz, attributePointer, uri, localName);
1238 return index == -1 ? NULL : getAttributeValueByIndex(env, clazz, attributePointer, index);
The Android Open Source Projectfdb27042008-10-21 07:00:00 -07001239}
1240
1241/**
1242 * Clones an array of strings. Uses one contiguous block of memory so as to
1243 * maximize performance.
Elliott Hughes91499042010-06-21 18:06:12 -07001244 *
1245 * @param address char** to clone
1246 * @param count number of attributes
The Android Open Source Projectfdb27042008-10-21 07:00:00 -07001247 */
Elliott Hughes91499042010-06-21 18:06:12 -07001248static jint cloneAttributes(JNIEnv* env, jobject, jint address, jint count) {
1249 const char** source = reinterpret_cast<const char**>(static_cast<uintptr_t>(address));
1250 count <<= 1;
1251
The Android Open Source Projectfdb27042008-10-21 07:00:00 -07001252 // Figure out how big the buffer needs to be.
1253 int arraySize = (count + 1) * sizeof(char*);
1254 int totalSize = arraySize;
1255 int stringLengths[count];
1256 for (int i = 0; i < count; i++) {
1257 int length = strlen(source[i]);
1258 stringLengths[i] = length;
1259 totalSize += length + 1;
1260 }
1261
Elliott Hughes4eafbcc2010-06-21 16:52:06 -07001262 char* buffer = new char[totalSize];
The Android Open Source Projectfdb27042008-10-21 07:00:00 -07001263 if (buffer == NULL) {
Elliott Hughes4eafbcc2010-06-21 16:52:06 -07001264 throw_OutOfMemoryError(env);
The Android Open Source Projectfdb27042008-10-21 07:00:00 -07001265 return NULL;
1266 }
1267
1268 // Array is at the beginning of the buffer.
1269 char** clonedArray = (char**) buffer;
1270 clonedArray[count] = NULL; // null terminate
1271
Elliott Hughes91499042010-06-21 18:06:12 -07001272 // String data follows immediately after.
The Android Open Source Projectfdb27042008-10-21 07:00:00 -07001273 char* destinationString = buffer + arraySize;
The Android Open Source Projectfdb27042008-10-21 07:00:00 -07001274 for (int i = 0; i < count; i++) {
1275 const char* sourceString = source[i];
1276 int stringLength = stringLengths[i];
1277 memcpy(destinationString, sourceString, stringLength + 1);
1278 clonedArray[i] = destinationString;
1279 destinationString += stringLength + 1;
1280 }
1281
Elliott Hughes91499042010-06-21 18:06:12 -07001282 return static_cast<jint>(reinterpret_cast<uintptr_t>(buffer));
The Android Open Source Projectfdb27042008-10-21 07:00:00 -07001283}
1284
1285/**
1286 * Frees cloned attributes.
1287 */
Brian Carlstrom44e0e562010-05-06 23:44:16 -07001288static void freeAttributes(JNIEnv*, jobject, jint pointer) {
Elliott Hughes4eafbcc2010-06-21 16:52:06 -07001289 delete[] reinterpret_cast<char*>(static_cast<uintptr_t>(pointer));
The Android Open Source Projectfdb27042008-10-21 07:00:00 -07001290}
1291
1292/**
1293 * Called when we initialize our Java parser class.
1294 *
1295 * @param clazz Java ExpatParser class
1296 */
Elliott Hughesc9b92b42009-12-09 16:05:29 -08001297static void staticInitialize(JNIEnv* env, jobject classObject, jstring empty) {
1298 jclass clazz = reinterpret_cast<jclass>(classObject);
1299 startElementMethod = env->GetMethodID(clazz, "startElement",
The Android Open Source Projectfdb27042008-10-21 07:00:00 -07001300 "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;II)V");
1301 if (startElementMethod == NULL) return;
Elliott Hughes99c59bf2010-05-17 16:22:04 -07001302
Elliott Hughesc9b92b42009-12-09 16:05:29 -08001303 endElementMethod = env->GetMethodID(clazz, "endElement",
The Android Open Source Projectfdb27042008-10-21 07:00:00 -07001304 "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
1305 if (endElementMethod == NULL) return;
1306
Elliott Hughesc9b92b42009-12-09 16:05:29 -08001307 textMethod = env->GetMethodID(clazz, "text", "([CI)V");
The Android Open Source Projectfdb27042008-10-21 07:00:00 -07001308 if (textMethod == NULL) return;
1309
Elliott Hughesc9b92b42009-12-09 16:05:29 -08001310 commentMethod = env->GetMethodID(clazz, "comment", "([CI)V");
The Android Open Source Projectfdb27042008-10-21 07:00:00 -07001311 if (commentMethod == NULL) return;
1312
Elliott Hughesc9b92b42009-12-09 16:05:29 -08001313 startCdataMethod = env->GetMethodID(clazz, "startCdata", "()V");
The Android Open Source Projectfdb27042008-10-21 07:00:00 -07001314 if (startCdataMethod == NULL) return;
1315
Elliott Hughesc9b92b42009-12-09 16:05:29 -08001316 endCdataMethod = env->GetMethodID(clazz, "endCdata", "()V");
The Android Open Source Projectfdb27042008-10-21 07:00:00 -07001317 if (endCdataMethod == NULL) return;
1318
Elliott Hughesc9b92b42009-12-09 16:05:29 -08001319 startDtdMethod = env->GetMethodID(clazz, "startDtd",
The Android Open Source Projectfdb27042008-10-21 07:00:00 -07001320 "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
1321 if (startDtdMethod == NULL) return;
1322
Elliott Hughesc9b92b42009-12-09 16:05:29 -08001323 endDtdMethod = env->GetMethodID(clazz, "endDtd", "()V");
The Android Open Source Projectfdb27042008-10-21 07:00:00 -07001324 if (endDtdMethod == NULL) return;
1325
Elliott Hughesc9b92b42009-12-09 16:05:29 -08001326 startNamespaceMethod = env->GetMethodID(clazz, "startNamespace",
The Android Open Source Projectfdb27042008-10-21 07:00:00 -07001327 "(Ljava/lang/String;Ljava/lang/String;)V");
1328 if (startNamespaceMethod == NULL) return;
1329
Elliott Hughesc9b92b42009-12-09 16:05:29 -08001330 endNamespaceMethod = env->GetMethodID(clazz, "endNamespace",
The Android Open Source Projectfdb27042008-10-21 07:00:00 -07001331 "(Ljava/lang/String;)V");
1332 if (endNamespaceMethod == NULL) return;
1333
Elliott Hughesc9b92b42009-12-09 16:05:29 -08001334 processingInstructionMethod = env->GetMethodID(clazz,
The Android Open Source Projectfdb27042008-10-21 07:00:00 -07001335 "processingInstruction", "(Ljava/lang/String;Ljava/lang/String;)V");
1336 if (processingInstructionMethod == NULL) return;
1337
Elliott Hughesc9b92b42009-12-09 16:05:29 -08001338 handleExternalEntityMethod = env->GetMethodID(clazz,
The Android Open Source Projectfdb27042008-10-21 07:00:00 -07001339 "handleExternalEntity",
1340 "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
1341 if (handleExternalEntityMethod == NULL) return;
1342
Elliott Hughesc9b92b42009-12-09 16:05:29 -08001343 notationDeclMethod = env->GetMethodID(clazz, "notationDecl",
1344 "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
1345 if (notationDeclMethod == NULL) return;
1346
1347 unparsedEntityDeclMethod = env->GetMethodID(clazz, "unparsedEntityDecl",
1348 "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
1349 if (unparsedEntityDeclMethod == NULL) return;
1350
Elliott Hughesa9f5c162010-06-16 16:32:18 -07001351 internMethod = env->GetMethodID(JniConstants::stringClass, "intern", "()Ljava/lang/String;");
The Android Open Source Projectfdb27042008-10-21 07:00:00 -07001352 if (internMethod == NULL) return;
1353
1354 // Reference to "".
1355 emptyString = (jstring) env->NewGlobalRef(empty);
1356}
1357
1358static JNINativeMethod parserMethods[] = {
1359 { "line", "(I)I", (void*) line },
1360 { "column", "(I)I", (void*) column },
1361 { "release", "(I)V", (void*) release },
1362 { "releaseParser", "(I)V", (void*) releaseParser },
1363 { "append", "(ILjava/lang/String;Z)V", (void*) appendString },
1364 { "append", "(I[CII)V", (void*) appendCharacters },
1365 { "append", "(I[BII)V", (void*) appendBytes },
Elliott Hughes05960872010-05-26 17:45:07 -07001366 { "initialize", "(Ljava/lang/String;Z)I", (void*) initialize},
Elliott Hughes4e3714f2010-06-22 14:32:47 -07001367 { "createEntityParser", "(ILjava/lang/String;)I", (void*) createEntityParser},
The Android Open Source Projectfdb27042008-10-21 07:00:00 -07001368 { "staticInitialize", "(Ljava/lang/String;)V", (void*) staticInitialize},
1369 { "cloneAttributes", "(II)I", (void*) cloneAttributes },
1370};
1371
1372static JNINativeMethod attributeMethods[] = {
1373 { "getURI", "(III)Ljava/lang/String;", (void*) getAttributeURI },
1374 { "getLocalName", "(III)Ljava/lang/String;", (void*) getAttributeLocalName },
1375 { "getQName", "(III)Ljava/lang/String;", (void*) getAttributeQName },
1376 { "getValue", "(II)Ljava/lang/String;", (void*) getAttributeValueByIndex },
1377 { "getIndex", "(ILjava/lang/String;Ljava/lang/String;)I",
1378 (void*) getAttributeIndex },
1379 { "getIndex", "(ILjava/lang/String;)I",
1380 (void*) getAttributeIndexForQName },
1381 { "getValue", "(ILjava/lang/String;Ljava/lang/String;)Ljava/lang/String;",
1382 (void*) getAttributeValue },
1383 { "getValue", "(ILjava/lang/String;)Ljava/lang/String;",
1384 (void*) getAttributeValueForQName },
1385 { "freeAttributes", "(I)V", (void*) freeAttributes },
1386};
Elliott Hughesc08f9fb2010-04-16 17:44:12 -07001387int register_org_apache_harmony_xml_ExpatParser(JNIEnv* env) {
The Android Open Source Projectfdb27042008-10-21 07:00:00 -07001388 int result = jniRegisterNativeMethods(env, "org/apache/harmony/xml/ExpatParser",
1389 parserMethods, NELEM(parserMethods));
1390 if (result != 0) {
1391 return result;
1392 }
1393
1394 return jniRegisterNativeMethods(env, "org/apache/harmony/xml/ExpatAttributes",
1395 attributeMethods, NELEM(attributeMethods));
1396}