blob: f6f812c8d2a94bbbff3516f8276bf8d7af3678ac [file] [log] [blame]
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#define LOG_TAG "ExpatParser"
#include "JNIHelp.h"
#include "JniConstants.h"
#include "JniException.h"
#include "LocalArray.h"
#include "ScopedLocalRef.h"
#include "ScopedPrimitiveArray.h"
#include "ScopedStringChars.h"
#include "ScopedUtfChars.h"
#include "jni.h"
#include "cutils/log.h"
#include "unicode/unistr.h"
#include <memory>
#include <string.h>
#include <expat.h>
#define BUCKET_COUNT 128
/**
* Wrapper around an interned string.
*/
struct InternedString {
InternedString() : interned(NULL), bytes(NULL) {
}
~InternedString() {
delete[] bytes;
}
/** The interned string itself. */
jstring interned;
/** UTF-8 equivalent of the interned string. */
const char* bytes;
/** Hash code of the interned string. */
int hash;
};
/**
* Keeps track of strings between start and end events.
*/
class StringStack {
public:
StringStack() : array(new jstring[DEFAULT_CAPACITY]), capacity(DEFAULT_CAPACITY), size(0) {
}
~StringStack() {
delete[] array;
}
void push(JNIEnv* env, jstring s) {
if (size == capacity) {
int newCapacity = capacity * 2;
jstring* newArray = new jstring[newCapacity];
if (newArray == NULL) {
jniThrowOutOfMemoryError(env, NULL);
return;
}
memcpy(newArray, array, capacity * sizeof(jstring));
delete[] array;
array = newArray;
capacity = newCapacity;
}
array[size++] = s;
}
jstring pop() {
return (size == 0) ? NULL : array[--size];
}
private:
enum { DEFAULT_CAPACITY = 10 };
jstring* array;
int capacity;
int size;
};
/**
* Data passed to parser handler method by the parser.
*/
struct ParsingContext {
ParsingContext(jobject object) : env(NULL), object(object), buffer(NULL), bufferSize(-1) {
for (int i = 0; i < BUCKET_COUNT; i++) {
internedStrings[i] = NULL;
}
}
// Warning: 'env' must be valid on entry.
~ParsingContext() {
freeBuffer();
// Free interned string cache.
for (int i = 0; i < BUCKET_COUNT; i++) {
if (internedStrings[i]) {
InternedString** bucket = internedStrings[i];
InternedString* current;
while ((current = *(bucket++)) != NULL) {
// Free the interned string reference.
env->DeleteGlobalRef(current->interned);
// Free the bucket.
delete current;
}
// Free the buckets.
delete[] internedStrings[i];
}
}
}
jcharArray ensureCapacity(int length) {
if (bufferSize < length) {
// Free the existing char[].
freeBuffer();
// Allocate a new char[].
jcharArray javaBuffer = env->NewCharArray(length);
if (javaBuffer == NULL) return NULL;
// Create a global reference.
javaBuffer = reinterpret_cast<jcharArray>(env->NewGlobalRef(javaBuffer));
if (javaBuffer == NULL) return NULL;
buffer = javaBuffer;
bufferSize = length;
}
return buffer;
}
private:
void freeBuffer() {
if (buffer != NULL) {
env->DeleteGlobalRef(buffer);
buffer = NULL;
bufferSize = -1;
}
}
public:
/**
* The JNI environment for the current thread. This should only be used
* to keep a reference to the env for use in Expat callbacks.
*/
JNIEnv* env;
/** The Java parser object. */
jobject object;
/** Buffer for text events. */
jcharArray buffer;
private:
/** The size of our buffer in jchars. */
int bufferSize;
public:
/** Current attributes. */
const char** attributes;
/** Number of attributes. */
int attributeCount;
/** True if namespace support is enabled. */
bool processNamespaces;
/** Keep track of names. */
StringStack stringStack;
/** Cache of interned strings. */
InternedString** internedStrings[BUCKET_COUNT];
};
static ParsingContext* toParsingContext(void* data) {
return reinterpret_cast<ParsingContext*>(data);
}
static ParsingContext* toParsingContext(XML_Parser parser) {
return reinterpret_cast<ParsingContext*>(XML_GetUserData(parser));
}
static XML_Parser toXMLParser(jlong address) {
return reinterpret_cast<XML_Parser>(address);
}
static jlong fromXMLParser(XML_Parser parser) {
return reinterpret_cast<uintptr_t>(parser);
}
static jmethodID commentMethod;
static jmethodID endCdataMethod;
static jmethodID endDtdMethod;
static jmethodID endElementMethod;
static jmethodID endNamespaceMethod;
static jmethodID handleExternalEntityMethod;
static jmethodID internMethod;
static jmethodID notationDeclMethod;
static jmethodID processingInstructionMethod;
static jmethodID startCdataMethod;
static jmethodID startDtdMethod;
static jmethodID startElementMethod;
static jmethodID startNamespaceMethod;
static jmethodID textMethod;
static jmethodID unparsedEntityDeclMethod;
static jstring emptyString;
/**
* Calculates a hash code for a null-terminated string. This is *not* equivalent
* to Java's String.hashCode(). This hashes the bytes while String.hashCode()
* hashes UTF-16 chars.
*
* @param s null-terminated string to hash
* @returns hash code
*/
static int hashString(const char* s) {
int hash = 0;
if (s) {
while (*s) {
hash = hash * 31 + *s++;
}
}
return hash;
}
/**
* Creates a new interned string wrapper. Looks up the interned string
* representing the given UTF-8 bytes.
*
* @param bytes null-terminated string to intern
* @param hash of bytes
* @returns wrapper of interned Java string
*/
static InternedString* newInternedString(JNIEnv* env, const char* bytes, int hash) {
// Allocate a new wrapper.
std::unique_ptr<InternedString> wrapper(new InternedString);
if (wrapper.get() == NULL) {
jniThrowOutOfMemoryError(env, NULL);
return NULL;
}
// Create a copy of the UTF-8 bytes.
// TODO: sometimes we already know the length. Reuse it if so.
char* copy = new char[strlen(bytes) + 1];
if (copy == NULL) {
jniThrowOutOfMemoryError(env, NULL);
return NULL;
}
strcpy(copy, bytes);
wrapper->bytes = copy;
// Save the hash.
wrapper->hash = hash;
// To intern a string, we must first create a new string and then call
// intern() on it. We then keep a global reference to the interned string.
ScopedLocalRef<jstring> newString(env, env->NewStringUTF(bytes));
if (env->ExceptionCheck()) {
return NULL;
}
// Call intern().
ScopedLocalRef<jstring> interned(env,
reinterpret_cast<jstring>(env->CallObjectMethod(newString.get(), internMethod)));
if (env->ExceptionCheck()) {
return NULL;
}
// Create a global reference to the interned string.
wrapper->interned = reinterpret_cast<jstring>(env->NewGlobalRef(interned.get()));
if (env->ExceptionCheck()) {
return NULL;
}
return wrapper.release();
}
/**
* Allocates a new bucket with one entry.
*
* @param entry to store in the bucket
* @returns a reference to the bucket
*/
static InternedString** newInternedStringBucket(InternedString* entry) {
InternedString** bucket = new InternedString*[2];
if (bucket != NULL) {
bucket[0] = entry;
bucket[1] = NULL;
}
return bucket;
}
/**
* Expands an interned string bucket and adds the given entry. Frees the
* provided bucket and returns a new one.
*
* @param existingBucket the bucket to replace
* @param entry to add to the bucket
* @returns a reference to the newly-allocated bucket containing the given entry
*/
static InternedString** expandInternedStringBucket(
InternedString** existingBucket, InternedString* entry) {
// Determine the size of the existing bucket.
int size = 0;
while (existingBucket[size]) size++;
// Allocate the new bucket with enough space for one more entry and
// a null terminator.
InternedString** newBucket = new InternedString*[size + 2];
if (newBucket == NULL) return NULL;
memcpy(newBucket, existingBucket, size * sizeof(InternedString*));
newBucket[size] = entry;
newBucket[size + 1] = NULL;
delete[] existingBucket;
return newBucket;
}
/**
* Returns an interned string for the given UTF-8 string.
*
* @param bucket to search for s
* @param s null-terminated string to find
* @param hash of s
* @returns interned Java string equivalent of s or null if not found
*/
static jstring findInternedString(InternedString** bucket, const char* s, int hash) {
InternedString* current;
while ((current = *(bucket++)) != NULL) {
if (current->hash != hash) continue;
if (!strcmp(s, current->bytes)) return current->interned;
}
return NULL;
}
/**
* Returns an interned string for the given UTF-8 string.
*
* @param s null-terminated string to intern
* @returns interned Java string equivelent of s or NULL if s is null
*/
static jstring internString(JNIEnv* env, ParsingContext* parsingContext, const char* s) {
if (s == NULL) return NULL;
int hash = hashString(s);
int bucketIndex = hash & (BUCKET_COUNT - 1);
InternedString*** buckets = parsingContext->internedStrings;
InternedString** bucket = buckets[bucketIndex];
InternedString* internedString;
if (bucket) {
// We have a bucket already. Look for the given string.
jstring found = findInternedString(bucket, s, hash);
if (found) {
// We found it!
return found;
}
// We didn't find it. :(
// Create a new entry.
internedString = newInternedString(env, s, hash);
if (internedString == NULL) return NULL;
// Expand the bucket.
bucket = expandInternedStringBucket(bucket, internedString);
if (bucket == NULL) {
delete internedString;
jniThrowOutOfMemoryError(env, NULL);
return NULL;
}
buckets[bucketIndex] = bucket;
return internedString->interned;
} else {
// We don't even have a bucket yet. Create an entry.
internedString = newInternedString(env, s, hash);
if (internedString == NULL) return NULL;
// Create a new bucket with one entry.
bucket = newInternedStringBucket(internedString);
if (bucket == NULL) {
delete internedString;
jniThrowOutOfMemoryError(env, NULL);
return NULL;
}
buckets[bucketIndex] = bucket;
return internedString->interned;
}
}
static void jniThrowExpatException(JNIEnv* env, XML_Error error) {
const char* message = XML_ErrorString(error);
jniThrowException(env, "org/apache/harmony/xml/ExpatException", message);
}
/**
* Copies UTF-8 characters into the buffer. Returns the number of Java chars
* which were buffered.
*
* @returns number of UTF-16 characters which were copied
*/
static size_t fillBuffer(ParsingContext* parsingContext, const char* utf8, int byteCount) {
JNIEnv* env = parsingContext->env;
// Grow buffer if necessary (the length in bytes is always >= the length in chars).
jcharArray javaChars = parsingContext->ensureCapacity(byteCount);
if (javaChars == NULL) {
return -1;
}
// Decode UTF-8 characters into our char[].
ScopedCharArrayRW chars(env, javaChars);
if (chars.get() == NULL) {
return -1;
}
UErrorCode status = U_ZERO_ERROR;
icu::UnicodeString utf16(icu::UnicodeString::fromUTF8(icu::StringPiece(utf8, byteCount)));
return utf16.extract(chars.get(), byteCount, status);
}
/**
* Buffers the given text and passes it to the given method.
*
* @param method to pass the characters and length to with signature
* (char[], int)
* @param data parsing context
* @param text to copy into the buffer
* @param length of text to copy (in bytes)
*/
static void bufferAndInvoke(jmethodID method, void* data, const char* text, size_t length) {
ParsingContext* parsingContext = toParsingContext(data);
JNIEnv* env = parsingContext->env;
// Bail out if a previously called handler threw an exception.
if (env->ExceptionCheck()) return;
// Buffer the element name.
size_t utf16length = fillBuffer(parsingContext, text, length);
// Invoke given method.
jobject javaParser = parsingContext->object;
jcharArray buffer = parsingContext->buffer;
env->CallVoidMethod(javaParser, method, buffer, utf16length);
}
static const char** toAttributes(jlong attributePointer) {
return reinterpret_cast<const char**>(static_cast<uintptr_t>(attributePointer));
}
/**
* The component parts of an attribute or element name.
*/
class ExpatElementName {
public:
ExpatElementName(JNIEnv* env, ParsingContext* parsingContext, jlong attributePointer, jint index) {
const char** attributes = toAttributes(attributePointer);
const char* name = attributes[index * 2];
init(env, parsingContext, name);
}
ExpatElementName(JNIEnv* env, ParsingContext* parsingContext, const char* s) {
init(env, parsingContext, s);
}
~ExpatElementName() {
free(mCopy);
}
/**
* Returns the namespace URI, like "http://www.w3.org/1999/xhtml".
* Possibly empty.
*/
jstring uri() {
return internString(mEnv, mParsingContext, mUri);
}
/**
* Returns the element or attribute local name, like "h1". Never empty. When
* namespace processing is disabled, this may contain a prefix, yielding a
* local name like "html:h1". In such cases, the qName will always be empty.
*/
jstring localName() {
return internString(mEnv, mParsingContext, mLocalName);
}
/**
* Returns the namespace prefix, like "html". Possibly empty.
*/
jstring qName() {
if (*mPrefix == 0) {
return localName();
}
// return prefix + ":" + localName
::LocalArray<1024> qName(strlen(mPrefix) + 1 + strlen(mLocalName) + 1);
snprintf(&qName[0], qName.size(), "%s:%s", mPrefix, mLocalName);
return internString(mEnv, mParsingContext, &qName[0]);
}
/**
* Returns true if this expat name has the same URI and local name.
*/
bool matches(const char* uri, const char* localName) {
return strcmp(uri, mUri) == 0 && strcmp(localName, mLocalName) == 0;
}
/**
* Returns true if this expat name has the same qualified name.
*/
bool matchesQName(const char* qName) {
const char* lastColon = strrchr(qName, ':');
// Compare local names only if either:
// - the input qualified name doesn't have a colon (like "h1")
// - this element doesn't have a prefix. Such is the case when it
// doesn't belong to a namespace, or when this parser's namespace
// processing is disabled. In the latter case, this element's local
// name may still contain a colon (like "html:h1").
if (lastColon == NULL || *mPrefix == 0) {
return strcmp(qName, mLocalName) == 0;
}
// Otherwise compare both prefix and local name
size_t prefixLength = lastColon - qName;
return strlen(mPrefix) == prefixLength
&& strncmp(qName, mPrefix, prefixLength) == 0
&& strcmp(lastColon + 1, mLocalName) == 0;
}
private:
JNIEnv* mEnv;
ParsingContext* mParsingContext;
char* mCopy;
const char* mUri;
const char* mLocalName;
const char* mPrefix;
/**
* Decodes an Expat-encoded name of one of these three forms:
* "uri|localName|prefix" (example: "http://www.w3.org/1999/xhtml|h1|html")
* "uri|localName" (example: "http://www.w3.org/1999/xhtml|h1")
* "localName" (example: "h1")
*/
void init(JNIEnv* env, ParsingContext* parsingContext, const char* s) {
mEnv = env;
mParsingContext = parsingContext;
mCopy = strdup(s);
// split the input into up to 3 parts: a|b|c
char* context = NULL;
char* a = strtok_r(mCopy, "|", &context);
char* b = strtok_r(NULL, "|", &context);
char* c = strtok_r(NULL, "|", &context);
if (c != NULL) { // input of the form "uri|localName|prefix"
mUri = a;
mLocalName = b;
mPrefix = c;
} else if (b != NULL) { // input of the form "uri|localName"
mUri = a;
mLocalName = b;
mPrefix = "";
} else { // input of the form "localName"
mLocalName = a;
mUri = "";
mPrefix = "";
}
}
// Disallow copy and assignment.
ExpatElementName(const ExpatElementName&);
void operator=(const ExpatElementName&);
};
/**
* Called by Expat at the start of an element. Delegates to the same method
* on the Java parser.
*
* @param data parsing context
* @param elementName "uri|localName" or "localName" for the current element
* @param attributes alternating attribute names and values. Like element
* names, attribute names follow the format "uri|localName" or "localName".
*/
static void startElement(void* data, const char* elementName, const char** attributes) {
ParsingContext* parsingContext = toParsingContext(data);
JNIEnv* env = parsingContext->env;
// Bail out if a previously called handler threw an exception.
if (env->ExceptionCheck()) return;
// Count the number of attributes.
int count = 0;
while (attributes[count * 2]) count++;
// Make the attributes available for the duration of this call.
parsingContext->attributes = attributes;
parsingContext->attributeCount = count;
jobject javaParser = parsingContext->object;
ExpatElementName e(env, parsingContext, elementName);
jstring uri = parsingContext->processNamespaces ? e.uri() : emptyString;
jstring localName = parsingContext->processNamespaces ? e.localName() : emptyString;
jstring qName = e.qName();
parsingContext->stringStack.push(env, qName);
parsingContext->stringStack.push(env, uri);
parsingContext->stringStack.push(env, localName);
jlong attributesAddress = reinterpret_cast<jlong>(attributes);
env->CallVoidMethod(javaParser, startElementMethod, uri, localName, qName, attributesAddress, count);
parsingContext->attributes = NULL;
parsingContext->attributeCount = -1;
}
/**
* Called by Expat at the end of an element. Delegates to the same method
* on the Java parser.
*
* @param data parsing context
* @param elementName "uri|localName" or "localName" for the current element;
* we assume that this matches the last data on our stack.
*/
static void endElement(void* data, const char* /*elementName*/) {
ParsingContext* parsingContext = toParsingContext(data);
JNIEnv* env = parsingContext->env;
// Bail out if a previously called handler threw an exception.
if (env->ExceptionCheck()) return;
jobject javaParser = parsingContext->object;
jstring localName = parsingContext->stringStack.pop();
jstring uri = parsingContext->stringStack.pop();
jstring qName = parsingContext->stringStack.pop();
env->CallVoidMethod(javaParser, endElementMethod, uri, localName, qName);
}
/**
* Called by Expat when it encounters text. Delegates to the same method
* on the Java parser. This may be called mutiple times with incremental pieces
* of the same contiguous block of text.
*
* @param data parsing context
* @param characters buffer containing encountered text
* @param length number of characters in the buffer
*/
static void text(void* data, const char* characters, int length) {
bufferAndInvoke(textMethod, data, characters, length);
}
/**
* Called by Expat when it encounters a comment. Delegates to the same method
* on the Java parser.
* @param data parsing context
* @param comment 0-terminated
*/
static void comment(void* data, const char* comment) {
bufferAndInvoke(commentMethod, data, comment, strlen(comment));
}
/**
* Called by Expat at the beginning of a namespace mapping.
*
* @param data parsing context
* @param prefix null-terminated namespace prefix used in the XML
* @param uri of the namespace
*/
static void startNamespace(void* data, const char* prefix, const char* uri) {
ParsingContext* parsingContext = toParsingContext(data);
JNIEnv* env = parsingContext->env;
// Bail out if a previously called handler threw an exception.
if (env->ExceptionCheck()) return;
jstring internedPrefix = emptyString;
if (prefix != NULL) {
internedPrefix = internString(env, parsingContext, prefix);
if (env->ExceptionCheck()) return;
}
jstring internedUri = emptyString;
if (uri != NULL) {
internedUri = internString(env, parsingContext, uri);
if (env->ExceptionCheck()) return;
}
parsingContext->stringStack.push(env, internedPrefix);
jobject javaParser = parsingContext->object;
env->CallVoidMethod(javaParser, startNamespaceMethod, internedPrefix, internedUri);
}
/**
* Called by Expat at the end of a namespace mapping.
*
* @param data parsing context
* @param prefix null-terminated namespace prefix used in the XML;
* we assume this is the same as the last prefix on the stack.
*/
static void endNamespace(void* data, const char* /*prefix*/) {
ParsingContext* parsingContext = toParsingContext(data);
JNIEnv* env = parsingContext->env;
// Bail out if a previously called handler threw an exception.
if (env->ExceptionCheck()) return;
jstring internedPrefix = parsingContext->stringStack.pop();
jobject javaParser = parsingContext->object;
env->CallVoidMethod(javaParser, endNamespaceMethod, internedPrefix);
}
/**
* Called by Expat at the beginning of a CDATA section.
*
* @param data parsing context
*/
static void startCdata(void* data) {
ParsingContext* parsingContext = toParsingContext(data);
JNIEnv* env = parsingContext->env;
// Bail out if a previously called handler threw an exception.
if (env->ExceptionCheck()) return;
jobject javaParser = parsingContext->object;
env->CallVoidMethod(javaParser, startCdataMethod);
}
/**
* Called by Expat at the end of a CDATA section.
*
* @param data parsing context
*/
static void endCdata(void* data) {
ParsingContext* parsingContext = toParsingContext(data);
JNIEnv* env = parsingContext->env;
// Bail out if a previously called handler threw an exception.
if (env->ExceptionCheck()) return;
jobject javaParser = parsingContext->object;
env->CallVoidMethod(javaParser, endCdataMethod);
}
/**
* Called by Expat at the beginning of a DOCTYPE section.
* Expat gives us 'hasInternalSubset', but the Java API doesn't expect it, so we don't need it.
*/
static void startDtd(void* data, const char* name,
const char* systemId, const char* publicId, int /*hasInternalSubset*/) {
ParsingContext* parsingContext = toParsingContext(data);
JNIEnv* env = parsingContext->env;
// Bail out if a previously called handler threw an exception.
if (env->ExceptionCheck()) return;
jstring javaName = internString(env, parsingContext, name);
if (env->ExceptionCheck()) return;
jstring javaPublicId = internString(env, parsingContext, publicId);
if (env->ExceptionCheck()) return;
jstring javaSystemId = internString(env, parsingContext, systemId);
if (env->ExceptionCheck()) return;
jobject javaParser = parsingContext->object;
env->CallVoidMethod(javaParser, startDtdMethod, javaName, javaPublicId,
javaSystemId);
}
/**
* Called by Expat at the end of a DOCTYPE section.
*
* @param data parsing context
*/
static void endDtd(void* data) {
ParsingContext* parsingContext = toParsingContext(data);
JNIEnv* env = parsingContext->env;
// Bail out if a previously called handler threw an exception.
if (env->ExceptionCheck()) return;
jobject javaParser = parsingContext->object;
env->CallVoidMethod(javaParser, endDtdMethod);
}
/**
* Called by Expat when it encounters processing instructions.
*
* @param data parsing context
* @param target of the instruction
* @param instructionData
*/
static void processingInstruction(void* data, const char* target, const char* instructionData) {
ParsingContext* parsingContext = toParsingContext(data);
JNIEnv* env = parsingContext->env;
// Bail out if a previously called handler threw an exception.
if (env->ExceptionCheck()) return;
jstring javaTarget = internString(env, parsingContext, target);
if (env->ExceptionCheck()) return;
ScopedLocalRef<jstring> javaInstructionData(env, env->NewStringUTF(instructionData));
if (env->ExceptionCheck()) return;
jobject javaParser = parsingContext->object;
env->CallVoidMethod(javaParser, processingInstructionMethod, javaTarget, javaInstructionData.get());
}
/**
* Creates a new entity parser.
*
* @param object the Java ExpatParser instance
* @param parentParser pointer
* @param javaEncoding the character encoding name
* @param javaContext that was provided to handleExternalEntity
* @returns the pointer to the C Expat entity parser
*/
static jlong ExpatParser_createEntityParser(JNIEnv* env, jobject, jlong parentParser, jstring javaContext) {
ScopedUtfChars context(env, javaContext);
if (context.c_str() == NULL) {
return 0;
}
XML_Parser parent = toXMLParser(parentParser);
XML_Parser entityParser = XML_ExternalEntityParserCreate(parent, context.c_str(), NULL);
if (entityParser == NULL) {
jniThrowOutOfMemoryError(env, NULL);
}
return fromXMLParser(entityParser);
}
/**
* Handles external entities. We ignore the "base" URI and keep track of it
* ourselves.
*/
static int handleExternalEntity(XML_Parser parser, const char* context,
const char*, const char* systemId, const char* publicId) {
ParsingContext* parsingContext = toParsingContext(parser);
jobject javaParser = parsingContext->object;
JNIEnv* env = parsingContext->env;
jobject object = parsingContext->object;
// Bail out if a previously called handler threw an exception.
if (env->ExceptionCheck()) {
return XML_STATUS_ERROR;
}
ScopedLocalRef<jstring> javaSystemId(env, env->NewStringUTF(systemId));
if (env->ExceptionCheck()) {
return XML_STATUS_ERROR;
}
ScopedLocalRef<jstring> javaPublicId(env, env->NewStringUTF(publicId));
if (env->ExceptionCheck()) {
return XML_STATUS_ERROR;
}
ScopedLocalRef<jstring> javaContext(env, env->NewStringUTF(context));
if (env->ExceptionCheck()) {
return XML_STATUS_ERROR;
}
// Pass the wrapped parser and both strings to java.
env->CallVoidMethod(javaParser, handleExternalEntityMethod, javaContext.get(),
javaPublicId.get(), javaSystemId.get());
/*
* Parsing the external entity leaves parsingContext->env and object set to
* NULL, so we need to restore both.
*
* TODO: consider restoring the original env and object instead of setting
* them to NULL in the append() functions.
*/
parsingContext->env = env;
parsingContext->object = object;
return env->ExceptionCheck() ? XML_STATUS_ERROR : XML_STATUS_OK;
}
/**
* Expat gives us 'base', but the Java API doesn't expect it, so we don't need it.
*/
static void unparsedEntityDecl(void* data, const char* name, const char* /*base*/, const char* systemId, const char* publicId, const char* notationName) {
ParsingContext* parsingContext = toParsingContext(data);
jobject javaParser = parsingContext->object;
JNIEnv* env = parsingContext->env;
// Bail out if a previously called handler threw an exception.
if (env->ExceptionCheck()) return;
ScopedLocalRef<jstring> javaName(env, env->NewStringUTF(name));
if (env->ExceptionCheck()) return;
ScopedLocalRef<jstring> javaPublicId(env, env->NewStringUTF(publicId));
if (env->ExceptionCheck()) return;
ScopedLocalRef<jstring> javaSystemId(env, env->NewStringUTF(systemId));
if (env->ExceptionCheck()) return;
ScopedLocalRef<jstring> javaNotationName(env, env->NewStringUTF(notationName));
if (env->ExceptionCheck()) return;
env->CallVoidMethod(javaParser, unparsedEntityDeclMethod, javaName.get(), javaPublicId.get(), javaSystemId.get(), javaNotationName.get());
}
/**
* Expat gives us 'base', but the Java API doesn't expect it, so we don't need it.
*/
static void notationDecl(void* data, const char* name, const char* /*base*/, const char* systemId, const char* publicId) {
ParsingContext* parsingContext = toParsingContext(data);
jobject javaParser = parsingContext->object;
JNIEnv* env = parsingContext->env;
// Bail out if a previously called handler threw an exception.
if (env->ExceptionCheck()) return;
ScopedLocalRef<jstring> javaName(env, env->NewStringUTF(name));
if (env->ExceptionCheck()) return;
ScopedLocalRef<jstring> javaPublicId(env, env->NewStringUTF(publicId));
if (env->ExceptionCheck()) return;
ScopedLocalRef<jstring> javaSystemId(env, env->NewStringUTF(systemId));
if (env->ExceptionCheck()) return;
env->CallVoidMethod(javaParser, notationDeclMethod, javaName.get(), javaPublicId.get(), javaSystemId.get());
}
/**
* Creates a new Expat parser. Called from the Java ExpatParser constructor.
*
* @param object the Java ExpatParser instance
* @param javaEncoding the character encoding name
* @param processNamespaces true if the parser should handle namespaces
* @returns the pointer to the C Expat parser
*/
static jlong ExpatParser_initialize(JNIEnv* env, jobject object, jstring javaEncoding,
jboolean processNamespaces) {
// Allocate parsing context.
std::unique_ptr<ParsingContext> context(new ParsingContext(object));
if (context.get() == NULL) {
jniThrowOutOfMemoryError(env, NULL);
return 0;
}
context->processNamespaces = processNamespaces;
// Create a parser.
XML_Parser parser;
ScopedUtfChars encoding(env, javaEncoding);
if (encoding.c_str() == NULL) {
return 0;
}
if (processNamespaces) {
// Use '|' to separate URIs from local names.
parser = XML_ParserCreateNS(encoding.c_str(), '|');
} else {
parser = XML_ParserCreate(encoding.c_str());
}
if (parser != NULL) {
if (processNamespaces) {
XML_SetNamespaceDeclHandler(parser, startNamespace, endNamespace);
XML_SetReturnNSTriplet(parser, 1);
}
XML_SetCdataSectionHandler(parser, startCdata, endCdata);
XML_SetCharacterDataHandler(parser, text);
XML_SetCommentHandler(parser, comment);
XML_SetDoctypeDeclHandler(parser, startDtd, endDtd);
XML_SetElementHandler(parser, startElement, endElement);
XML_SetExternalEntityRefHandler(parser, handleExternalEntity);
XML_SetNotationDeclHandler(parser, notationDecl);
XML_SetProcessingInstructionHandler(parser, processingInstruction);
XML_SetUnparsedEntityDeclHandler(parser, unparsedEntityDecl);
XML_SetUserData(parser, context.release());
} else {
jniThrowOutOfMemoryError(env, NULL);
return 0;
}
return fromXMLParser(parser);
}
/**
* Decodes the bytes as characters and parse the characters as XML. This
* performs character decoding using the charset specified at XML_Parser
* creation. For Java chars, that charset must be UTF-16 so that a Java char[]
* can be reinterpreted as a UTF-16 encoded byte[]. appendBytes, appendChars
* and appendString all call through this method.
*/
static void append(JNIEnv* env, jobject object, jlong pointer,
const char* bytes, size_t byteOffset, size_t byteCount, jboolean isFinal) {
XML_Parser parser = toXMLParser(pointer);
ParsingContext* context = toParsingContext(parser);
context->env = env;
context->object = object;
if (!XML_Parse(parser, bytes + byteOffset, byteCount, isFinal) && !env->ExceptionCheck()) {
jniThrowExpatException(env, XML_GetErrorCode(parser));
}
context->object = NULL;
context->env = NULL;
}
static void ExpatParser_appendBytes(JNIEnv* env, jobject object, jlong pointer,
jbyteArray xml, jint byteOffset, jint byteCount) {
ScopedByteArrayRO byteArray(env, xml);
if (byteArray.get() == NULL) {
return;
}
const char* bytes = reinterpret_cast<const char*>(byteArray.get());
append(env, object, pointer, bytes, byteOffset, byteCount, XML_FALSE);
}
static void ExpatParser_appendChars(JNIEnv* env, jobject object, jlong pointer,
jcharArray xml, jint charOffset, jint charCount) {
ScopedCharArrayRO charArray(env, xml);
if (charArray.get() == NULL) {
return;
}
const char* bytes = reinterpret_cast<const char*>(charArray.get());
size_t byteOffset = 2 * charOffset;
size_t byteCount = 2 * charCount;
append(env, object, pointer, bytes, byteOffset, byteCount, XML_FALSE);
}
static void ExpatParser_appendString(JNIEnv* env, jobject object, jlong pointer, jstring javaXml, jboolean isFinal) {
ScopedStringChars xml(env, javaXml);
if (xml.get() == NULL) {
return;
}
const char* bytes = reinterpret_cast<const char*>(xml.get());
size_t byteCount = 2 * xml.size();
append(env, object, pointer, bytes, 0, byteCount, isFinal);
}
/**
* Releases parser only.
*/
static void ExpatParser_releaseParser(JNIEnv*, jobject, jlong address) {
XML_ParserFree(toXMLParser(address));
}
/**
* Cleans up after the parser. Called at garbage collection time.
*/
static void ExpatParser_release(JNIEnv* env, jobject, jlong address) {
XML_Parser parser = toXMLParser(address);
ParsingContext* context = toParsingContext(parser);
context->env = env;
delete context;
XML_ParserFree(parser);
}
static int ExpatParser_line(JNIEnv*, jobject, jlong address) {
return XML_GetCurrentLineNumber(toXMLParser(address));
}
static int ExpatParser_column(JNIEnv*, jobject, jlong address) {
return XML_GetCurrentColumnNumber(toXMLParser(address));
}
/**
* Gets the URI of the attribute at the given index.
*
* @param attributePointer to the attribute array
* @param index of the attribute
* @returns interned Java string containing attribute's URI
*/
static jstring ExpatAttributes_getURI(JNIEnv* env, jobject, jlong address,
jlong attributePointer, jint index) {
ParsingContext* context = toParsingContext(toXMLParser(address));
return ExpatElementName(env, context, attributePointer, index).uri();
}
/**
* Gets the local name of the attribute at the given index.
*
* @param attributePointer to the attribute array
* @param index of the attribute
* @returns interned Java string containing attribute's local name
*/
static jstring ExpatAttributes_getLocalName(JNIEnv* env, jobject, jlong address,
jlong attributePointer, jint index) {
ParsingContext* context = toParsingContext(toXMLParser(address));
return ExpatElementName(env, context, attributePointer, index).localName();
}
/**
* Gets the qualified name of the attribute at the given index.
*
* @param attributePointer to the attribute array
* @param index of the attribute
* @returns interned Java string containing attribute's local name
*/
static jstring ExpatAttributes_getQName(JNIEnv* env, jobject, jlong address,
jlong attributePointer, jint index) {
ParsingContext* context = toParsingContext(toXMLParser(address));
return ExpatElementName(env, context, attributePointer, index).qName();
}
/**
* Gets the value of the attribute at the given index.
*
* @param object Java ExpatParser instance
* @param attributePointer to the attribute array
* @param index of the attribute
* @returns Java string containing attribute's value
*/
static jstring ExpatAttributes_getValueByIndex(JNIEnv* env, jobject,
jlong attributePointer, jint index) {
const char** attributes = toAttributes(attributePointer);
const char* value = attributes[(index * 2) + 1];
return env->NewStringUTF(value);
}
/**
* Gets the index of the attribute with the given qualified name.
*
* @param attributePointer to the attribute array
* @param qName to look for
* @returns index of attribute with the given uri and local name or -1 if not
* found
*/
static jint ExpatAttributes_getIndexForQName(JNIEnv* env, jobject,
jlong attributePointer, jstring qName) {
ScopedUtfChars qNameBytes(env, qName);
if (qNameBytes.c_str() == NULL) {
return -1;
}
const char** attributes = toAttributes(attributePointer);
int found = -1;
for (int index = 0; attributes[index * 2]; ++index) {
if (ExpatElementName(NULL, NULL, attributePointer, index).matchesQName(qNameBytes.c_str())) {
found = index;
break;
}
}
return found;
}
/**
* Gets the index of the attribute with the given URI and name.
*
* @param attributePointer to the attribute array
* @param uri to look for
* @param localName to look for
* @returns index of attribute with the given uri and local name or -1 if not
* found
*/
static jint ExpatAttributes_getIndex(JNIEnv* env, jobject, jlong attributePointer,
jstring uri, jstring localName) {
ScopedUtfChars uriBytes(env, uri);
if (uriBytes.c_str() == NULL) {
return -1;
}
ScopedUtfChars localNameBytes(env, localName);
if (localNameBytes.c_str() == NULL) {
return -1;
}
const char** attributes = toAttributes(attributePointer);
for (int index = 0; attributes[index * 2]; ++index) {
if (ExpatElementName(NULL, NULL, attributePointer, index).matches(uriBytes.c_str(),
localNameBytes.c_str())) {
return index;
}
}
return -1;
}
/**
* Gets the value of the attribute with the given qualified name.
*
* @param attributePointer to the attribute array
* @param uri to look for
* @param localName to look for
* @returns value of attribute with the given uri and local name or NULL if not
* found
*/
static jstring ExpatAttributes_getValueForQName(JNIEnv* env, jobject clazz,
jlong attributePointer, jstring qName) {
jint index = ExpatAttributes_getIndexForQName(env, clazz, attributePointer, qName);
return index == -1 ? NULL
: ExpatAttributes_getValueByIndex(env, clazz, attributePointer, index);
}
/**
* Gets the value of the attribute with the given URI and name.
*
* @param attributePointer to the attribute array
* @param uri to look for
* @param localName to look for
* @returns value of attribute with the given uri and local name or NULL if not
* found
*/
static jstring ExpatAttributes_getValue(JNIEnv* env, jobject clazz,
jlong attributePointer, jstring uri, jstring localName) {
jint index = ExpatAttributes_getIndex(env, clazz, attributePointer, uri, localName);
return index == -1 ? NULL
: ExpatAttributes_getValueByIndex(env, clazz, attributePointer, index);
}
/**
* Clones an array of strings. Uses one contiguous block of memory so as to
* maximize performance.
*
* @param address char** to clone
* @param count number of attributes
*/
static jlong ExpatParser_cloneAttributes(JNIEnv* env, jobject, jlong address, jint count) {
const char** source = reinterpret_cast<const char**>(static_cast<uintptr_t>(address));
count *= 2;
// Figure out how big the buffer needs to be.
int arraySize = (count + 1) * sizeof(char*);
int totalSize = arraySize;
int stringLengths[count];
for (int i = 0; i < count; i++) {
int length = strlen(source[i]);
stringLengths[i] = length;
totalSize += length + 1;
}
char* buffer = new char[totalSize];
if (buffer == NULL) {
jniThrowOutOfMemoryError(env, NULL);
return 0;
}
// Array is at the beginning of the buffer.
char** clonedArray = reinterpret_cast<char**>(buffer);
clonedArray[count] = NULL; // null terminate
// String data follows immediately after.
char* destinationString = buffer + arraySize;
for (int i = 0; i < count; i++) {
const char* sourceString = source[i];
int stringLength = stringLengths[i];
memcpy(destinationString, sourceString, stringLength + 1);
clonedArray[i] = destinationString;
destinationString += stringLength + 1;
}
return reinterpret_cast<uintptr_t>(buffer);
}
/**
* Frees cloned attributes.
*/
static void ExpatAttributes_freeAttributes(JNIEnv*, jobject, jlong pointer) {
delete[] reinterpret_cast<char*>(static_cast<uintptr_t>(pointer));
}
/**
* Called when we initialize our Java parser class.
*
* @param clazz Java ExpatParser class
*/
static void ExpatParser_staticInitialize(JNIEnv* env, jobject classObject, jstring empty) {
jclass clazz = reinterpret_cast<jclass>(classObject);
startElementMethod = env->GetMethodID(clazz, "startElement",
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;JI)V");
if (startElementMethod == NULL) return;
endElementMethod = env->GetMethodID(clazz, "endElement",
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
if (endElementMethod == NULL) return;
textMethod = env->GetMethodID(clazz, "text", "([CI)V");
if (textMethod == NULL) return;
commentMethod = env->GetMethodID(clazz, "comment", "([CI)V");
if (commentMethod == NULL) return;
startCdataMethod = env->GetMethodID(clazz, "startCdata", "()V");
if (startCdataMethod == NULL) return;
endCdataMethod = env->GetMethodID(clazz, "endCdata", "()V");
if (endCdataMethod == NULL) return;
startDtdMethod = env->GetMethodID(clazz, "startDtd",
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
if (startDtdMethod == NULL) return;
endDtdMethod = env->GetMethodID(clazz, "endDtd", "()V");
if (endDtdMethod == NULL) return;
startNamespaceMethod = env->GetMethodID(clazz, "startNamespace",
"(Ljava/lang/String;Ljava/lang/String;)V");
if (startNamespaceMethod == NULL) return;
endNamespaceMethod = env->GetMethodID(clazz, "endNamespace",
"(Ljava/lang/String;)V");
if (endNamespaceMethod == NULL) return;
processingInstructionMethod = env->GetMethodID(clazz,
"processingInstruction", "(Ljava/lang/String;Ljava/lang/String;)V");
if (processingInstructionMethod == NULL) return;
handleExternalEntityMethod = env->GetMethodID(clazz,
"handleExternalEntity",
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
if (handleExternalEntityMethod == NULL) return;
notationDeclMethod = env->GetMethodID(clazz, "notationDecl",
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
if (notationDeclMethod == NULL) return;
unparsedEntityDeclMethod = env->GetMethodID(clazz, "unparsedEntityDecl",
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
if (unparsedEntityDeclMethod == NULL) return;
internMethod = env->GetMethodID(JniConstants::stringClass, "intern", "()Ljava/lang/String;");
if (internMethod == NULL) return;
// Reference to "".
emptyString = reinterpret_cast<jstring>(env->NewGlobalRef(empty));
}
static JNINativeMethod parserMethods[] = {
NATIVE_METHOD(ExpatParser, appendString, "(JLjava/lang/String;Z)V"),
NATIVE_METHOD(ExpatParser, appendBytes, "(J[BII)V"),
NATIVE_METHOD(ExpatParser, appendChars, "(J[CII)V"),
NATIVE_METHOD(ExpatParser, cloneAttributes, "(JI)J"),
NATIVE_METHOD(ExpatParser, column, "(J)I"),
NATIVE_METHOD(ExpatParser, createEntityParser, "(JLjava/lang/String;)J"),
NATIVE_METHOD(ExpatParser, initialize, "(Ljava/lang/String;Z)J"),
NATIVE_METHOD(ExpatParser, line, "(J)I"),
NATIVE_METHOD(ExpatParser, release, "(J)V"),
NATIVE_METHOD(ExpatParser, releaseParser, "(J)V"),
NATIVE_METHOD(ExpatParser, staticInitialize, "(Ljava/lang/String;)V"),
};
static JNINativeMethod attributeMethods[] = {
NATIVE_METHOD(ExpatAttributes, freeAttributes, "(J)V"),
NATIVE_METHOD(ExpatAttributes, getIndexForQName, "(JLjava/lang/String;)I"),
NATIVE_METHOD(ExpatAttributes, getIndex, "(JLjava/lang/String;Ljava/lang/String;)I"),
NATIVE_METHOD(ExpatAttributes, getLocalName, "(JJI)Ljava/lang/String;"),
NATIVE_METHOD(ExpatAttributes, getQName, "(JJI)Ljava/lang/String;"),
NATIVE_METHOD(ExpatAttributes, getURI, "(JJI)Ljava/lang/String;"),
NATIVE_METHOD(ExpatAttributes, getValueByIndex, "(JI)Ljava/lang/String;"),
NATIVE_METHOD(ExpatAttributes, getValueForQName, "(JLjava/lang/String;)Ljava/lang/String;"),
NATIVE_METHOD(ExpatAttributes, getValue, "(JLjava/lang/String;Ljava/lang/String;)Ljava/lang/String;"),
};
void register_org_apache_harmony_xml_ExpatParser(JNIEnv* env) {
jniRegisterNativeMethods(env, "org/apache/harmony/xml/ExpatParser", parserMethods, NELEM(parserMethods));
jniRegisterNativeMethods(env, "org/apache/harmony/xml/ExpatAttributes", attributeMethods, NELEM(attributeMethods));
}