| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2015, the Dartino project authors. Please see the AUTHORS file | |
| 2 // for details. All rights reserved. Use of this source code is governed by a | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 | |
| 5 library old_servicec.plugins.java; | |
| 6 | |
| 7 import 'dart:core' hide Type; | |
| 8 import 'dart:io'; | |
| 9 | |
| 10 import 'package:path/path.dart' show | |
| 11 basenameWithoutExtension, | |
| 12 join, | |
| 13 relative; | |
| 14 import 'package:servicec/util.dart' as strings; | |
| 15 | |
| 16 import 'shared.dart'; | |
| 17 import '../emitter.dart'; | |
| 18 import '../struct_layout.dart'; | |
| 19 import '../primitives.dart' as primitives; | |
| 20 | |
| 21 import 'cc.dart' show CcVisitor; | |
| 22 | |
| 23 const HEADER = """ | |
| 24 // Copyright (c) 2015, the Dartino project authors. Please see the AUTHORS file | |
| 25 // for details. All rights reserved. Use of this source code is governed by a | |
| 26 // BSD-style license that can be found in the LICENSE.md file. | |
| 27 | |
| 28 // Generated file. Do not edit. | |
| 29 """; | |
| 30 | |
| 31 const HEADER_MK = """ | |
| 32 # Copyright (c) 2015, the Dartino project authors. Please see the AUTHORS file | |
| 33 # for details. All rights reserved. Use of this source code is governed by a | |
| 34 # BSD-style license that can be found in the LICENSE.md file. | |
| 35 | |
| 36 # Generated file. Do not edit. | |
| 37 """; | |
| 38 | |
| 39 const READER_HEADER = """ | |
| 40 package dartino; | |
| 41 """; | |
| 42 | |
| 43 const DARTINO_API_JAVA = """ | |
| 44 package dartino; | |
| 45 | |
| 46 public class DartinoApi { | |
| 47 public static abstract class PrintInterceptor { | |
| 48 public abstract void Out(String message); | |
| 49 public abstract void Error(String message); | |
| 50 private long nativePtr = 0; | |
| 51 } | |
| 52 public static native void Setup(); | |
| 53 public static native void TearDown(); | |
| 54 public static native void RunSnapshot(byte[] snapshot); | |
| 55 public static native void WaitForDebuggerConnection(int port); | |
| 56 public static native void AddDefaultSharedLibrary(String library); | |
| 57 public static native void RegisterPrintInterceptor(PrintInterceptor intercepto
r); | |
| 58 public static native void UnregisterPrintInterceptor(PrintInterceptor intercep
tor); | |
| 59 } | |
| 60 """; | |
| 61 | |
| 62 const DARTINO_SERVICE_API_JAVA = """ | |
| 63 package dartino; | |
| 64 | |
| 65 public class DartinoServiceApi { | |
| 66 public static native void Setup(); | |
| 67 public static native void TearDown(); | |
| 68 } | |
| 69 """; | |
| 70 | |
| 71 const DARTINO_API_JAVA_IMPL = """ | |
| 72 #include <jni.h> | |
| 73 #include <stdlib.h> | |
| 74 | |
| 75 #include "dartino_api.h" | |
| 76 | |
| 77 #ifdef ANDROID | |
| 78 typedef JNIEnv* AttachEnvType; | |
| 79 #else | |
| 80 typedef void* AttachEnvType; | |
| 81 #endif | |
| 82 | |
| 83 static JNIEnv* AttachCurrentThreadAndGetEnv(JavaVM* vm) { | |
| 84 AttachEnvType result = NULL; | |
| 85 if (vm->AttachCurrentThread(&result, NULL) != JNI_OK) { | |
| 86 // TODO(zerny): Nicer error recovery? | |
| 87 exit(1); | |
| 88 } | |
| 89 return reinterpret_cast<JNIEnv*>(result); | |
| 90 } | |
| 91 | |
| 92 class JNIPrintInterceptorInfo { | |
| 93 public: | |
| 94 JNIPrintInterceptorInfo(JavaVM* vm, jobject obj) | |
| 95 : vm(vm), obj(obj), interceptor(NULL) {} | |
| 96 JavaVM* vm; | |
| 97 jobject obj; | |
| 98 DartinoPrintInterceptor interceptor; | |
| 99 }; | |
| 100 | |
| 101 static void JNIPrintInterceptorFunction( | |
| 102 const char* message, int out, void* raw) { | |
| 103 JNIPrintInterceptorInfo* info = | |
| 104 reinterpret_cast<JNIPrintInterceptorInfo*>(raw); | |
| 105 JNIEnv* env = AttachCurrentThreadAndGetEnv(info->vm); | |
| 106 jobject obj = info->obj; | |
| 107 jclass clazz = env->GetObjectClass(obj); | |
| 108 jmethodID method = NULL; | |
| 109 const char* methodName = (out == 2) ? "Out" : (out == 3) ? "Error" : NULL; | |
| 110 const char* methodSig = "(Ljava/lang/String;)V"; | |
| 111 if (methodName == NULL) exit(1); | |
| 112 method = env->GetMethodID(clazz, methodName, methodSig); | |
| 113 jstring argument = env->NewStringUTF(message); | |
| 114 env->CallVoidMethod(obj, method, argument); | |
| 115 info->vm->DetachCurrentThread(); | |
| 116 } | |
| 117 | |
| 118 static void RegisterPrintInterceptor(JNIEnv* env, jobject obj) { | |
| 119 // TODO(zerny): Associate the Java object and native object in a map. | |
| 120 jclass clazz = env->GetObjectClass(obj); | |
| 121 jfieldID nativePtr = env->GetFieldID(clazz, "nativePtr", "J"); | |
| 122 jlong nativePtrValue = env->GetLongField(obj, nativePtr); | |
| 123 if (nativePtrValue != 0) return; | |
| 124 obj = env->NewGlobalRef(obj); | |
| 125 JavaVM* vm = NULL; | |
| 126 env->GetJavaVM(&vm); | |
| 127 JNIPrintInterceptorInfo* info = new JNIPrintInterceptorInfo(vm, obj); | |
| 128 info->interceptor = DartinoRegisterPrintInterceptor( | |
| 129 JNIPrintInterceptorFunction, reinterpret_cast<void*>(info)); | |
| 130 env->SetLongField(obj, nativePtr, reinterpret_cast<jlong>(info)); | |
| 131 } | |
| 132 | |
| 133 static void UnregisterPrintInterceptor(JNIEnv* env, jobject obj) { | |
| 134 // TODO(zerny): Retrieve the native object from the Java object via a map. | |
| 135 jclass clazz = env->GetObjectClass(obj); | |
| 136 jfieldID nativePtr = env->GetFieldID(clazz, "nativePtr", "J"); | |
| 137 jlong nativePtrValue = env->GetLongField(obj, nativePtr); | |
| 138 if (nativePtrValue == 0) return; | |
| 139 env->SetLongField(obj, nativePtr, 0); | |
| 140 JNIPrintInterceptorInfo* info = | |
| 141 reinterpret_cast<JNIPrintInterceptorInfo*>(nativePtrValue); | |
| 142 DartinoUnregisterPrintInterceptor(info->interceptor); | |
| 143 env->DeleteGlobalRef(info->obj); | |
| 144 delete info; | |
| 145 } | |
| 146 | |
| 147 #ifdef __cplusplus | |
| 148 extern "C" { | |
| 149 #endif | |
| 150 | |
| 151 JNIEXPORT void JNICALL Java_dartino_DartinoApi_Setup(JNIEnv*, jclass) { | |
| 152 DartinoSetup(); | |
| 153 } | |
| 154 | |
| 155 JNIEXPORT void JNICALL Java_dartino_DartinoApi_TearDown(JNIEnv*, jclass) { | |
| 156 DartinoTearDown(); | |
| 157 } | |
| 158 | |
| 159 JNIEXPORT void JNICALL Java_dartino_DartinoApi_RunSnapshot(JNIEnv* env, | |
| 160 jclass, | |
| 161 jbyteArray snapshot) { | |
| 162 int len = env->GetArrayLength(snapshot); | |
| 163 unsigned char* copy = new unsigned char[len]; | |
| 164 env->GetByteArrayRegion(snapshot, 0, len, reinterpret_cast<jbyte*>(copy)); | |
| 165 DartinoProgram program = DartinoLoadSnapshot(copy, len); | |
| 166 delete[] copy; | |
| 167 DartinoRunMain(program, 0, NULL); | |
| 168 DartinoDeleteProgram(program); | |
| 169 } | |
| 170 | |
| 171 JNIEXPORT void JNICALL Java_dartino_DartinoApi_AddDefaultSharedLibrary( | |
| 172 JNIEnv* env, jclass, jstring str) { | |
| 173 const char* library = env->GetStringUTFChars(str, 0); | |
| 174 DartinoAddDefaultSharedLibrary(library); | |
| 175 env->ReleaseStringUTFChars(str, library); | |
| 176 } | |
| 177 | |
| 178 JNIEXPORT void JNICALL Java_dartino_DartinoApi_RegisterPrintInterceptor( | |
| 179 JNIEnv* env, jclass, jobject obj) { | |
| 180 RegisterPrintInterceptor(env, obj); | |
| 181 } | |
| 182 | |
| 183 JNIEXPORT void JNICALL Java_dartino_DartinoApi_UnregisterPrintInterceptor( | |
| 184 JNIEnv* env, jclass, jobject obj) { | |
| 185 UnregisterPrintInterceptor(env, obj); | |
| 186 } | |
| 187 | |
| 188 #ifdef __cplusplus | |
| 189 } | |
| 190 #endif | |
| 191 """; | |
| 192 | |
| 193 const DARTINO_SERVICE_API_JAVA_IMPL = """ | |
| 194 #include <jni.h> | |
| 195 | |
| 196 #include "service_api.h" | |
| 197 | |
| 198 #ifdef __cplusplus | |
| 199 extern "C" { | |
| 200 #endif | |
| 201 | |
| 202 JNIEXPORT void JNICALL Java_dartino_DartinoServiceApi_Setup(JNIEnv*, jclass) { | |
| 203 ServiceApiSetup(); | |
| 204 } | |
| 205 | |
| 206 JNIEXPORT void JNICALL Java_dartino_DartinoServiceApi_TearDown(JNIEnv*, jclass)
{ | |
| 207 ServiceApiTearDown(); | |
| 208 } | |
| 209 | |
| 210 #ifdef __cplusplus | |
| 211 } | |
| 212 #endif | |
| 213 """; | |
| 214 | |
| 215 String JNI_UTILS = """ | |
| 216 static JNIEnv* AttachCurrentThreadAndGetEnv(JavaVM* vm) { | |
| 217 AttachEnvType result = NULL; | |
| 218 if (vm->AttachCurrentThread(&result, NULL) != JNI_OK) { | |
| 219 // TODO(ager): Nicer error recovery? | |
| 220 exit(1); | |
| 221 } | |
| 222 return reinterpret_cast<JNIEnv*>(result); | |
| 223 } | |
| 224 | |
| 225 static void DetachCurrentThread(JavaVM* vm) { | |
| 226 if (vm->DetachCurrentThread() != JNI_OK) { | |
| 227 // TODO(ager): Nicer error recovery? | |
| 228 exit(1); | |
| 229 } | |
| 230 } | |
| 231 | |
| 232 static jobject CreateByteArray(JNIEnv* env, char* memory, int size) { | |
| 233 jbyteArray result = env->NewByteArray(size); | |
| 234 jbyte* contents = reinterpret_cast<jbyte*>(memory); | |
| 235 env->SetByteArrayRegion(result, 0, size, contents); | |
| 236 free(memory); | |
| 237 return result; | |
| 238 } | |
| 239 | |
| 240 static jobject CreateByteArrayArray(JNIEnv* env, char* memory, int size) { | |
| 241 jobjectArray array = env->NewObjectArray(size, env->FindClass("[B"), NULL); | |
| 242 for (int i = 0; i < size; i++) { | |
| 243 int64_t address = *reinterpret_cast<int64_t*>(memory + 8 + (i * 16)); | |
| 244 int size = *reinterpret_cast<int*>(memory + 16 + (i * 16)); | |
| 245 char* contents = reinterpret_cast<char*>(address); | |
| 246 env->SetObjectArrayElement(array, i, CreateByteArray(env, contents, size)); | |
| 247 } | |
| 248 free(memory); | |
| 249 return array; | |
| 250 } | |
| 251 | |
| 252 static jobject GetRootSegment(JNIEnv* env, char* memory) { | |
| 253 int32_t segments = *reinterpret_cast<int32_t*>(memory); | |
| 254 if (segments == 0) { | |
| 255 int32_t size = *reinterpret_cast<int32_t*>(memory + 4); | |
| 256 return CreateByteArray(env, memory, size); | |
| 257 } | |
| 258 return CreateByteArrayArray(env, memory, segments); | |
| 259 } | |
| 260 | |
| 261 class CallbackInfo { | |
| 262 public: | |
| 263 CallbackInfo(jobject jcallback, JavaVM* jvm) | |
| 264 : callback(jcallback), vm(jvm) { } | |
| 265 jobject callback; | |
| 266 JavaVM* vm; | |
| 267 }; | |
| 268 | |
| 269 static char* ExtractByteArrayData(JNIEnv* env, | |
| 270 jbyteArray segment, | |
| 271 jint segment_length) { | |
| 272 jbyte* data = env->GetByteArrayElements(segment, NULL); | |
| 273 char* segment_copy = reinterpret_cast<char*>(malloc(segment_length)); | |
| 274 memcpy(segment_copy, data, segment_length); | |
| 275 env->ReleaseByteArrayElements(segment, data, JNI_ABORT); | |
| 276 return segment_copy; | |
| 277 } | |
| 278 | |
| 279 static int ComputeMessage(JNIEnv* env, | |
| 280 jobject builder, | |
| 281 jobject callback, | |
| 282 JavaVM* vm, | |
| 283 char** buffer) { | |
| 284 CallbackInfo* info = NULL; | |
| 285 if (callback != NULL) { | |
| 286 info = new CallbackInfo(callback, vm); | |
| 287 } | |
| 288 | |
| 289 jclass clazz = env->GetObjectClass(builder); | |
| 290 jmethodID method_id = env->GetMethodID(clazz, "getSegments", "()[Ljava/lang/Ob
ject;"); | |
| 291 jobjectArray array = (jobjectArray)env->CallObjectMethod(builder, method_id); | |
| 292 jobjectArray segments = (jobjectArray)env->GetObjectArrayElement(array, 0); | |
| 293 jintArray sizes_array = (jintArray)env->GetObjectArrayElement(array, 1); | |
| 294 int* sizes = env->GetIntArrayElements(sizes_array, NULL); | |
| 295 int number_of_segments = env->GetArrayLength(segments); | |
| 296 | |
| 297 if (number_of_segments > 1) { | |
| 298 int size = $REQUEST_HEADER_SIZE + 8 + (number_of_segments * 16); | |
| 299 *buffer = reinterpret_cast<char*>(malloc(size)); | |
| 300 int offset = $REQUEST_HEADER_SIZE + 8; | |
| 301 for (int i = 0; i < number_of_segments; i++) { | |
| 302 jbyteArray segment = (jbyteArray)env->GetObjectArrayElement(segments, i); | |
| 303 jint segment_length = sizes[i]; | |
| 304 char* segment_copy = ExtractByteArrayData(env, segment, segment_length); | |
| 305 *reinterpret_cast<void**>(*buffer + offset) = segment_copy; | |
| 306 *reinterpret_cast<int*>(*buffer + offset + 8) = segment_length; | |
| 307 offset += 16; | |
| 308 } | |
| 309 | |
| 310 env->ReleaseIntArrayElements(sizes_array, sizes, JNI_ABORT); | |
| 311 // Mark the request as being segmented. | |
| 312 *${pointerToArgument('*buffer', -8, 'int64_t')} = number_of_segments; | |
| 313 // Set the callback information. | |
| 314 *${pointerToArgument('*buffer', -16, 'CallbackInfo*')} = info; | |
| 315 return size; | |
| 316 } | |
| 317 | |
| 318 jbyteArray segment = (jbyteArray)env->GetObjectArrayElement(segments, 0); | |
| 319 jint segment_length = sizes[0]; | |
| 320 *buffer = ExtractByteArrayData(env, segment, segment_length); | |
| 321 env->ReleaseIntArrayElements(sizes_array, sizes, JNI_ABORT); | |
| 322 // Mark the request as being non-segmented. | |
| 323 *${pointerToArgument('*buffer', -8, 'int64_t')} = 0; | |
| 324 // Set the callback information. | |
| 325 *${pointerToArgument('*buffer', -16, 'CallbackInfo*')} = info; | |
| 326 return segment_length; | |
| 327 } | |
| 328 | |
| 329 static void DeleteMessage(char* message) { | |
| 330 int32_t segments = *${pointerToArgument('message', -8, 'int32_t')}; | |
| 331 for (int i = 0; i < segments; i++) { | |
| 332 int64_t address = *reinterpret_cast<int64_t*>(message + ${REQUEST_HEADER_SIZ
E + 8} + (i * 16)); | |
| 333 char* memory = reinterpret_cast<char*>(address); | |
| 334 free(memory); | |
| 335 } | |
| 336 free(message); | |
| 337 }"""; | |
| 338 | |
| 339 const int REQUEST_HEADER_SIZE = 56; | |
| 340 | |
| 341 String pointerToArgument(String buffer, int offset, String type) { | |
| 342 offset += REQUEST_HEADER_SIZE; | |
| 343 return 'reinterpret_cast<$type*>($buffer + $offset)'; | |
| 344 } | |
| 345 | |
| 346 const List<String> JAVA_RESOURCES = const [ | |
| 347 "Builder.java", | |
| 348 "BuilderSegment.java", | |
| 349 "ListBuilder.java", | |
| 350 "ListReader.java", | |
| 351 "MessageBuilder.java", | |
| 352 "MessageReader.java", | |
| 353 "Reader.java", | |
| 354 "Segment.java" | |
| 355 ]; | |
| 356 | |
| 357 void generate(String path, | |
| 358 Unit unit, | |
| 359 String resourcesDirectory, | |
| 360 String outputDirectory) { | |
| 361 _generateDartinoApis(outputDirectory); | |
| 362 _generateServiceJava(path, unit, outputDirectory); | |
| 363 _generateServiceJni(path, unit, outputDirectory); | |
| 364 _generateServiceJniMakeFiles(path, unit, resourcesDirectory, outputDirectory); | |
| 365 | |
| 366 resourcesDirectory = join(resourcesDirectory, 'java', 'dartino'); | |
| 367 String dartinoDirectory = join(outputDirectory, 'java', 'dartino'); | |
| 368 for (String resource in JAVA_RESOURCES) { | |
| 369 String resourcePath = join(resourcesDirectory, resource); | |
| 370 File file = new File(resourcePath); | |
| 371 String contents = file.readAsStringSync(); | |
| 372 writeToFile(dartinoDirectory, resource, contents); | |
| 373 } | |
| 374 } | |
| 375 | |
| 376 void _generateDartinoApis(String outputDirectory) { | |
| 377 String dartinoDirectory = join(outputDirectory, 'java', 'dartino'); | |
| 378 String jniDirectory = join(outputDirectory, 'java', 'jni'); | |
| 379 | |
| 380 StringBuffer buffer = new StringBuffer(HEADER); | |
| 381 buffer.writeln(); | |
| 382 buffer.write(DARTINO_API_JAVA); | |
| 383 writeToFile(dartinoDirectory, 'DartinoApi', buffer.toString(), | |
| 384 extension: 'java'); | |
| 385 | |
| 386 buffer = new StringBuffer(HEADER); | |
| 387 buffer.writeln(); | |
| 388 buffer.write(DARTINO_SERVICE_API_JAVA); | |
| 389 writeToFile(dartinoDirectory, 'DartinoServiceApi', buffer.toString(), | |
| 390 extension: 'java'); | |
| 391 | |
| 392 buffer = new StringBuffer(HEADER); | |
| 393 buffer.writeln(); | |
| 394 buffer.write(DARTINO_API_JAVA_IMPL); | |
| 395 writeToFile(jniDirectory, 'dartino_api_wrapper', buffer.toString(), | |
| 396 extension: 'cc'); | |
| 397 | |
| 398 buffer = new StringBuffer(HEADER); | |
| 399 buffer.writeln(); | |
| 400 buffer.write(DARTINO_SERVICE_API_JAVA_IMPL); | |
| 401 writeToFile(jniDirectory, 'dartino_service_api_wrapper', | |
| 402 buffer.toString(), extension: 'cc'); | |
| 403 } | |
| 404 | |
| 405 void _generateServiceJava(String path, Unit unit, String outputDirectory) { | |
| 406 _JavaVisitor visitor = new _JavaVisitor(path, outputDirectory); | |
| 407 visitor.visit(unit); | |
| 408 String contents = visitor.buffer.toString(); | |
| 409 String directory = join(outputDirectory, 'java', 'dartino'); | |
| 410 // TODO(ager): We should generate a file per service here. | |
| 411 if (unit.services.length > 1) { | |
| 412 print('Java plugin: multiple services in one file is not supported.'); | |
| 413 } | |
| 414 String serviceName = unit.services.first.name; | |
| 415 writeToFile(directory, serviceName, contents, extension: 'java'); | |
| 416 } | |
| 417 | |
| 418 void _generateServiceJni(String path, Unit unit, String outputDirectory) { | |
| 419 _JniVisitor visitor = new _JniVisitor(path); | |
| 420 visitor.visit(unit); | |
| 421 String contents = visitor.buffer.toString(); | |
| 422 String directory = join(outputDirectory, 'java', 'jni'); | |
| 423 // TODO(ager): We should generate a file per service here. | |
| 424 if (unit.services.length > 1) { | |
| 425 print('Java plugin: multiple services in one file is not supported.'); | |
| 426 } | |
| 427 String projectName = basenameWithoutExtension(path); | |
| 428 String file = '${projectName}_wrapper'; | |
| 429 writeToFile(directory, file, contents, extension: 'cc'); | |
| 430 } | |
| 431 | |
| 432 class _JavaVisitor extends CodeGenerationVisitor { | |
| 433 final Set<Type> neededListTypes; | |
| 434 final String outputDirectory; | |
| 435 | |
| 436 static Map<String, String> _GETTERS = const { | |
| 437 'bool' : 'segment.getBoolean', | |
| 438 | |
| 439 'uint8' : 'segment.getUnsigned', | |
| 440 'uint16' : 'segment.getUnsignedChar', | |
| 441 | |
| 442 'int8' : 'segment.buffer().get', | |
| 443 'int16' : 'segment.buffer().getShort', | |
| 444 'int32' : 'segment.buffer().getInt', | |
| 445 'int64' : 'segment.buffer().getLong', | |
| 446 | |
| 447 'float32' : 'segment.buffer().getFloat', | |
| 448 'float64' : 'segment.buffer().getDouble', | |
| 449 }; | |
| 450 | |
| 451 static Map<String, String> _SETTERS = const { | |
| 452 'bool' : 'segment.buffer().put', | |
| 453 | |
| 454 'uint8' : 'segment.buffer().put', | |
| 455 'uint16' : 'segment.buffer().putChar', | |
| 456 | |
| 457 'int8' : 'segment.buffer().put', | |
| 458 'int16' : 'segment.buffer().putShort', | |
| 459 'int32' : 'segment.buffer().putInt', | |
| 460 'int64' : 'segment.buffer().putLong', | |
| 461 | |
| 462 'float32' : 'segment.buffer().putFloat', | |
| 463 'float64' : 'segment.buffer().pubDouble', | |
| 464 }; | |
| 465 | |
| 466 static Map<String, String> _SETTER_TYPES = const { | |
| 467 'bool' : 'byte', | |
| 468 | |
| 469 'uint8' : 'byte', | |
| 470 'uint16' : 'char', | |
| 471 | |
| 472 'int8' : 'byte', | |
| 473 'int16' : 'char', | |
| 474 'int32' : 'int', | |
| 475 'int64' : 'long', | |
| 476 | |
| 477 'float32' : 'float', | |
| 478 'float64' : 'double', | |
| 479 }; | |
| 480 | |
| 481 _JavaVisitor(String path, String this.outputDirectory) | |
| 482 : neededListTypes = new Set<Type>(), | |
| 483 super(path); | |
| 484 | |
| 485 static const PRIMITIVE_TYPES = const <String, String> { | |
| 486 'void' : 'void', | |
| 487 'bool' : 'boolean', | |
| 488 | |
| 489 'uint8' : 'int', | |
| 490 'uint16' : 'int', | |
| 491 | |
| 492 'int8' : 'int', | |
| 493 'int16' : 'int', | |
| 494 'int32' : 'int', | |
| 495 'int64' : 'long', | |
| 496 | |
| 497 'float32' : 'float', | |
| 498 'float64' : 'double', | |
| 499 }; | |
| 500 | |
| 501 static const PRIMITIVE_LIST_TYPES = const <String, String> { | |
| 502 'bool' : 'boolean', | |
| 503 | |
| 504 'uint8' : 'int', | |
| 505 'uint16' : 'int', | |
| 506 | |
| 507 'int8' : 'int', | |
| 508 'int16' : 'int', | |
| 509 'int32' : 'int', | |
| 510 'int64' : 'long', | |
| 511 | |
| 512 'float32' : 'float', | |
| 513 'float64' : 'double', | |
| 514 }; | |
| 515 | |
| 516 String getType(Type node) { | |
| 517 Node resolved = node.resolved; | |
| 518 if (resolved != null) { | |
| 519 return '${node.identifier}Builder'; | |
| 520 } else { | |
| 521 String type = PRIMITIVE_TYPES[node.identifier]; | |
| 522 return type; | |
| 523 } | |
| 524 } | |
| 525 | |
| 526 String getReturnType(Type node) { | |
| 527 Node resolved = node.resolved; | |
| 528 if (resolved != null) { | |
| 529 return '${node.identifier}'; | |
| 530 } else { | |
| 531 String type = PRIMITIVE_TYPES[node.identifier]; | |
| 532 return type; | |
| 533 } | |
| 534 } | |
| 535 | |
| 536 String getListType(Type node) { | |
| 537 Node resolved = node.resolved; | |
| 538 if (resolved != null) { | |
| 539 return '${node.identifier}'; | |
| 540 } else { | |
| 541 String type = PRIMITIVE_LIST_TYPES[node.identifier]; | |
| 542 return type; | |
| 543 } | |
| 544 } | |
| 545 | |
| 546 void writeType(Type node) => write(getType(node)); | |
| 547 void writeTypeToBuffer(Type node, StringBuffer buffer) { | |
| 548 buffer.write(getType(node)); | |
| 549 } | |
| 550 | |
| 551 void writeReturnType(Type node) => write(getReturnType(node)); | |
| 552 void writeReturnTypeToBuffer(Type node, StringBuffer buffer) { | |
| 553 buffer.write(getReturnType(node)); | |
| 554 } | |
| 555 | |
| 556 void writeListTypeToBuffer(Type node, StringBuffer buffer) { | |
| 557 buffer.write(getListType(node)); | |
| 558 } | |
| 559 | |
| 560 visitUnit(Unit node) { | |
| 561 writeln(HEADER); | |
| 562 writeln('package dartino;'); | |
| 563 writeln(); | |
| 564 node.structs.forEach(visit); | |
| 565 node.services.forEach(visit); | |
| 566 neededListTypes.forEach(writeListReaderImplementation); | |
| 567 neededListTypes.forEach(writeListBuilderImplementation); | |
| 568 } | |
| 569 | |
| 570 visitService(Service node) { | |
| 571 writeln('public class ${node.name} {'); | |
| 572 writeln(' public static native void Setup();'); | |
| 573 writeln(' public static native void TearDown();'); | |
| 574 node.methods.forEach(visit); | |
| 575 writeln('}'); | |
| 576 } | |
| 577 | |
| 578 visitMethod(Method node) { | |
| 579 String name = node.name; | |
| 580 String camelName = name.substring(0, 1).toUpperCase() + name.substring(1); | |
| 581 writeln(); | |
| 582 writeln(' public static abstract class ${camelName}Callback {'); | |
| 583 if (!node.returnType.isVoid && !node.returnType.isPrimitive) { | |
| 584 write(' public final java.lang.Class returnType = '); | |
| 585 writeReturnType(node.returnType); | |
| 586 writeln('.class;'); | |
| 587 } | |
| 588 write(' public abstract void handle('); | |
| 589 if (!node.returnType.isVoid) { | |
| 590 writeReturnType(node.returnType); | |
| 591 write(' result'); | |
| 592 } | |
| 593 writeln(');'); | |
| 594 writeln(' }'); | |
| 595 | |
| 596 writeln(); | |
| 597 write(' public static native '); | |
| 598 writeReturnType(node.returnType); | |
| 599 write(' $name('); | |
| 600 visitArguments(node.arguments); | |
| 601 writeln(');'); | |
| 602 write(' public static native void ${name}Async('); | |
| 603 visitArguments(node.arguments); | |
| 604 if (node.arguments.isNotEmpty) write(', '); | |
| 605 writeln('${camelName}Callback callback);'); | |
| 606 } | |
| 607 | |
| 608 visitArguments(List<Formal> formals) { | |
| 609 visitNodes(formals, (first) => first ? '' : ', '); | |
| 610 } | |
| 611 | |
| 612 visitFormal(Formal node) { | |
| 613 writeType(node.type); | |
| 614 write(' ${node.name}'); | |
| 615 } | |
| 616 | |
| 617 visitStruct(Struct node) { | |
| 618 writeReader(node); | |
| 619 writeBuilder(node); | |
| 620 } | |
| 621 | |
| 622 void writeReader(Struct node) { | |
| 623 String dartinoDirectory = join(outputDirectory, 'java', 'dartino'); | |
| 624 String name = node.name; | |
| 625 StructLayout layout = node.layout; | |
| 626 | |
| 627 StringBuffer buffer = new StringBuffer(HEADER); | |
| 628 buffer.writeln(); | |
| 629 buffer.writeln(READER_HEADER); | |
| 630 | |
| 631 buffer.writeln('import java.util.List;'); | |
| 632 buffer.writeln(); | |
| 633 | |
| 634 buffer.writeln('public class $name extends Reader {'); | |
| 635 buffer.writeln(' public $name() { }'); | |
| 636 buffer.writeln(); | |
| 637 buffer.writeln(' public $name(byte[] memory, int offset) {'); | |
| 638 buffer.writeln(' super(memory, offset);'); | |
| 639 buffer.writeln(' }'); | |
| 640 buffer.writeln(); | |
| 641 buffer.writeln(' public $name(Segment segment, int offset) {'); | |
| 642 buffer.writeln(' super(segment, offset);'); | |
| 643 buffer.writeln(' }'); | |
| 644 buffer.writeln(); | |
| 645 buffer.writeln(' public $name(byte[][] segments, int offset) {'); | |
| 646 buffer.writeln(' super(segments, offset);'); | |
| 647 buffer.writeln(' }'); | |
| 648 buffer.writeln(); | |
| 649 buffer.writeln(' public static $name create(Object rawData) {'); | |
| 650 buffer.writeln(' if (rawData instanceof byte[]) {'); | |
| 651 buffer.writeln(' return new $name((byte[])rawData, 8);'); | |
| 652 buffer.writeln(' }'); | |
| 653 buffer.writeln(' return new $name((byte[][])rawData, 8);'); | |
| 654 buffer.writeln(' }'); | |
| 655 | |
| 656 for (StructSlot slot in layout.slots) { | |
| 657 buffer.writeln(); | |
| 658 Type slotType = slot.slot.type; | |
| 659 String camel = camelize(slot.slot.name); | |
| 660 | |
| 661 if (slot.isUnionSlot) { | |
| 662 String tagName = camelize(slot.union.tag.name); | |
| 663 int tag = slot.unionTag; | |
| 664 buffer.writeln( | |
| 665 ' public boolean is$camel() { return $tag == get$tagName(); }'); | |
| 666 } | |
| 667 | |
| 668 if (slotType.isList) { | |
| 669 neededListTypes.add(slotType); | |
| 670 String list = '${camelize(slotType.identifier)}List'; | |
| 671 buffer.writeln(' public $list get$camel() {'); | |
| 672 buffer.writeln(' ListReader reader = new ListReader();'); | |
| 673 buffer.writeln(' readList(reader, ${slot.offset});'); | |
| 674 buffer.writeln(' return new $list(reader);'); | |
| 675 buffer.writeln(' }'); | |
| 676 } else if (slotType.isVoid) { | |
| 677 // No getters for void slots. | |
| 678 } else if (slotType.isString) { | |
| 679 buffer.write(' public String get$camel() { '); | |
| 680 buffer.writeln('return readString(${slot.offset}); }'); | |
| 681 // TODO(ager): This is nasty. Maybe inject this type earler in the | |
| 682 // pipeline? | |
| 683 Type uint16ListType = new ListType(new SimpleType("uint16", false)); | |
| 684 uint16ListType.primitiveType = primitives.lookup("uint16"); | |
| 685 neededListTypes.add(uint16ListType); | |
| 686 buffer.writeln(' public Uint16List get${camel}Data() {'); | |
| 687 buffer.writeln(' ListReader reader = new ListReader();'); | |
| 688 buffer.writeln(' readList(reader, ${slot.offset});'); | |
| 689 buffer.writeln(' return new Uint16List(reader);'); | |
| 690 buffer.writeln(' }'); | |
| 691 } else if (slotType.isPrimitive) { | |
| 692 // TODO(ager): Dealing with unsigned numbers in Java is annoying. | |
| 693 if (camel == 'Tag') { | |
| 694 String getter = 'getUnsigned'; | |
| 695 String offset = 'base + ${slot.offset}'; | |
| 696 buffer.writeln(' public int getTag() {'); | |
| 697 buffer.writeln(' short shortTag = segment.$getter($offset);'); | |
| 698 buffer.writeln(' int tag = (int)shortTag;'); | |
| 699 buffer.writeln(' return tag < 0 ? -tag : tag;'); | |
| 700 buffer.writeln(' }'); | |
| 701 } else { | |
| 702 String getter = _GETTERS[slotType.identifier]; | |
| 703 String offset = "base + ${slot.offset}"; | |
| 704 buffer.write(' public ${getType(slotType)} get$camel() { '); | |
| 705 buffer.writeln('return $getter($offset); }'); | |
| 706 } | |
| 707 } else { | |
| 708 String returnType = getReturnType(slotType); | |
| 709 buffer.write(' public $returnType get$camel() {'); | |
| 710 if (!slotType.isPointer) { | |
| 711 String offset = 'base + ${slot.offset}'; | |
| 712 buffer.writeln(' return new $returnType(segment, $offset); }'); | |
| 713 } else { | |
| 714 buffer.writeln(); | |
| 715 int offset = slot.offset; | |
| 716 buffer.writeln(' $returnType reader = new $returnType();'); | |
| 717 buffer.writeln(' return ($returnType)readStruct(reader, $offset);')
; | |
| 718 buffer.writeln(' }'); | |
| 719 } | |
| 720 } | |
| 721 } | |
| 722 | |
| 723 buffer.writeln('}'); | |
| 724 | |
| 725 writeToFile(dartinoDirectory, '$name', buffer.toString(), | |
| 726 extension: 'java'); | |
| 727 } | |
| 728 | |
| 729 void writeBuilder(Struct node) { | |
| 730 String dartinoDirectory = join(outputDirectory, 'java', 'dartino'); | |
| 731 String name = '${node.name}Builder'; | |
| 732 StructLayout layout = node.layout; | |
| 733 | |
| 734 StringBuffer buffer = new StringBuffer(HEADER); | |
| 735 buffer.writeln(); | |
| 736 buffer.writeln(READER_HEADER); | |
| 737 | |
| 738 buffer.write('import java.util.List;'); | |
| 739 buffer.writeln(); | |
| 740 buffer.writeln('public class $name extends Builder {'); | |
| 741 buffer.writeln(' public static int kSize = ${layout.size};'); | |
| 742 buffer.writeln(' public $name(BuilderSegment segment, int offset) {'); | |
| 743 buffer.writeln(' super(segment, offset);'); | |
| 744 buffer.writeln(' }'); | |
| 745 buffer.writeln(); | |
| 746 buffer.writeln(' public $name() {'); | |
| 747 buffer.writeln(' super();'); | |
| 748 buffer.writeln(' }'); | |
| 749 | |
| 750 for (StructSlot slot in layout.slots) { | |
| 751 buffer.writeln(); | |
| 752 String slotName = slot.slot.name; | |
| 753 String camel = camelize(slotName); | |
| 754 Type slotType = slot.slot.type; | |
| 755 | |
| 756 String updateTag = ''; | |
| 757 if (slot.isUnionSlot) { | |
| 758 String tagName = camelize(slot.union.tag.name); | |
| 759 int tag = slot.unionTag; | |
| 760 updateTag = ' set$tagName($tag);\n'; | |
| 761 } | |
| 762 | |
| 763 if (slotType.isList) { | |
| 764 String listBuilder = ''; | |
| 765 if (slotType.isPrimitive) { | |
| 766 listBuilder = '${camelize(slotType.identifier)}ListBuilder'; | |
| 767 } else { | |
| 768 listBuilder = '${getListType(slotType)}ListBuilder'; | |
| 769 } | |
| 770 buffer.writeln(' public $listBuilder init$camel(int length) {'); | |
| 771 buffer.write(updateTag); | |
| 772 int size = 0; | |
| 773 if (slotType.isPrimitive) { | |
| 774 size = primitives.size(slotType.primitiveType); | |
| 775 } else { | |
| 776 Struct element = slotType.resolved; | |
| 777 StructLayout elementLayout = element.layout; | |
| 778 size = elementLayout.size; | |
| 779 } | |
| 780 buffer.writeln(' ListBuilder builder = new ListBuilder();'); | |
| 781 buffer.writeln(' newList(builder, ${slot.offset}, length, $size);'); | |
| 782 buffer.writeln(' return new ${listBuilder}(builder);'); | |
| 783 buffer.writeln(' }'); | |
| 784 } else if (slotType.isVoid) { | |
| 785 assert(slot.isUnionSlot); | |
| 786 String tagName = camelize(slot.union.tag.name); | |
| 787 int tag = slot.unionTag; | |
| 788 buffer.writeln(' public void set$camel() {' | |
| 789 ' set$tagName($tag); }'); | |
| 790 } else if (slotType.isString) { | |
| 791 buffer.writeln(' public void set$camel(String value) {'); | |
| 792 buffer.write(updateTag); | |
| 793 buffer.writeln(' newString(${slot.offset}, value);'); | |
| 794 buffer.writeln(' }'); | |
| 795 buffer.writeln(); | |
| 796 buffer.writeln(' public Uint16ListBuilder init${camel}Data(int length)
{'); | |
| 797 buffer.write(updateTag); | |
| 798 buffer.writeln(' ListBuilder builder = new ListBuilder();'); | |
| 799 buffer.writeln(' newList(builder, ${slot.offset}, length, 2);'); | |
| 800 buffer.writeln(' return new Uint16ListBuilder(builder);'); | |
| 801 buffer.writeln(' }'); | |
| 802 } else if (slotType.isPrimitive) { | |
| 803 String setter = _SETTERS[slotType.identifier]; | |
| 804 String setterType = _SETTER_TYPES[slotType.identifier]; | |
| 805 String offset = 'base + ${slot.offset}'; | |
| 806 buffer.writeln(' public void set$camel(${getType(slotType)} value) {'); | |
| 807 buffer.write(updateTag); | |
| 808 if (slotType.isBool) { | |
| 809 buffer.writeln(' $setter($offset,' | |
| 810 ' (byte)(value ? 1 : 0));'); | |
| 811 } else { | |
| 812 buffer.writeln(' $setter($offset, (${setterType})value);'); | |
| 813 } | |
| 814 buffer.writeln(' }'); | |
| 815 } else { | |
| 816 buffer.writeln(' public ${getType(slotType)} init$camel() {'); | |
| 817 buffer.write(updateTag); | |
| 818 String builderType = getType(slotType); | |
| 819 if (!slotType.isPointer) { | |
| 820 buffer.writeln(' $builderType result = new $builderType();'); | |
| 821 buffer.writeln(' result.segment = segment;'); | |
| 822 buffer.writeln(' result.base = base + ${slot.offset};'); | |
| 823 buffer.writeln(' return result;'); | |
| 824 } else { | |
| 825 Struct element = slotType.resolved; | |
| 826 StructLayout elementLayout = element.layout; | |
| 827 int size = elementLayout.size; | |
| 828 buffer.writeln(' $builderType result = new $builderType();'); | |
| 829 buffer.writeln(' newStruct(result, ${slot.offset}, $size);'); | |
| 830 buffer.writeln(' return result;'); | |
| 831 } | |
| 832 buffer.writeln(' }'); | |
| 833 } | |
| 834 } | |
| 835 | |
| 836 buffer.writeln('}'); | |
| 837 | |
| 838 writeToFile(dartinoDirectory, '$name', buffer.toString(), | |
| 839 extension: 'java'); | |
| 840 } | |
| 841 | |
| 842 void writeListReaderImplementation(Type type) { | |
| 843 String dartinoDirectory = join(outputDirectory, 'java', 'dartino'); | |
| 844 String name = '${camelize(type.identifier)}List'; | |
| 845 String listType = getListType(type); | |
| 846 | |
| 847 StringBuffer buffer = new StringBuffer(HEADER); | |
| 848 buffer.writeln(); | |
| 849 buffer.writeln(READER_HEADER); | |
| 850 | |
| 851 buffer.writeln('public class $name {'); | |
| 852 if (type.isPrimitive) { | |
| 853 int elementSize = primitives.size(type.primitiveType); | |
| 854 String offset = 'reader.base + index * $elementSize'; | |
| 855 | |
| 856 buffer.writeln(' private ListReader reader;'); | |
| 857 buffer.writeln(); | |
| 858 buffer.writeln(' public $name(ListReader reader) {' | |
| 859 ' this.reader = reader; }'); | |
| 860 buffer.writeln(); | |
| 861 buffer.writeln(' public $listType get(int index) {'); | |
| 862 buffer.write(' return '); | |
| 863 buffer.writeln('reader.${_GETTERS[type.identifier]}($offset);'); | |
| 864 buffer.writeln(' }'); | |
| 865 } else { | |
| 866 Struct element = type.resolved; | |
| 867 StructLayout elementLayout = element.layout;; | |
| 868 int elementSize = elementLayout.size; | |
| 869 String returnType = getReturnType(type); | |
| 870 | |
| 871 buffer.writeln(' private ListReader reader;'); | |
| 872 buffer.writeln(); | |
| 873 buffer.writeln(' public $name(ListReader reader) {' | |
| 874 ' this.reader = reader; }'); | |
| 875 buffer.writeln(); | |
| 876 buffer.writeln(' public $returnType get(int index) {'); | |
| 877 buffer.writeln(' $returnType result = new $returnType();'); | |
| 878 buffer.writeln(' reader.readListElement(' | |
| 879 'result, index, $elementSize);'); | |
| 880 buffer.writeln(' return result;'); | |
| 881 buffer.writeln(' }'); | |
| 882 } | |
| 883 | |
| 884 buffer.writeln(); | |
| 885 buffer.writeln(' public int size() { return reader.length; }'); | |
| 886 | |
| 887 buffer.writeln('}'); | |
| 888 | |
| 889 writeToFile(dartinoDirectory, '$name', buffer.toString(), | |
| 890 extension: 'java'); | |
| 891 } | |
| 892 | |
| 893 void writeListBuilderImplementation(Type type) { | |
| 894 String dartinoDirectory = join(outputDirectory, 'java', 'dartino'); | |
| 895 String name = '${camelize(type.identifier)}ListBuilder'; | |
| 896 | |
| 897 StringBuffer buffer = new StringBuffer(HEADER); | |
| 898 buffer.writeln(); | |
| 899 buffer.writeln(READER_HEADER); | |
| 900 | |
| 901 buffer.writeln('public class $name {'); | |
| 902 buffer.writeln(' private ListBuilder builder;'); | |
| 903 buffer.writeln(); | |
| 904 buffer.writeln(' public $name(ListBuilder builder) {' | |
| 905 ' this.builder = builder; }'); | |
| 906 buffer.writeln(); | |
| 907 | |
| 908 if (type.isPrimitive) { | |
| 909 int elementSize = primitives.size(type.primitiveType); | |
| 910 String offset = 'builder.base + index * $elementSize'; | |
| 911 String listType = getListType(type); | |
| 912 String getter = _GETTERS[type.identifier]; | |
| 913 | |
| 914 buffer.writeln(' public $listType get(int index) {'); | |
| 915 buffer.writeln(' return builder.$getter($offset);'); | |
| 916 buffer.writeln(' }'); | |
| 917 | |
| 918 buffer.writeln(); | |
| 919 String setter = _SETTERS[type.identifier]; | |
| 920 String setterType = _SETTER_TYPES[type.identifier]; | |
| 921 buffer.writeln(' public $listType set(int index, $listType value) {'); | |
| 922 buffer.write(' builder.$setter($offset, (${setterType})value);'); | |
| 923 buffer.writeln(' return value;'); | |
| 924 buffer.writeln(' }'); | |
| 925 } else { | |
| 926 Struct element = type.resolved; | |
| 927 StructLayout elementLayout = element.layout;; | |
| 928 int elementSize = elementLayout.size; | |
| 929 String structType = getType(type); | |
| 930 | |
| 931 buffer.writeln(' public $structType get(int index) {'); | |
| 932 buffer.writeln(' $structType result = new $structType();'); | |
| 933 buffer.writeln(' builder.readListElement(' | |
| 934 'result, index, $elementSize);'); | |
| 935 buffer.writeln(' return result;'); | |
| 936 buffer.writeln(' }'); | |
| 937 } | |
| 938 | |
| 939 buffer.writeln(); | |
| 940 buffer.writeln(' public int size() { return builder.length; }'); | |
| 941 | |
| 942 buffer.writeln('}'); | |
| 943 | |
| 944 writeToFile(dartinoDirectory, '$name', buffer.toString(), | |
| 945 extension: 'java'); | |
| 946 } | |
| 947 } | |
| 948 | |
| 949 class _JniVisitor extends CcVisitor { | |
| 950 static const int REQUEST_HEADER_SIZE = 48 + 8; | |
| 951 static const int RESPONSE_HEADER_SIZE = 8; | |
| 952 | |
| 953 int methodId = 1; | |
| 954 String serviceName; | |
| 955 | |
| 956 _JniVisitor(String path) : super(path); | |
| 957 | |
| 958 visitUnit(Unit node) { | |
| 959 writeln(HEADER); | |
| 960 writeln('#include <jni.h>'); | |
| 961 writeln('#include <stdlib.h>'); | |
| 962 writeln('#include <string.h>'); | |
| 963 writeln(); | |
| 964 writeln('#include "service_api.h"'); | |
| 965 node.services.forEach(visit); | |
| 966 } | |
| 967 | |
| 968 visitService(Service node) { | |
| 969 serviceName = node.name; | |
| 970 | |
| 971 writeln(); | |
| 972 | |
| 973 writeln('#ifdef __cplusplus'); | |
| 974 writeln('extern "C" {'); | |
| 975 writeln('#endif'); | |
| 976 | |
| 977 // TODO(ager): Get rid of this if we can. For some reason | |
| 978 // the jni.h header that is used by the NDK differs. | |
| 979 writeln(); | |
| 980 writeln('#ifdef ANDROID'); | |
| 981 writeln(' typedef JNIEnv* AttachEnvType;'); | |
| 982 writeln('#else'); | |
| 983 writeln(' typedef void* AttachEnvType;'); | |
| 984 writeln('#endif'); | |
| 985 | |
| 986 writeln(); | |
| 987 writeln('static ServiceId service_id_ = kNoServiceId;'); | |
| 988 | |
| 989 writeln(); | |
| 990 write('JNIEXPORT void JNICALL Java_dartino_'); | |
| 991 writeln('${serviceName}_Setup(JNIEnv*, jclass) {'); | |
| 992 writeln(' service_id_ = ServiceApiLookup("$serviceName");'); | |
| 993 writeln('}'); | |
| 994 | |
| 995 writeln(); | |
| 996 write('JNIEXPORT void JNICALL Java_dartino_'); | |
| 997 writeln('${serviceName}_TearDown(JNIEnv*, jclass) {'); | |
| 998 writeln(' ServiceApiTerminate(service_id_);'); | |
| 999 writeln('}'); | |
| 1000 | |
| 1001 // TODO(ager): Put this in resources and copy as a file instead. | |
| 1002 writeln(); | |
| 1003 writeln(JNI_UTILS); | |
| 1004 | |
| 1005 node.methods.forEach(visit); | |
| 1006 | |
| 1007 writeln(); | |
| 1008 writeln('#ifdef __cplusplus'); | |
| 1009 writeln('}'); | |
| 1010 writeln('#endif'); | |
| 1011 } | |
| 1012 | |
| 1013 visitMethod(Method node) { | |
| 1014 String name = node.name; | |
| 1015 String id = '_k${name}Id'; | |
| 1016 | |
| 1017 writeln(); | |
| 1018 write('static const MethodId $id = '); | |
| 1019 writeln('reinterpret_cast<MethodId>(${methodId++});'); | |
| 1020 | |
| 1021 writeln(); | |
| 1022 write('JNIEXPORT '); | |
| 1023 writeReturnType(node.returnType); | |
| 1024 write(' JNICALL Java_dartino_${serviceName}_${name}('); | |
| 1025 write('JNIEnv* _env, jclass'); | |
| 1026 if (node.arguments.isNotEmpty) write(', '); | |
| 1027 if (node.inputKind != InputKind.PRIMITIVES) { | |
| 1028 write('jobject ${node.arguments.single.name}'); | |
| 1029 } else { | |
| 1030 visitArguments(node.arguments); | |
| 1031 } | |
| 1032 writeln(') {'); | |
| 1033 if (node.inputKind != InputKind.PRIMITIVES) { | |
| 1034 visitStructArgumentMethodBody(id, node); | |
| 1035 } else { | |
| 1036 visitMethodBody(id, node); | |
| 1037 } | |
| 1038 writeln('}'); | |
| 1039 | |
| 1040 String callback; | |
| 1041 if (node.inputKind == InputKind.STRUCT) { | |
| 1042 Struct struct = node.arguments.single.type.resolved; | |
| 1043 StructLayout layout = struct.layout; | |
| 1044 callback = ensureCallback(node.returnType, layout); | |
| 1045 } else { | |
| 1046 callback = | |
| 1047 ensureCallback(node.returnType, node.inputPrimitiveStructLayout); | |
| 1048 } | |
| 1049 | |
| 1050 writeln(); | |
| 1051 write('JNIEXPORT void JNICALL '); | |
| 1052 write('Java_dartino_${serviceName}_${name}Async('); | |
| 1053 write('JNIEnv* _env, jclass'); | |
| 1054 if (node.arguments.isNotEmpty) write(', '); | |
| 1055 if (node.inputKind != InputKind.PRIMITIVES) { | |
| 1056 write('jobject ${node.arguments.single.name}'); | |
| 1057 } else { | |
| 1058 visitArguments(node.arguments); | |
| 1059 } | |
| 1060 writeln(', jobject _callback) {'); | |
| 1061 writeln(' jobject callback = NULL;'); | |
| 1062 writeln(' JavaVM* vm = NULL;'); | |
| 1063 writeln(' if (_callback) {'); | |
| 1064 writeln(' callback = _env->NewGlobalRef(_callback);'); | |
| 1065 writeln(' _env->GetJavaVM(&vm);'); | |
| 1066 writeln(' }'); | |
| 1067 // TODO(zerny): Issue #45. Store VM pointer in 'callback data' header field. | |
| 1068 if (node.inputKind != InputKind.PRIMITIVES) { | |
| 1069 visitStructArgumentMethodBody(id, | |
| 1070 node, | |
| 1071 extraArguments: [ 'vm' ], | |
| 1072 callback: callback); | |
| 1073 } else { | |
| 1074 visitMethodBody(id, | |
| 1075 node, | |
| 1076 extraArguments: [ 'vm' ], | |
| 1077 callback: callback); | |
| 1078 } | |
| 1079 writeln('}'); | |
| 1080 } | |
| 1081 | |
| 1082 visitMethodBody(String id, | |
| 1083 Method method, | |
| 1084 {bool cStyle: false, | |
| 1085 List<String> extraArguments: const [], | |
| 1086 String callback}) { | |
| 1087 String cast(String type) => CcVisitor.cast(type, false); | |
| 1088 | |
| 1089 String pointerToArgument(int offset, int pointers, String type) { | |
| 1090 offset += REQUEST_HEADER_SIZE; | |
| 1091 String prefix = cast('$type*'); | |
| 1092 if (pointers == 0) return '$prefix(_buffer + $offset)'; | |
| 1093 return '$prefix(_buffer + $offset + $pointers * sizeof(void*))'; | |
| 1094 } | |
| 1095 | |
| 1096 List<Formal> arguments = method.arguments; | |
| 1097 assert(method.inputKind == InputKind.PRIMITIVES); | |
| 1098 StructLayout layout = method.inputPrimitiveStructLayout; | |
| 1099 final bool async = callback != null; | |
| 1100 int size = REQUEST_HEADER_SIZE + layout.size; | |
| 1101 if (async) { | |
| 1102 write(' static const int kSize = '); | |
| 1103 writeln('${size} + ${extraArguments.length} * sizeof(void*);'); | |
| 1104 } else { | |
| 1105 writeln(' static const int kSize = ${size};'); | |
| 1106 } | |
| 1107 | |
| 1108 if (async) { | |
| 1109 writeln(' char* _buffer = ${cast("char*")}(malloc(kSize));'); | |
| 1110 } else { | |
| 1111 writeln(' char _bits[kSize];'); | |
| 1112 writeln(' char* _buffer = _bits;'); | |
| 1113 } | |
| 1114 | |
| 1115 // Mark the message as being non-segmented. | |
| 1116 writeln(' *${pointerToArgument(-8, 0, "int64_t")} = 0;'); | |
| 1117 | |
| 1118 int arity = arguments.length; | |
| 1119 for (int i = 0; i < arity; i++) { | |
| 1120 String name = arguments[i].name; | |
| 1121 int offset = layout[arguments[i]].offset; | |
| 1122 String type = PRIMITIVE_TYPES[arguments[i].type.identifier]; | |
| 1123 writeln(' *${pointerToArgument(offset, 0, type)} = $name;'); | |
| 1124 } | |
| 1125 | |
| 1126 if (async) { | |
| 1127 writeln(' CallbackInfo* info = callback ? new CallbackInfo(callback, vm)
: NULL;'); | |
| 1128 writeln(' *${pointerToArgument(-16, 0, "CallbackInfo*")} = info;'); | |
| 1129 write(' ServiceApiInvokeAsync(service_id_, $id, $callback, '); | |
| 1130 writeln('_buffer, kSize);'); | |
| 1131 } else { | |
| 1132 writeln(' ServiceApiInvoke(service_id_, $id, _buffer, kSize);'); | |
| 1133 if (method.outputKind == OutputKind.STRUCT) { | |
| 1134 Type type = method.returnType; | |
| 1135 writeln(' int64_t result = *${pointerToArgument(0, 0, 'int64_t')};'); | |
| 1136 writeln(' char* memory = reinterpret_cast<char*>(result);'); | |
| 1137 writeln(' jobject rootSegment = GetRootSegment(_env, memory);'); | |
| 1138 writeln(' jclass resultClass = ' | |
| 1139 '_env->FindClass("dartino/${type.identifier}");'); | |
| 1140 writeln(' jmethodID create = _env->GetStaticMethodID(' | |
| 1141 'resultClass, "create", ' | |
| 1142 '"(Ljava/lang/Object;)Ldartino/${type.identifier};");'); | |
| 1143 writeln(' jobject resultObject = _env->CallStaticObjectMethod(' | |
| 1144 'resultClass, create, rootSegment);'); | |
| 1145 writeln(' return resultObject;'); | |
| 1146 } else if (!method.returnType.isVoid) { | |
| 1147 writeln(' return *${pointerToArgument(0, 0, 'int64_t')};'); | |
| 1148 } | |
| 1149 } | |
| 1150 } | |
| 1151 | |
| 1152 visitStructArgumentMethodBody(String id, | |
| 1153 Method method, | |
| 1154 {bool cStyle: false, | |
| 1155 List<String> extraArguments: const [], | |
| 1156 String callback}) { | |
| 1157 String cast(String type) => CcVisitor.cast(type, false); | |
| 1158 | |
| 1159 String pointerToArgument(int offset, int pointers, String type) { | |
| 1160 offset += REQUEST_HEADER_SIZE; | |
| 1161 String prefix = cast('$type*'); | |
| 1162 if (pointers == 0) return '$prefix(buffer + $offset)'; | |
| 1163 return '$prefix(buffer + $offset + $pointers * sizeof(void*))'; | |
| 1164 } | |
| 1165 | |
| 1166 String argumentName = method.arguments.single.name; | |
| 1167 | |
| 1168 bool async = callback != null; | |
| 1169 String javaCallback = async ? 'callback' : 'NULL'; | |
| 1170 String javaVM = async ? 'vm' : 'NULL'; | |
| 1171 | |
| 1172 writeln(' char* buffer = NULL;'); | |
| 1173 writeln(' int size = ComputeMessage(' | |
| 1174 '_env, $argumentName, $javaCallback, $javaVM, &buffer);'); | |
| 1175 | |
| 1176 if (async) { | |
| 1177 write(' ServiceApiInvokeAsync(service_id_, $id, $callback, '); | |
| 1178 writeln('buffer, size);'); | |
| 1179 } else { | |
| 1180 writeln(' ServiceApiInvoke(service_id_, $id, buffer, size);'); | |
| 1181 writeln(' int64_t result = *${pointerToArgument(0, 0, 'int64_t')};'); | |
| 1182 writeln(' DeleteMessage(buffer);'); | |
| 1183 if (method.outputKind == OutputKind.STRUCT) { | |
| 1184 Type type = method.returnType; | |
| 1185 writeln(' char* memory = reinterpret_cast<char*>(result);'); | |
| 1186 writeln(' jobject rootSegment = GetRootSegment(_env, memory);'); | |
| 1187 writeln(' jclass resultClass = ' | |
| 1188 '_env->FindClass("dartino/${type.identifier}");'); | |
| 1189 writeln(' jmethodID create = _env->GetStaticMethodID(' | |
| 1190 'resultClass, "create", ' | |
| 1191 '"(Ljava/lang/Object;)Ldartino/${type.identifier};");'); | |
| 1192 writeln(' jobject resultObject = _env->CallStaticObjectMethod(' | |
| 1193 'resultClass, create, rootSegment);'); | |
| 1194 writeln(' return resultObject;'); | |
| 1195 } else { | |
| 1196 if (!method.returnType.isVoid) writeln(' return result;'); | |
| 1197 } | |
| 1198 } | |
| 1199 } | |
| 1200 | |
| 1201 static const Map<String, String> PRIMITIVE_TYPES = const { | |
| 1202 'void' : 'void', | |
| 1203 'bool' : 'jboolean', | |
| 1204 | |
| 1205 'uint8' : 'jboolean', | |
| 1206 'uint16' : 'jchar', | |
| 1207 | |
| 1208 'int8' : 'jbyte', | |
| 1209 'int16' : 'jshort', | |
| 1210 'int32' : 'jint', | |
| 1211 'int64' : 'jlong', | |
| 1212 | |
| 1213 'float32' : 'jfloat', | |
| 1214 'float64' : 'jdouble', | |
| 1215 }; | |
| 1216 | |
| 1217 void writeType(Type node) { | |
| 1218 String type = PRIMITIVE_TYPES[node.identifier]; | |
| 1219 write(type); | |
| 1220 } | |
| 1221 | |
| 1222 void writeReturnType(Type node) { | |
| 1223 Node resolved = node.resolved; | |
| 1224 if (resolved != null) { | |
| 1225 write('jobject'); | |
| 1226 } else { | |
| 1227 String type = PRIMITIVE_TYPES[node.identifier]; | |
| 1228 write(type); | |
| 1229 } | |
| 1230 } | |
| 1231 | |
| 1232 static const Map<String, String> PRIMITIVE_JNI_SIG = const { | |
| 1233 'void' : '', | |
| 1234 'bool' : 'Z', | |
| 1235 | |
| 1236 'uint8' : 'Z', | |
| 1237 'uint16' : 'C', | |
| 1238 | |
| 1239 'int8' : 'B', | |
| 1240 'int16' : 'S', | |
| 1241 'int32' : 'I', | |
| 1242 'int64' : 'J', | |
| 1243 | |
| 1244 'float32' : 'F', | |
| 1245 'float64' : 'D', | |
| 1246 }; | |
| 1247 | |
| 1248 String getJNISignatureType(Type type) { | |
| 1249 String name = type.identifier; | |
| 1250 if (type.isPrimitive) return PRIMITIVE_JNI_SIG[name]; | |
| 1251 return 'Ldartino/$name;'; | |
| 1252 } | |
| 1253 | |
| 1254 final Map<String, String> callbacks = {}; | |
| 1255 String ensureCallback(Type type, | |
| 1256 StructLayout layout, | |
| 1257 {bool cStyle: false}) { | |
| 1258 String key = '${type.identifier}_${layout.size}'; | |
| 1259 return callbacks.putIfAbsent(key, () { | |
| 1260 String cast(String type) => CcVisitor.cast(type, cStyle); | |
| 1261 String pointerToArgument(int offset, String type) { | |
| 1262 offset += REQUEST_HEADER_SIZE; | |
| 1263 String prefix = cast('$type*'); | |
| 1264 return '$prefix(buffer + $offset)'; | |
| 1265 } | |
| 1266 String name = 'Unwrap_$key'; | |
| 1267 writeln(); | |
| 1268 writeln('static void $name(void* raw) {'); | |
| 1269 writeln(' char* buffer = ${cast('char*')}(raw);'); | |
| 1270 write(' CallbackInfo* info ='); | |
| 1271 writeln(' *${pointerToArgument(-16, "CallbackInfo*")};'); | |
| 1272 writeln(' if (info == NULL) return;'); | |
| 1273 writeln(' JNIEnv* env = AttachCurrentThreadAndGetEnv(info->vm);'); | |
| 1274 writeln(' jclass clazz = env->GetObjectClass(info->callback);'); | |
| 1275 if (!type.isVoid) { | |
| 1276 write(' int64_t result ='); | |
| 1277 writeln(' *${pointerToArgument(0, 'int64_t')};'); | |
| 1278 writeln(' DeleteMessage(buffer);'); | |
| 1279 if (!type.isPrimitive) { | |
| 1280 writeln(' char* memory = reinterpret_cast<char*>(result);'); | |
| 1281 writeln(' jobject rootSegment = GetRootSegment(env, memory);'); | |
| 1282 write(' jfieldID returnTypeField = env->GetFieldID('); | |
| 1283 writeln('clazz, "returnType", "Ljava/lang/Class;");'); | |
| 1284 write(' jclass resultClass = (jclass)'); | |
| 1285 writeln('env->GetObjectField(info->callback, returnTypeField);'); | |
| 1286 writeln(' jmethodID create = env->GetStaticMethodID(' | |
| 1287 'resultClass, "create", ' | |
| 1288 '"(Ljava/lang/Object;)Ldartino/${type.identifier};");'); | |
| 1289 writeln(' jobject resultObject = env->CallStaticObjectMethod(' | |
| 1290 'resultClass, create, rootSegment);'); | |
| 1291 } | |
| 1292 } else { | |
| 1293 writeln(' DeleteMessage(buffer);'); | |
| 1294 } | |
| 1295 write(' jmethodID methodId = env->GetMethodID'); | |
| 1296 write('(clazz, "handle", '); | |
| 1297 if (type.isVoid) { | |
| 1298 writeln('"()V");'); | |
| 1299 writeln(' env->CallVoidMethod(info->callback, methodId);'); | |
| 1300 } else { | |
| 1301 String signatureType = getJNISignatureType(type); | |
| 1302 writeln('"($signatureType)V");'); | |
| 1303 write(' env->CallVoidMethod(info->callback, methodId,'); | |
| 1304 if (!type.isPrimitive) { | |
| 1305 writeln(' resultObject);'); | |
| 1306 } else { | |
| 1307 writeln(' result);'); | |
| 1308 } | |
| 1309 } | |
| 1310 writeln(' env->DeleteGlobalRef(info->callback);'); | |
| 1311 writeln(' DetachCurrentThread(info->vm);'); | |
| 1312 writeln(' delete info;'); | |
| 1313 writeln('}'); | |
| 1314 return name; | |
| 1315 }); | |
| 1316 } | |
| 1317 } | |
| 1318 | |
| 1319 void _generateServiceJniMakeFiles(String path, | |
| 1320 Unit unit, | |
| 1321 String resourcesDirectory, | |
| 1322 String outputDirectory) { | |
| 1323 String out = join(outputDirectory, 'java'); | |
| 1324 // TODO(stanm): pass dartino root directly | |
| 1325 String dartinoRoot = join(resourcesDirectory, '..', '..', '..', '..', '..'); | |
| 1326 String dartinoLibraryBuildDir = join(dartinoRoot, | |
| 1327 'tools', | |
| 1328 'android_build', | |
| 1329 'jni'); | |
| 1330 | |
| 1331 String dartinoIncludeDir = join(dartinoRoot, 'include'); | |
| 1332 | |
| 1333 String modulePath = relative(dartinoLibraryBuildDir, from: out); | |
| 1334 String includePath = relative(dartinoIncludeDir, from: out); | |
| 1335 | |
| 1336 StringBuffer buffer = new StringBuffer(HEADER_MK); | |
| 1337 | |
| 1338 buffer.writeln(); | |
| 1339 buffer.writeln('LOCAL_PATH := \$(call my-dir)'); | |
| 1340 | |
| 1341 buffer.writeln(); | |
| 1342 buffer.writeln('include \$(CLEAR_VARS)'); | |
| 1343 buffer.writeln('LOCAL_MODULE := dartino'); | |
| 1344 buffer.writeln('LOCAL_CFLAGS := -DDARTINO32'); | |
| 1345 buffer.writeln('LOCAL_LDLIBS := -llog -ldl -rdynamic'); | |
| 1346 | |
| 1347 buffer.writeln(); | |
| 1348 buffer.writeln('LOCAL_SRC_FILES := \\'); | |
| 1349 buffer.writeln('\tdartino_api_wrapper.cc \\'); | |
| 1350 buffer.writeln('\tdartino_service_api_wrapper.cc \\'); | |
| 1351 | |
| 1352 if (unit.services.length > 1) { | |
| 1353 print('Java plugin: multiple services in one file is not supported.'); | |
| 1354 } | |
| 1355 String projectName = basenameWithoutExtension(path); | |
| 1356 String file = '${projectName}_wrapper'; | |
| 1357 | |
| 1358 buffer.writeln('\t${file}.cc'); | |
| 1359 | |
| 1360 buffer.writeln(); | |
| 1361 buffer.writeln('LOCAL_C_INCLUDES += \$(LOCAL_PATH)'); | |
| 1362 buffer.writeln('LOCAL_C_INCLUDES += ${includePath}'); | |
| 1363 buffer.writeln('LOCAL_STATIC_LIBRARIES := dartino-library'); | |
| 1364 | |
| 1365 buffer.writeln(); | |
| 1366 buffer.writeln('include \$(BUILD_SHARED_LIBRARY)'); | |
| 1367 | |
| 1368 buffer.writeln(); | |
| 1369 buffer.writeln('\$(call import-module, ${modulePath})'); | |
| 1370 | |
| 1371 writeToFile(join(out, 'jni'), 'Android', buffer.toString(), | |
| 1372 extension: 'mk'); | |
| 1373 | |
| 1374 buffer = new StringBuffer(HEADER_MK); | |
| 1375 buffer.writeln('APP_STL := gnustl_static'); | |
| 1376 buffer.writeln('APP_ABI := x86 armeabi-v7a'); | |
| 1377 // TODO(zerny): Is this the right place and way to ensure ABI >= 8? | |
| 1378 buffer.writeln('APP_PLATFORM := android-8'); | |
| 1379 writeToFile(join(out, 'jni'), 'Application', buffer.toString(), | |
| 1380 extension: 'mk'); | |
| 1381 } | |
| OLD | NEW |