| /* |
| * Copyright (C) 2007 Esmertec AG. |
| * Copyright (C) 2007 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 "wbxml" |
| |
| #include <jni.h> |
| #include <cutils/jstring.h> |
| |
| #include "wbxml_parser.h" |
| #include "imps_encoder.h" |
| #include "csp13_hash.h" |
| |
| // FIXME: dalvik seems to have problem throwing non-system exceptions. Use IAE for now. |
| #if 1 |
| #define PARSER_EXCEPTION_CLASS "java/lang/IllegalArgumentException" |
| #define SERIALIZER_EXCEPTION_CLASS "java/lang/IllegalArgumentException" |
| #else |
| #define PARSER_EXCEPTION_CLASS "com/android/im/imps/ParserException" |
| #define SERIALIZER_EXCEPTION_CLASS "com/android/im/imps/SerializerException" |
| #endif |
| |
| class JniWbxmlContentHandler; |
| class JniWbxmlDataHandler; |
| |
| struct WbxmlParsingContext |
| { |
| /** The JNI enviromenet. */ |
| JNIEnv * env; |
| /** The Java parser object. */ |
| jobject object; |
| /** The wbxml parser. */ |
| WbxmlParser * parser; |
| /** The content handler.*/ |
| JniWbxmlContentHandler * contentHandler; |
| }; |
| |
| struct WbxmlEncodingContext |
| { |
| /** The JNI enviroment. */ |
| JNIEnv * env; |
| /** The Java encoder object. */ |
| jobject object; |
| /** The wbxml encoder. */ |
| WbxmlEncoder * encoder; |
| /** The handler to get encoding result. */ |
| JniWbxmlDataHandler * handler; |
| }; |
| |
| static jmethodID parserCallbackStartElement; |
| static jmethodID parserCallbackEndElement; |
| static jmethodID parserCallbackCharacters; |
| static jmethodID encoderCallbackWbxmlData; |
| static jclass stringClass; |
| |
| /** |
| * Copies UTF-8 characters into the buffer. |
| */ |
| static jcharArray bytesToCharArray(JNIEnv * env, const char *data, int length, size_t * out_len) |
| { |
| // TODO: store the buffer in ParsingContext to reuse it instead create a |
| // new one everytime. |
| jcharArray buffer = env->NewCharArray(length); |
| if(buffer == NULL) { |
| return NULL; |
| } |
| |
| // Get a native reference to the buffer. |
| jboolean copy; |
| jchar *nativeBuffer = env->GetCharArrayElements(buffer, ©); |
| if (copy) { |
| jclass clazz = env->FindClass("java/lang/AssertionError"); |
| env->ThrowNew(clazz, "Unexpected copy"); |
| return NULL; |
| } |
| |
| // Decode UTF-8 characters into the buffer. |
| strcpylen8to16((char16_t *) nativeBuffer, data, length, out_len); |
| |
| // Release our native reference. |
| env->ReleaseCharArrayElements(buffer, nativeBuffer, JNI_ABORT); |
| |
| return buffer; |
| } |
| |
| class JniWbxmlContentHandler : public DefaultWbxmlContentHandler |
| { |
| public: |
| JniWbxmlContentHandler(WbxmlParsingContext * context):mContext(context) |
| { |
| } |
| |
| void reset(void) |
| { |
| mCurTag.clear(); |
| } |
| |
| void startElement(const char * name, const vector<Attribute> & atts) |
| { |
| JNIEnv * env = mContext->env; |
| |
| if(env->ExceptionCheck()) return; |
| |
| mCurTag = name; |
| |
| //TODO cache jstrings for later use? |
| jstring localName = env->NewStringUTF(name); |
| jobjectArray attrNames = NULL; |
| jobjectArray attrValues = NULL; |
| int count = atts.size(); |
| if(count > 0) { |
| attrNames = env->NewObjectArray(count, stringClass, NULL); |
| attrValues = env->NewObjectArray(count, stringClass, NULL); |
| } |
| |
| for(int i = 0; i < count; i++) { |
| jstring attrName = env->NewStringUTF(atts[i].name.c_str()); |
| jstring attrValue = env->NewStringUTF(atts[i].value.c_str()); |
| env->SetObjectArrayElement(attrNames, i, attrName); |
| env->SetObjectArrayElement(attrValues, i, attrValue); |
| } |
| |
| jobject javaParser = mContext->object; |
| env->CallVoidMethod(javaParser, parserCallbackStartElement, localName, attrNames, attrValues); |
| env->DeleteLocalRef(localName); |
| for(int i = 0; i < count; i++) { |
| env->DeleteLocalRef(env->GetObjectArrayElement(attrNames, i)); |
| env->DeleteLocalRef(env->GetObjectArrayElement(attrValues, i)); |
| } |
| } |
| |
| void endElement(const char * name) |
| { |
| JNIEnv * env = mContext->env; |
| |
| if(env->ExceptionCheck()) return; |
| |
| mCurTag.clear(); |
| |
| jstring localName = env->NewStringUTF(name); |
| jobject javaParser = mContext->object; |
| |
| env->CallVoidMethod(javaParser, parserCallbackEndElement, localName); |
| env->DeleteLocalRef(localName); |
| } |
| |
| void characters(const char * data, int len) |
| { |
| JNIEnv * env = mContext->env; |
| |
| if(env->ExceptionCheck()) return; |
| |
| size_t utf16length; |
| jcharArray buffer = bytesToCharArray(env, data, len, &utf16length); |
| jobject javaParser = mContext->object; |
| env->CallVoidMethod(javaParser, parserCallbackCharacters, buffer, utf16length); |
| env->DeleteLocalRef(buffer); |
| } |
| |
| void opaque(const char * data, int len) |
| { |
| JNIEnv * env = mContext->env; |
| |
| if(env->ExceptionCheck()) return; |
| |
| int val = 0; |
| if (csp13IsIntegerTag(mCurTag.c_str())) { |
| while (len--){ |
| val <<= 8; |
| val |= (unsigned char)*data++; |
| } |
| |
| char buf[20]; |
| sprintf(buf, "%d", val); |
| size_t utf16length; |
| jcharArray buffer = bytesToCharArray(env, buf, strlen(buf), &utf16length); |
| jobject javaParser = mContext->object; |
| env->CallVoidMethod(javaParser, parserCallbackCharacters, buffer, utf16length); |
| env->DeleteLocalRef(buffer); |
| } |
| // FIXME: handle DateTime and binary data too |
| } |
| private: |
| WbxmlParsingContext * mContext; |
| string mCurTag; |
| }; |
| |
| static void parserStaticInitialize(JNIEnv * env, jclass clazz) |
| { |
| parserCallbackStartElement = env->GetMethodID((jclass) clazz, "startElement", |
| "(Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/String;)V"); |
| |
| parserCallbackEndElement = env->GetMethodID((jclass) clazz, "endElement", |
| "(Ljava/lang/String;)V"); |
| |
| parserCallbackCharacters = env->GetMethodID((jclass) clazz, "characters", |
| "([CI)V"); |
| |
| stringClass = env->FindClass("java/lang/String"); |
| } |
| |
| static jint parserCreate(JNIEnv * env, jobject thisObj, jstring encoding) |
| { |
| // TODO: encoding |
| WbxmlParsingContext * context = NULL; |
| WbxmlParser * parser = NULL; |
| JniWbxmlContentHandler * handler = NULL; |
| |
| context = new WbxmlParsingContext(); |
| if (!context) { |
| goto error; |
| } |
| |
| parser = new WbxmlParser(0); |
| if (!parser) { |
| goto error; |
| } |
| |
| handler = new JniWbxmlContentHandler(context); |
| if (!handler) { |
| goto error; |
| } |
| |
| parser->setContentHandler(handler); |
| context->parser = parser; |
| context->contentHandler = handler; |
| context->env = env; |
| context->object = thisObj; |
| |
| return (jint)context; |
| |
| error: |
| // C++ is OK with deleting NULL ptr. |
| delete context; |
| delete parser; |
| return 0; |
| } |
| |
| static void parserDelete(JNIEnv * env, jobject thisObj, jint nativeParser) |
| { |
| WbxmlParsingContext * context = (WbxmlParsingContext *)nativeParser; |
| delete context->parser; |
| delete context->contentHandler; |
| delete context; |
| } |
| |
| static void parserReset(JNIEnv * env, jobject thisObj, jint nativeParser) |
| { |
| WbxmlParsingContext * context = (WbxmlParsingContext *)nativeParser; |
| WbxmlParser * parser = context->parser; |
| JniWbxmlContentHandler * handler = context->contentHandler; |
| |
| parser->reset(); |
| handler->reset(); |
| parser->setContentHandler(handler); |
| } |
| |
| static void parserParse(JNIEnv * env, jobject thisObj, jint nativeParser, |
| jbyteArray buf, jint len, jboolean isEnd) |
| { |
| WbxmlParsingContext * context = (WbxmlParsingContext *)nativeParser; |
| WbxmlParser * parser = context->parser; |
| context->env = env; |
| |
| jboolean copy; |
| jbyte * bytes = env->GetByteArrayElements(buf, ©); |
| if(copy) { |
| jclass clazz = env->FindClass("java/lang/AssertionError"); |
| env->ThrowNew(clazz, "Unexpected copy"); |
| return; |
| } |
| |
| // make sure the context is updated because we may get called from |
| // a different thread |
| context->env = env; |
| context->object = thisObj; |
| |
| if (parser->parse((char*)bytes, len, isEnd) != WBXML_STATUS_OK) { |
| LOGW("WbxmlParser parse error %d\n", parser->getError()); |
| jclass clazz = env->FindClass(PARSER_EXCEPTION_CLASS); |
| if (clazz == NULL) { |
| LOGE("Can't find class " PARSER_EXCEPTION_CLASS "\n"); |
| return; |
| } |
| env->ThrowNew(clazz, NULL); |
| } |
| |
| jthrowable exception = env->ExceptionOccurred(); |
| if (exception) { |
| env->ExceptionClear(); |
| env->ReleaseByteArrayElements(buf, bytes, JNI_ABORT); |
| env->Throw(exception); |
| } else { |
| env->ReleaseByteArrayElements(buf, bytes, JNI_ABORT); |
| } |
| context->env = NULL; |
| } |
| |
| class JniWbxmlDataHandler : public WbxmlHandler |
| { |
| public: |
| JniWbxmlDataHandler(WbxmlEncodingContext * context) : mContext(context) |
| { |
| } |
| |
| void reset() |
| { |
| // nothing to do |
| } |
| |
| void wbxmlData(const char *data, uint32_t len) |
| { |
| JNIEnv * env = mContext->env; |
| |
| if (env->ExceptionCheck()) { |
| return; |
| } |
| |
| if (encoderCallbackWbxmlData == NULL) { |
| jclass clazz = env->GetObjectClass(mContext->object); |
| encoderCallbackWbxmlData = env->GetMethodID(clazz, "onWbxmlData", "([BI)V"); |
| } |
| jbyteArray byteArray = env->NewByteArray(len); |
| if (byteArray == NULL) { |
| return; |
| } |
| |
| env->SetByteArrayRegion(byteArray, 0, len, (const jbyte*)data); |
| |
| env->CallVoidMethod(mContext->object, encoderCallbackWbxmlData, byteArray, len); |
| } |
| |
| private: |
| WbxmlEncodingContext * mContext; |
| }; |
| |
| static jint encoderCreate(JNIEnv * env, jobject thisObj, int publicId) |
| { |
| WbxmlEncodingContext * context = NULL; |
| WbxmlEncoder * encoder = NULL; |
| JniWbxmlDataHandler * handler = NULL; |
| |
| context = new WbxmlEncodingContext(); |
| if (!context) { |
| goto error; |
| } |
| |
| encoder = new ImpsWbxmlEncoder(publicId); |
| if (!encoder) { |
| goto error; |
| } |
| |
| handler = new JniWbxmlDataHandler(context); |
| if (!handler) { |
| goto error; |
| } |
| |
| encoder->setWbxmlHandler(handler); |
| |
| context->encoder = encoder; |
| context->handler = handler; |
| context->env = env; |
| context->object = thisObj; |
| |
| return (jint)context; |
| |
| error: |
| // C++ is OK with deleting NULL ptr. |
| delete context; |
| delete encoder; |
| return 0; |
| } |
| |
| static void encoderDelete(JNIEnv * env, jobject thisObj, jint nativeEncoder) |
| { |
| WbxmlEncodingContext * context = (WbxmlEncodingContext *)nativeEncoder; |
| delete context->encoder; |
| delete context->handler; |
| delete context; |
| } |
| |
| static void encoderReset(JNIEnv * env, jobject thisObj, jint nativeEncoder) |
| { |
| WbxmlEncodingContext * context = (WbxmlEncodingContext *)nativeEncoder; |
| WbxmlEncoder * encoder = context->encoder; |
| JniWbxmlDataHandler * handler = context->handler; |
| |
| encoder->reset(); |
| handler->reset(); |
| encoder->setWbxmlHandler(handler); |
| } |
| |
| static void encoderStartElement(JNIEnv * env, jobject thisObj, jint nativeEncoder, |
| jstring name, jobjectArray atts) |
| { |
| WbxmlEncodingContext * context = (WbxmlEncodingContext *)nativeEncoder; |
| WbxmlEncoder * encoder = context->encoder; |
| |
| const char ** c_atts = NULL; |
| int count = atts ? env->GetArrayLength(atts) : 0; |
| // TODO: handle out of memory. |
| c_atts = new const char *[count + 1]; |
| if (atts != NULL) { |
| for(int i = 0; i < count; i++) { |
| jstring str = (jstring)env->GetObjectArrayElement(atts, i); |
| c_atts[i] = env->GetStringUTFChars(str, NULL); |
| } |
| } |
| c_atts[count] = NULL; |
| const char * c_name = env->GetStringUTFChars(name, NULL); |
| |
| // make sure the context is updated because we may get called from |
| // a different thread |
| context->env = env; |
| context->object = thisObj; |
| |
| EncoderError ret = encoder->startElement(c_name, c_atts); |
| |
| // encoder->startElement() may result in a call to WbxmlHandler.wbxmlData() |
| // which in turns call back to the Java side. Therefore we might have |
| // a pending Java exception here. Although current WbxmlEncoder always |
| // call WbxmlHandler.wbxmlData() only after a successful complete parsing |
| // but this may change later. |
| |
| if (env->ExceptionCheck() == JNI_FALSE && ret != NO_ERROR) { |
| // only throw a new exception when there is no pending one |
| LOGW("WbxmlEncoder startElement error:%d\n", ret); |
| jclass clazz = env->FindClass(SERIALIZER_EXCEPTION_CLASS); |
| if (clazz == NULL) { |
| LOGE("Can't find class " SERIALIZER_EXCEPTION_CLASS); |
| return; |
| } |
| env->ThrowNew(clazz, "Wbxml encode error"); |
| } |
| |
| jthrowable exception = env->ExceptionOccurred(); |
| if(exception) { |
| env->ExceptionClear(); |
| } |
| |
| env->ReleaseStringUTFChars(name, c_name); |
| for (int i = 0; i < count; i++) { |
| jstring str = (jstring)env->GetObjectArrayElement(atts, i); |
| env->ReleaseStringUTFChars(str, c_atts[i]); |
| } |
| delete[] c_atts; |
| if (exception) { |
| env->Throw(exception); |
| } |
| } |
| |
| static void encoderCharacters(JNIEnv * env, jobject thisObj, jint nativeEncoder, jstring chars) |
| { |
| WbxmlEncodingContext * context = (WbxmlEncodingContext *)nativeEncoder; |
| WbxmlEncoder * encoder = context->encoder; |
| |
| const char * c_chars = env->GetStringUTFChars(chars, NULL); |
| |
| // make sure the context is updated because we may get called from |
| // a different thread |
| context->env = env; |
| context->object = thisObj; |
| |
| EncoderError ret = encoder->characters(c_chars, env->GetStringUTFLength(chars)); |
| |
| // encoder->characters() may result in a call to WbxmlHandler.wbxmlData() |
| // which in turns call back to the Java side. Therefore we might have |
| // a pending Java exception here. Although current WbxmlEncoder always |
| // call WbxmlHandler.wbxmlData() only after a successful complete parsing |
| // but this may change later. |
| |
| if (env->ExceptionCheck() == JNI_FALSE && ret != NO_ERROR) { |
| // only throw a new exception when there is no pending one |
| LOGE("WbxmlEncoder characters error:%d\n", ret); |
| jclass clazz = env->FindClass(SERIALIZER_EXCEPTION_CLASS); |
| if (clazz == NULL) { |
| LOGE("Can't find class " SERIALIZER_EXCEPTION_CLASS); |
| return; |
| } |
| env->ThrowNew(clazz, "Wbxml encode error"); |
| } |
| jthrowable exception = env->ExceptionOccurred(); |
| if (exception) { |
| env->ExceptionClear(); |
| env->ReleaseStringUTFChars(chars, c_chars); |
| env->Throw(exception); |
| } else { |
| env->ReleaseStringUTFChars(chars, c_chars); |
| } |
| } |
| |
| static void encoderEndElement(JNIEnv * env, jobject thisObj, jint nativeEncoder) |
| { |
| WbxmlEncodingContext * context = (WbxmlEncodingContext *)nativeEncoder; |
| WbxmlEncoder * encoder = context->encoder; |
| |
| // make sure the context is updated because we may get called from |
| // a different thread |
| context->env = env; |
| context->object = thisObj; |
| |
| EncoderError ret = encoder->endElement(); |
| |
| // encoder->endElement() may result in a call to WbxmlHandler.wbxmlData() |
| // which in turns call back to the Java side. Therefore we might have |
| // a pending Java exception here. |
| if (env->ExceptionCheck() == JNI_FALSE && ret != NO_ERROR) { |
| // only throw a new exception when there is no pending one |
| LOGE("WbxmlEncoder endElement error:%d\n", ret); |
| jclass clazz = env->FindClass(SERIALIZER_EXCEPTION_CLASS); |
| if (clazz == NULL) { |
| LOGE("Can't find class " SERIALIZER_EXCEPTION_CLASS); |
| return; |
| } |
| env->ThrowNew(clazz, "Wbxml encode error"); |
| } |
| } |
| |
| /** |
| * Table of methods associated with WbxmlParser. |
| */ |
| static JNINativeMethod parserMethods[] = { |
| /* name, signature, funcPtr */ |
| { "nativeStaticInitialize", "()V", (void*) parserStaticInitialize }, |
| { "nativeCreate", "(Ljava/lang/String;)I", (void*) parserCreate }, |
| { "nativeRelease", "(I)V", (void*) parserDelete }, |
| { "nativeReset", "(I)V", (void*) parserReset }, |
| { "nativeParse", "(I[BIZ)V", (void*) parserParse }, |
| }; |
| |
| /** |
| * Table of methods associated with WbxmlSerializer. |
| */ |
| static JNINativeMethod encoderMethods[] = { |
| /* name, signature, funcPtr */ |
| { "nativeCreate", "(I)I", (void*) encoderCreate }, |
| { "nativeRelease", "(I)V", (void*) encoderDelete }, |
| { "nativeReset", "(I)V", (void*) encoderReset }, |
| { "nativeStartElement", "(ILjava/lang/String;[Ljava/lang/String;)V", (void*) encoderStartElement }, |
| { "nativeCharacters", "(ILjava/lang/String;)V", (void*) encoderCharacters }, |
| { "nativeEndElement", "(I)V", (void*) encoderEndElement }, |
| }; |
| |
| /* |
| * Register several native methods for one class. |
| */ |
| static int registerNativeMethods(JNIEnv* env, const char* className, |
| JNINativeMethod* methods, int numMethods) |
| { |
| jclass clazz; |
| |
| clazz = env->FindClass(className); |
| if (clazz == NULL) { |
| LOGE("Native registration unable to find class '%s'\n", className); |
| return JNI_FALSE; |
| } |
| if (env->RegisterNatives(clazz, methods, numMethods) < 0) { |
| LOGE("RegisterNatives failed for '%s'\n", className); |
| return JNI_FALSE; |
| } |
| |
| return JNI_TRUE; |
| } |
| |
| /* |
| * Register native methods. |
| */ |
| static int registerNatives(JNIEnv* env) |
| { |
| if (!registerNativeMethods(env, "com/android/im/imps/WbxmlParser", |
| parserMethods, sizeof(parserMethods) / sizeof(parserMethods[0]))) |
| return JNI_FALSE; |
| |
| if (!registerNativeMethods(env, "com/android/im/imps/WbxmlSerializer", |
| encoderMethods, sizeof(encoderMethods) / sizeof(encoderMethods[0]))) |
| return JNI_FALSE; |
| |
| return JNI_TRUE; |
| } |
| |
| /* |
| * Set some test stuff up. |
| * |
| * Returns the JNI version on success, -1 on failure. |
| */ |
| jint JNI_OnLoad(JavaVM* vm, void* reserved) |
| { |
| JNIEnv* env = NULL; |
| jint result = -1; |
| |
| if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) { |
| LOGE("ERROR: GetEnv failed\n"); |
| goto bail; |
| } |
| assert(env != NULL); |
| |
| // LOGI("In WbxmlParser JNI_OnLoad\n"); |
| |
| if (!registerNatives(env)) { |
| LOGE("ERROR: WbxmlParser native registration failed\n"); |
| goto bail; |
| } |
| |
| /* success -- return valid version number */ |
| result = JNI_VERSION_1_4; |
| |
| bail: |
| return result; |
| } |
| |