2 * Copyright (C) 2007 Esmertec AG.
3 * Copyright (C) 2007 The Android Open Source Project
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
18 #define LOG_TAG "wbxml"
21 #include <cutils/jstring.h>
23 #include "wbxml_parser.h"
24 #include "imps_encoder.h"
25 #include "csp13_hash.h"
27 // FIXME: dalvik seems to have problem throwing non-system exceptions. Use IAE for now.
29 #define PARSER_EXCEPTION_CLASS "java/lang/IllegalArgumentException"
30 #define SERIALIZER_EXCEPTION_CLASS "java/lang/IllegalArgumentException"
32 #define PARSER_EXCEPTION_CLASS "com/android/im/imps/ParserException"
33 #define SERIALIZER_EXCEPTION_CLASS "com/android/im/imps/SerializerException"
36 class JniWbxmlContentHandler;
37 class JniWbxmlDataHandler;
39 struct WbxmlParsingContext
41 /** The JNI enviromenet. */
43 /** The Java parser object. */
45 /** The wbxml parser. */
47 /** The content handler.*/
48 JniWbxmlContentHandler * contentHandler;
51 struct WbxmlEncodingContext
53 /** The JNI enviroment. */
55 /** The Java encoder object. */
57 /** The wbxml encoder. */
58 WbxmlEncoder * encoder;
59 /** The handler to get encoding result. */
60 JniWbxmlDataHandler * handler;
63 static jmethodID parserCallbackStartElement;
64 static jmethodID parserCallbackEndElement;
65 static jmethodID parserCallbackCharacters;
66 static jmethodID encoderCallbackWbxmlData;
67 static jclass stringClass;
70 * Copies UTF-8 characters into the buffer.
72 static jcharArray bytesToCharArray(JNIEnv * env, const char *data, int length, size_t * out_len)
74 // TODO: store the buffer in ParsingContext to reuse it instead create a
76 jcharArray buffer = env->NewCharArray(length);
81 // Get a native reference to the buffer.
83 jchar *nativeBuffer = env->GetCharArrayElements(buffer, ©);
85 jclass clazz = env->FindClass("java/lang/AssertionError");
86 env->ThrowNew(clazz, "Unexpected copy");
90 // Decode UTF-8 characters into the buffer.
91 strcpylen8to16((char16_t *) nativeBuffer, data, length, out_len);
93 // Release our native reference.
94 env->ReleaseCharArrayElements(buffer, nativeBuffer, JNI_ABORT);
99 class JniWbxmlContentHandler : public DefaultWbxmlContentHandler
102 JniWbxmlContentHandler(WbxmlParsingContext * context):mContext(context)
111 void startElement(const char * name, const vector<Attribute> & atts)
113 JNIEnv * env = mContext->env;
115 if(env->ExceptionCheck()) return;
119 //TODO cache jstrings for later use?
120 jstring localName = env->NewStringUTF(name);
121 jobjectArray attrNames = NULL;
122 jobjectArray attrValues = NULL;
123 int count = atts.size();
125 attrNames = env->NewObjectArray(count, stringClass, NULL);
126 attrValues = env->NewObjectArray(count, stringClass, NULL);
129 for(int i = 0; i < count; i++) {
130 jstring attrName = env->NewStringUTF(atts[i].name.c_str());
131 jstring attrValue = env->NewStringUTF(atts[i].value.c_str());
132 env->SetObjectArrayElement(attrNames, i, attrName);
133 env->SetObjectArrayElement(attrValues, i, attrValue);
136 jobject javaParser = mContext->object;
137 env->CallVoidMethod(javaParser, parserCallbackStartElement, localName, attrNames, attrValues);
138 env->DeleteLocalRef(localName);
139 for(int i = 0; i < count; i++) {
140 env->DeleteLocalRef(env->GetObjectArrayElement(attrNames, i));
141 env->DeleteLocalRef(env->GetObjectArrayElement(attrValues, i));
145 void endElement(const char * name)
147 JNIEnv * env = mContext->env;
149 if(env->ExceptionCheck()) return;
153 jstring localName = env->NewStringUTF(name);
154 jobject javaParser = mContext->object;
156 env->CallVoidMethod(javaParser, parserCallbackEndElement, localName);
157 env->DeleteLocalRef(localName);
160 void characters(const char * data, int len)
162 JNIEnv * env = mContext->env;
164 if(env->ExceptionCheck()) return;
167 jcharArray buffer = bytesToCharArray(env, data, len, &utf16length);
168 jobject javaParser = mContext->object;
169 env->CallVoidMethod(javaParser, parserCallbackCharacters, buffer, utf16length);
170 env->DeleteLocalRef(buffer);
173 void opaque(const char * data, int len)
175 JNIEnv * env = mContext->env;
177 if(env->ExceptionCheck()) return;
180 if (csp13IsIntegerTag(mCurTag.c_str())) {
183 val |= (unsigned char)*data++;
187 sprintf(buf, "%d", val);
189 jcharArray buffer = bytesToCharArray(env, buf, strlen(buf), &utf16length);
190 jobject javaParser = mContext->object;
191 env->CallVoidMethod(javaParser, parserCallbackCharacters, buffer, utf16length);
192 env->DeleteLocalRef(buffer);
194 // FIXME: handle DateTime and binary data too
197 WbxmlParsingContext * mContext;
201 static void parserStaticInitialize(JNIEnv * env, jclass clazz)
203 parserCallbackStartElement = env->GetMethodID((jclass) clazz, "startElement",
204 "(Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/String;)V");
206 parserCallbackEndElement = env->GetMethodID((jclass) clazz, "endElement",
207 "(Ljava/lang/String;)V");
209 parserCallbackCharacters = env->GetMethodID((jclass) clazz, "characters",
212 stringClass = env->FindClass("java/lang/String");
215 static jint parserCreate(JNIEnv * env, jobject thisObj, jstring encoding)
218 WbxmlParsingContext * context = NULL;
219 WbxmlParser * parser = NULL;
220 JniWbxmlContentHandler * handler = NULL;
222 context = new WbxmlParsingContext();
227 parser = new WbxmlParser(0);
232 handler = new JniWbxmlContentHandler(context);
237 parser->setContentHandler(handler);
238 context->parser = parser;
239 context->contentHandler = handler;
241 context->object = thisObj;
243 return (jint)context;
246 // C++ is OK with deleting NULL ptr.
252 static void parserDelete(JNIEnv * env, jobject thisObj, jint nativeParser)
254 WbxmlParsingContext * context = (WbxmlParsingContext *)nativeParser;
255 delete context->parser;
256 delete context->contentHandler;
260 static void parserReset(JNIEnv * env, jobject thisObj, jint nativeParser)
262 WbxmlParsingContext * context = (WbxmlParsingContext *)nativeParser;
263 WbxmlParser * parser = context->parser;
264 JniWbxmlContentHandler * handler = context->contentHandler;
268 parser->setContentHandler(handler);
271 static void parserParse(JNIEnv * env, jobject thisObj, jint nativeParser,
272 jbyteArray buf, jint len, jboolean isEnd)
274 WbxmlParsingContext * context = (WbxmlParsingContext *)nativeParser;
275 WbxmlParser * parser = context->parser;
279 jbyte * bytes = env->GetByteArrayElements(buf, ©);
281 jclass clazz = env->FindClass("java/lang/AssertionError");
282 env->ThrowNew(clazz, "Unexpected copy");
286 // make sure the context is updated because we may get called from
287 // a different thread
289 context->object = thisObj;
291 if (parser->parse((char*)bytes, len, isEnd) != WBXML_STATUS_OK) {
292 LOGW("WbxmlParser parse error %d\n", parser->getError());
293 jclass clazz = env->FindClass(PARSER_EXCEPTION_CLASS);
295 LOGE("Can't find class " PARSER_EXCEPTION_CLASS "\n");
298 env->ThrowNew(clazz, NULL);
301 jthrowable exception = env->ExceptionOccurred();
303 env->ExceptionClear();
304 env->ReleaseByteArrayElements(buf, bytes, JNI_ABORT);
305 env->Throw(exception);
307 env->ReleaseByteArrayElements(buf, bytes, JNI_ABORT);
312 class JniWbxmlDataHandler : public WbxmlHandler
315 JniWbxmlDataHandler(WbxmlEncodingContext * context) : mContext(context)
324 void wbxmlData(const char *data, uint32_t len)
326 JNIEnv * env = mContext->env;
328 if (env->ExceptionCheck()) {
332 if (encoderCallbackWbxmlData == NULL) {
333 jclass clazz = env->GetObjectClass(mContext->object);
334 encoderCallbackWbxmlData = env->GetMethodID(clazz, "onWbxmlData", "([BI)V");
336 jbyteArray byteArray = env->NewByteArray(len);
337 if (byteArray == NULL) {
341 env->SetByteArrayRegion(byteArray, 0, len, (const jbyte*)data);
343 env->CallVoidMethod(mContext->object, encoderCallbackWbxmlData, byteArray, len);
347 WbxmlEncodingContext * mContext;
350 static jint encoderCreate(JNIEnv * env, jobject thisObj, int publicId)
352 WbxmlEncodingContext * context = NULL;
353 WbxmlEncoder * encoder = NULL;
354 JniWbxmlDataHandler * handler = NULL;
356 context = new WbxmlEncodingContext();
361 encoder = new ImpsWbxmlEncoder(publicId);
366 handler = new JniWbxmlDataHandler(context);
371 encoder->setWbxmlHandler(handler);
373 context->encoder = encoder;
374 context->handler = handler;
376 context->object = thisObj;
378 return (jint)context;
381 // C++ is OK with deleting NULL ptr.
387 static void encoderDelete(JNIEnv * env, jobject thisObj, jint nativeEncoder)
389 WbxmlEncodingContext * context = (WbxmlEncodingContext *)nativeEncoder;
390 delete context->encoder;
391 delete context->handler;
395 static void encoderReset(JNIEnv * env, jobject thisObj, jint nativeEncoder)
397 WbxmlEncodingContext * context = (WbxmlEncodingContext *)nativeEncoder;
398 WbxmlEncoder * encoder = context->encoder;
399 JniWbxmlDataHandler * handler = context->handler;
403 encoder->setWbxmlHandler(handler);
406 static void encoderStartElement(JNIEnv * env, jobject thisObj, jint nativeEncoder,
407 jstring name, jobjectArray atts)
409 WbxmlEncodingContext * context = (WbxmlEncodingContext *)nativeEncoder;
410 WbxmlEncoder * encoder = context->encoder;
412 const char ** c_atts = NULL;
413 int count = atts ? env->GetArrayLength(atts) : 0;
414 // TODO: handle out of memory.
415 c_atts = new const char *[count + 1];
417 for(int i = 0; i < count; i++) {
418 jstring str = (jstring)env->GetObjectArrayElement(atts, i);
419 c_atts[i] = env->GetStringUTFChars(str, NULL);
422 c_atts[count] = NULL;
423 const char * c_name = env->GetStringUTFChars(name, NULL);
425 // make sure the context is updated because we may get called from
426 // a different thread
428 context->object = thisObj;
430 EncoderError ret = encoder->startElement(c_name, c_atts);
432 // encoder->startElement() may result in a call to WbxmlHandler.wbxmlData()
433 // which in turns call back to the Java side. Therefore we might have
434 // a pending Java exception here. Although current WbxmlEncoder always
435 // call WbxmlHandler.wbxmlData() only after a successful complete parsing
436 // but this may change later.
438 if (env->ExceptionCheck() == JNI_FALSE && ret != NO_ERROR) {
439 // only throw a new exception when there is no pending one
440 LOGW("WbxmlEncoder startElement error:%d\n", ret);
441 jclass clazz = env->FindClass(SERIALIZER_EXCEPTION_CLASS);
443 LOGE("Can't find class " SERIALIZER_EXCEPTION_CLASS);
446 env->ThrowNew(clazz, "Wbxml encode error");
449 jthrowable exception = env->ExceptionOccurred();
451 env->ExceptionClear();
454 env->ReleaseStringUTFChars(name, c_name);
455 for (int i = 0; i < count; i++) {
456 jstring str = (jstring)env->GetObjectArrayElement(atts, i);
457 env->ReleaseStringUTFChars(str, c_atts[i]);
461 env->Throw(exception);
465 static void encoderCharacters(JNIEnv * env, jobject thisObj, jint nativeEncoder, jstring chars)
467 WbxmlEncodingContext * context = (WbxmlEncodingContext *)nativeEncoder;
468 WbxmlEncoder * encoder = context->encoder;
470 const char * c_chars = env->GetStringUTFChars(chars, NULL);
472 // make sure the context is updated because we may get called from
473 // a different thread
475 context->object = thisObj;
477 EncoderError ret = encoder->characters(c_chars, env->GetStringUTFLength(chars));
479 // encoder->characters() may result in a call to WbxmlHandler.wbxmlData()
480 // which in turns call back to the Java side. Therefore we might have
481 // a pending Java exception here. Although current WbxmlEncoder always
482 // call WbxmlHandler.wbxmlData() only after a successful complete parsing
483 // but this may change later.
485 if (env->ExceptionCheck() == JNI_FALSE && ret != NO_ERROR) {
486 // only throw a new exception when there is no pending one
487 LOGE("WbxmlEncoder characters error:%d\n", ret);
488 jclass clazz = env->FindClass(SERIALIZER_EXCEPTION_CLASS);
490 LOGE("Can't find class " SERIALIZER_EXCEPTION_CLASS);
493 env->ThrowNew(clazz, "Wbxml encode error");
495 jthrowable exception = env->ExceptionOccurred();
497 env->ExceptionClear();
498 env->ReleaseStringUTFChars(chars, c_chars);
499 env->Throw(exception);
501 env->ReleaseStringUTFChars(chars, c_chars);
505 static void encoderEndElement(JNIEnv * env, jobject thisObj, jint nativeEncoder)
507 WbxmlEncodingContext * context = (WbxmlEncodingContext *)nativeEncoder;
508 WbxmlEncoder * encoder = context->encoder;
510 // make sure the context is updated because we may get called from
511 // a different thread
513 context->object = thisObj;
515 EncoderError ret = encoder->endElement();
517 // encoder->endElement() may result in a call to WbxmlHandler.wbxmlData()
518 // which in turns call back to the Java side. Therefore we might have
519 // a pending Java exception here.
520 if (env->ExceptionCheck() == JNI_FALSE && ret != NO_ERROR) {
521 // only throw a new exception when there is no pending one
522 LOGE("WbxmlEncoder endElement error:%d\n", ret);
523 jclass clazz = env->FindClass(SERIALIZER_EXCEPTION_CLASS);
525 LOGE("Can't find class " SERIALIZER_EXCEPTION_CLASS);
528 env->ThrowNew(clazz, "Wbxml encode error");
533 * Table of methods associated with WbxmlParser.
535 static JNINativeMethod parserMethods[] = {
536 /* name, signature, funcPtr */
537 { "nativeStaticInitialize", "()V", (void*) parserStaticInitialize },
538 { "nativeCreate", "(Ljava/lang/String;)I", (void*) parserCreate },
539 { "nativeRelease", "(I)V", (void*) parserDelete },
540 { "nativeReset", "(I)V", (void*) parserReset },
541 { "nativeParse", "(I[BIZ)V", (void*) parserParse },
545 * Table of methods associated with WbxmlSerializer.
547 static JNINativeMethod encoderMethods[] = {
548 /* name, signature, funcPtr */
549 { "nativeCreate", "(I)I", (void*) encoderCreate },
550 { "nativeRelease", "(I)V", (void*) encoderDelete },
551 { "nativeReset", "(I)V", (void*) encoderReset },
552 { "nativeStartElement", "(ILjava/lang/String;[Ljava/lang/String;)V", (void*) encoderStartElement },
553 { "nativeCharacters", "(ILjava/lang/String;)V", (void*) encoderCharacters },
554 { "nativeEndElement", "(I)V", (void*) encoderEndElement },
558 * Register several native methods for one class.
560 static int registerNativeMethods(JNIEnv* env, const char* className,
561 JNINativeMethod* methods, int numMethods)
565 clazz = env->FindClass(className);
567 LOGE("Native registration unable to find class '%s'\n", className);
570 if (env->RegisterNatives(clazz, methods, numMethods) < 0) {
571 LOGE("RegisterNatives failed for '%s'\n", className);
579 * Register native methods.
581 static int registerNatives(JNIEnv* env)
583 if (!registerNativeMethods(env, "com/android/im/imps/WbxmlParser",
584 parserMethods, sizeof(parserMethods) / sizeof(parserMethods[0])))
587 if (!registerNativeMethods(env, "com/android/im/imps/WbxmlSerializer",
588 encoderMethods, sizeof(encoderMethods) / sizeof(encoderMethods[0])))
595 * Set some test stuff up.
597 * Returns the JNI version on success, -1 on failure.
599 jint JNI_OnLoad(JavaVM* vm, void* reserved)
604 if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
605 LOGE("ERROR: GetEnv failed\n");
610 // LOGI("In WbxmlParser JNI_OnLoad\n");
612 if (!registerNatives(env)) {
613 LOGE("ERROR: WbxmlParser native registration failed\n");
617 /* success -- return valid version number */
618 result = JNI_VERSION_1_4;