1 /*
2 * Copyright (C) 2007 Esmertec AG.
3 * Copyright (C) 2007 The Android Open Source Project
4 *
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
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
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.
16 */
17
18 #define LOG_TAG "wbxml"
19
20 #include <jni.h>
21 #include <cutils/jstring.h>
22
23 #include "wbxml_parser.h"
24 #include "imps_encoder.h"
25 #include "csp13_hash.h"
26
27 // FIXME: dalvik seems to have problem throwing non-system exceptions. Use IAE for now.
28 #if 1
29 #define PARSER_EXCEPTION_CLASS "java/lang/IllegalArgumentException"
30 #define SERIALIZER_EXCEPTION_CLASS "java/lang/IllegalArgumentException"
31 #else
32 #define PARSER_EXCEPTION_CLASS "com/android/im/imps/ParserException"
33 #define SERIALIZER_EXCEPTION_CLASS "com/android/im/imps/SerializerException"
34 #endif
35
36 class JniWbxmlContentHandler;
37 class JniWbxmlDataHandler;
38
39 struct WbxmlParsingContext
40 {
41 /** The JNI enviromenet. */
42 JNIEnv * env;
43 /** The Java parser object. */
44 jobject object;
45 /** The wbxml parser. */
46 WbxmlParser * parser;
47 /** The content handler.*/
48 JniWbxmlContentHandler * contentHandler;
49 };
50
51 struct WbxmlEncodingContext
52 {
53 /** The JNI enviroment. */
54 JNIEnv * env;
55 /** The Java encoder object. */
56 jobject object;
57 /** The wbxml encoder. */
58 WbxmlEncoder * encoder;
59 /** The handler to get encoding result. */
60 JniWbxmlDataHandler * handler;
61 };
62
63 static jmethodID parserCallbackStartElement;
64 static jmethodID parserCallbackEndElement;
65 static jmethodID parserCallbackCharacters;
66 static jmethodID encoderCallbackWbxmlData;
67 static jclass stringClass;
68
69 /**
70 * Copies UTF-8 characters into the buffer.
71 */
bytesToCharArray(JNIEnv * env,const char * data,int length,size_t * out_len)72 static jcharArray bytesToCharArray(JNIEnv * env, const char *data, int length, size_t * out_len)
73 {
74 // TODO: store the buffer in ParsingContext to reuse it instead create a
75 // new one everytime.
76 jcharArray buffer = env->NewCharArray(length);
77 if(buffer == NULL) {
78 return NULL;
79 }
80
81 // Get a native reference to the buffer.
82 jboolean copy;
83 jchar *nativeBuffer = env->GetCharArrayElements(buffer, ©);
84 if (copy) {
85 jclass clazz = env->FindClass("java/lang/AssertionError");
86 env->ThrowNew(clazz, "Unexpected copy");
87 return NULL;
88 }
89
90 // Decode UTF-8 characters into the buffer.
91 strcpylen8to16((char16_t *) nativeBuffer, data, length, out_len);
92
93 // Release our native reference.
94 env->ReleaseCharArrayElements(buffer, nativeBuffer, JNI_ABORT);
95
96 return buffer;
97 }
98
99 class JniWbxmlContentHandler : public DefaultWbxmlContentHandler
100 {
101 public:
JniWbxmlContentHandler(WbxmlParsingContext * context)102 JniWbxmlContentHandler(WbxmlParsingContext * context):mContext(context)
103 {
104 }
105
reset(void)106 void reset(void)
107 {
108 mCurTag.clear();
109 }
110
startElement(const char * name,const vector<Attribute> & atts)111 void startElement(const char * name, const vector<Attribute> & atts)
112 {
113 JNIEnv * env = mContext->env;
114
115 if(env->ExceptionCheck()) return;
116
117 mCurTag = name;
118
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();
124 if(count > 0) {
125 attrNames = env->NewObjectArray(count, stringClass, NULL);
126 attrValues = env->NewObjectArray(count, stringClass, NULL);
127 }
128
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);
134 }
135
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));
142 }
143 }
144
endElement(const char * name)145 void endElement(const char * name)
146 {
147 JNIEnv * env = mContext->env;
148
149 if(env->ExceptionCheck()) return;
150
151 mCurTag.clear();
152
153 jstring localName = env->NewStringUTF(name);
154 jobject javaParser = mContext->object;
155
156 env->CallVoidMethod(javaParser, parserCallbackEndElement, localName);
157 env->DeleteLocalRef(localName);
158 }
159
characters(const char * data,int len)160 void characters(const char * data, int len)
161 {
162 JNIEnv * env = mContext->env;
163
164 if(env->ExceptionCheck()) return;
165
166 size_t utf16length;
167 jcharArray buffer = bytesToCharArray(env, data, len, &utf16length);
168 jobject javaParser = mContext->object;
169 env->CallVoidMethod(javaParser, parserCallbackCharacters, buffer, utf16length);
170 env->DeleteLocalRef(buffer);
171 }
172
opaque(const char * data,int len)173 void opaque(const char * data, int len)
174 {
175 JNIEnv * env = mContext->env;
176
177 if(env->ExceptionCheck()) return;
178
179 int val = 0;
180 if (csp13IsIntegerTag(mCurTag.c_str())) {
181 while (len--){
182 val <<= 8;
183 val |= (unsigned char)*data++;
184 }
185
186 char buf[20];
187 sprintf(buf, "%d", val);
188 size_t utf16length;
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);
193 }
194 // FIXME: handle DateTime and binary data too
195 }
196 private:
197 WbxmlParsingContext * mContext;
198 string mCurTag;
199 };
200
parserStaticInitialize(JNIEnv * env,jclass clazz)201 static void parserStaticInitialize(JNIEnv * env, jclass clazz)
202 {
203 parserCallbackStartElement = env->GetMethodID((jclass) clazz, "startElement",
204 "(Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/String;)V");
205
206 parserCallbackEndElement = env->GetMethodID((jclass) clazz, "endElement",
207 "(Ljava/lang/String;)V");
208
209 parserCallbackCharacters = env->GetMethodID((jclass) clazz, "characters",
210 "([CI)V");
211
212 stringClass = env->FindClass("java/lang/String");
213 }
214
parserCreate(JNIEnv * env,jobject thisObj,jstring encoding)215 static jint parserCreate(JNIEnv * env, jobject thisObj, jstring encoding)
216 {
217 // TODO: encoding
218 WbxmlParsingContext * context = NULL;
219 WbxmlParser * parser = NULL;
220 JniWbxmlContentHandler * handler = NULL;
221
222 context = new WbxmlParsingContext();
223 if (!context) {
224 goto error;
225 }
226
227 parser = new WbxmlParser(0);
228 if (!parser) {
229 goto error;
230 }
231
232 handler = new JniWbxmlContentHandler(context);
233 if (!handler) {
234 goto error;
235 }
236
237 parser->setContentHandler(handler);
238 context->parser = parser;
239 context->contentHandler = handler;
240 context->env = env;
241 context->object = thisObj;
242
243 return (jint)context;
244
245 error:
246 // C++ is OK with deleting NULL ptr.
247 delete context;
248 delete parser;
249 return 0;
250 }
251
parserDelete(JNIEnv * env,jobject thisObj,jint nativeParser)252 static void parserDelete(JNIEnv * env, jobject thisObj, jint nativeParser)
253 {
254 WbxmlParsingContext * context = (WbxmlParsingContext *)nativeParser;
255 delete context->parser;
256 delete context->contentHandler;
257 delete context;
258 }
259
parserReset(JNIEnv * env,jobject thisObj,jint nativeParser)260 static void parserReset(JNIEnv * env, jobject thisObj, jint nativeParser)
261 {
262 WbxmlParsingContext * context = (WbxmlParsingContext *)nativeParser;
263 WbxmlParser * parser = context->parser;
264 JniWbxmlContentHandler * handler = context->contentHandler;
265
266 parser->reset();
267 handler->reset();
268 parser->setContentHandler(handler);
269 }
270
parserParse(JNIEnv * env,jobject thisObj,jint nativeParser,jbyteArray buf,jint len,jboolean isEnd)271 static void parserParse(JNIEnv * env, jobject thisObj, jint nativeParser,
272 jbyteArray buf, jint len, jboolean isEnd)
273 {
274 WbxmlParsingContext * context = (WbxmlParsingContext *)nativeParser;
275 WbxmlParser * parser = context->parser;
276 context->env = env;
277
278 jboolean copy;
279 jbyte * bytes = env->GetByteArrayElements(buf, ©);
280 if(copy) {
281 jclass clazz = env->FindClass("java/lang/AssertionError");
282 env->ThrowNew(clazz, "Unexpected copy");
283 return;
284 }
285
286 // make sure the context is updated because we may get called from
287 // a different thread
288 context->env = env;
289 context->object = thisObj;
290
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);
294 if (clazz == NULL) {
295 LOGE("Can't find class " PARSER_EXCEPTION_CLASS "\n");
296 return;
297 }
298 env->ThrowNew(clazz, NULL);
299 }
300
301 jthrowable exception = env->ExceptionOccurred();
302 if (exception) {
303 env->ExceptionClear();
304 env->ReleaseByteArrayElements(buf, bytes, JNI_ABORT);
305 env->Throw(exception);
306 } else {
307 env->ReleaseByteArrayElements(buf, bytes, JNI_ABORT);
308 }
309 context->env = NULL;
310 }
311
312 class JniWbxmlDataHandler : public WbxmlHandler
313 {
314 public:
JniWbxmlDataHandler(WbxmlEncodingContext * context)315 JniWbxmlDataHandler(WbxmlEncodingContext * context) : mContext(context)
316 {
317 }
318
reset()319 void reset()
320 {
321 // nothing to do
322 }
323
wbxmlData(const char * data,uint32_t len)324 void wbxmlData(const char *data, uint32_t len)
325 {
326 JNIEnv * env = mContext->env;
327
328 if (env->ExceptionCheck()) {
329 return;
330 }
331
332 if (encoderCallbackWbxmlData == NULL) {
333 jclass clazz = env->GetObjectClass(mContext->object);
334 encoderCallbackWbxmlData = env->GetMethodID(clazz, "onWbxmlData", "([BI)V");
335 }
336 jbyteArray byteArray = env->NewByteArray(len);
337 if (byteArray == NULL) {
338 return;
339 }
340
341 env->SetByteArrayRegion(byteArray, 0, len, (const jbyte*)data);
342
343 env->CallVoidMethod(mContext->object, encoderCallbackWbxmlData, byteArray, len);
344 }
345
346 private:
347 WbxmlEncodingContext * mContext;
348 };
349
encoderCreate(JNIEnv * env,jobject thisObj,int publicId)350 static jint encoderCreate(JNIEnv * env, jobject thisObj, int publicId)
351 {
352 WbxmlEncodingContext * context = NULL;
353 WbxmlEncoder * encoder = NULL;
354 JniWbxmlDataHandler * handler = NULL;
355
356 context = new WbxmlEncodingContext();
357 if (!context) {
358 goto error;
359 }
360
361 encoder = new ImpsWbxmlEncoder(publicId);
362 if (!encoder) {
363 goto error;
364 }
365
366 handler = new JniWbxmlDataHandler(context);
367 if (!handler) {
368 goto error;
369 }
370
371 encoder->setWbxmlHandler(handler);
372
373 context->encoder = encoder;
374 context->handler = handler;
375 context->env = env;
376 context->object = thisObj;
377
378 return (jint)context;
379
380 error:
381 // C++ is OK with deleting NULL ptr.
382 delete context;
383 delete encoder;
384 return 0;
385 }
386
encoderDelete(JNIEnv * env,jobject thisObj,jint nativeEncoder)387 static void encoderDelete(JNIEnv * env, jobject thisObj, jint nativeEncoder)
388 {
389 WbxmlEncodingContext * context = (WbxmlEncodingContext *)nativeEncoder;
390 delete context->encoder;
391 delete context->handler;
392 delete context;
393 }
394
encoderReset(JNIEnv * env,jobject thisObj,jint nativeEncoder)395 static void encoderReset(JNIEnv * env, jobject thisObj, jint nativeEncoder)
396 {
397 WbxmlEncodingContext * context = (WbxmlEncodingContext *)nativeEncoder;
398 WbxmlEncoder * encoder = context->encoder;
399 JniWbxmlDataHandler * handler = context->handler;
400
401 encoder->reset();
402 handler->reset();
403 encoder->setWbxmlHandler(handler);
404 }
405
encoderStartElement(JNIEnv * env,jobject thisObj,jint nativeEncoder,jstring name,jobjectArray atts)406 static void encoderStartElement(JNIEnv * env, jobject thisObj, jint nativeEncoder,
407 jstring name, jobjectArray atts)
408 {
409 WbxmlEncodingContext * context = (WbxmlEncodingContext *)nativeEncoder;
410 WbxmlEncoder * encoder = context->encoder;
411
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];
416 if (atts != NULL) {
417 for(int i = 0; i < count; i++) {
418 jstring str = (jstring)env->GetObjectArrayElement(atts, i);
419 c_atts[i] = env->GetStringUTFChars(str, NULL);
420 }
421 }
422 c_atts[count] = NULL;
423 const char * c_name = env->GetStringUTFChars(name, NULL);
424
425 // make sure the context is updated because we may get called from
426 // a different thread
427 context->env = env;
428 context->object = thisObj;
429
430 EncoderError ret = encoder->startElement(c_name, c_atts);
431
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.
437
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);
442 if (clazz == NULL) {
443 LOGE("Can't find class " SERIALIZER_EXCEPTION_CLASS);
444 return;
445 }
446 env->ThrowNew(clazz, "Wbxml encode error");
447 }
448
449 jthrowable exception = env->ExceptionOccurred();
450 if(exception) {
451 env->ExceptionClear();
452 }
453
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]);
458 }
459 delete[] c_atts;
460 if (exception) {
461 env->Throw(exception);
462 }
463 }
464
encoderCharacters(JNIEnv * env,jobject thisObj,jint nativeEncoder,jstring chars)465 static void encoderCharacters(JNIEnv * env, jobject thisObj, jint nativeEncoder, jstring chars)
466 {
467 WbxmlEncodingContext * context = (WbxmlEncodingContext *)nativeEncoder;
468 WbxmlEncoder * encoder = context->encoder;
469
470 const char * c_chars = env->GetStringUTFChars(chars, NULL);
471
472 // make sure the context is updated because we may get called from
473 // a different thread
474 context->env = env;
475 context->object = thisObj;
476
477 EncoderError ret = encoder->characters(c_chars, env->GetStringUTFLength(chars));
478
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.
484
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);
489 if (clazz == NULL) {
490 LOGE("Can't find class " SERIALIZER_EXCEPTION_CLASS);
491 return;
492 }
493 env->ThrowNew(clazz, "Wbxml encode error");
494 }
495 jthrowable exception = env->ExceptionOccurred();
496 if (exception) {
497 env->ExceptionClear();
498 env->ReleaseStringUTFChars(chars, c_chars);
499 env->Throw(exception);
500 } else {
501 env->ReleaseStringUTFChars(chars, c_chars);
502 }
503 }
504
encoderEndElement(JNIEnv * env,jobject thisObj,jint nativeEncoder)505 static void encoderEndElement(JNIEnv * env, jobject thisObj, jint nativeEncoder)
506 {
507 WbxmlEncodingContext * context = (WbxmlEncodingContext *)nativeEncoder;
508 WbxmlEncoder * encoder = context->encoder;
509
510 // make sure the context is updated because we may get called from
511 // a different thread
512 context->env = env;
513 context->object = thisObj;
514
515 EncoderError ret = encoder->endElement();
516
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);
524 if (clazz == NULL) {
525 LOGE("Can't find class " SERIALIZER_EXCEPTION_CLASS);
526 return;
527 }
528 env->ThrowNew(clazz, "Wbxml encode error");
529 }
530 }
531
532 /**
533 * Table of methods associated with WbxmlParser.
534 */
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 },
542 };
543
544 /**
545 * Table of methods associated with WbxmlSerializer.
546 */
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 },
555 };
556
557 /*
558 * Register several native methods for one class.
559 */
registerNativeMethods(JNIEnv * env,const char * className,JNINativeMethod * methods,int numMethods)560 static int registerNativeMethods(JNIEnv* env, const char* className,
561 JNINativeMethod* methods, int numMethods)
562 {
563 jclass clazz;
564
565 clazz = env->FindClass(className);
566 if (clazz == NULL) {
567 LOGE("Native registration unable to find class '%s'\n", className);
568 return JNI_FALSE;
569 }
570 if (env->RegisterNatives(clazz, methods, numMethods) < 0) {
571 LOGE("RegisterNatives failed for '%s'\n", className);
572 return JNI_FALSE;
573 }
574
575 return JNI_TRUE;
576 }
577
578 /*
579 * Register native methods.
580 */
registerNatives(JNIEnv * env)581 static int registerNatives(JNIEnv* env)
582 {
583 if (!registerNativeMethods(env, "com/android/im/imps/WbxmlParser",
584 parserMethods, sizeof(parserMethods) / sizeof(parserMethods[0])))
585 return JNI_FALSE;
586
587 if (!registerNativeMethods(env, "com/android/im/imps/WbxmlSerializer",
588 encoderMethods, sizeof(encoderMethods) / sizeof(encoderMethods[0])))
589 return JNI_FALSE;
590
591 return JNI_TRUE;
592 }
593
594 /*
595 * Set some test stuff up.
596 *
597 * Returns the JNI version on success, -1 on failure.
598 */
JNI_OnLoad(JavaVM * vm,void * reserved)599 jint JNI_OnLoad(JavaVM* vm, void* reserved)
600 {
601 JNIEnv* env = NULL;
602 jint result = -1;
603
604 if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
605 LOGE("ERROR: GetEnv failed\n");
606 goto bail;
607 }
608 assert(env != NULL);
609
610 // LOGI("In WbxmlParser JNI_OnLoad\n");
611
612 if (!registerNatives(env)) {
613 LOGE("ERROR: WbxmlParser native registration failed\n");
614 goto bail;
615 }
616
617 /* success -- return valid version number */
618 result = JNI_VERSION_1_4;
619
620 bail:
621 return result;
622 }
623
624