1 /* This file is part of the Spring engine (GPL v2 or later), see LICENSE.html */
2
3 #include "JavaBridge.h"
4
5 #include "InterfaceDefines.h"
6 #include "JvmLocater.h"
7 #include "JniUtil.h"
8 #include "EventsJNIBridge.h"
9 #include "CUtils/Util.h"
10 #include "CUtils/SimpleLog.h"
11 #include "CUtils/SharedLibrary.h"
12
13 #include "ExternalAI/Interface/aidefines.h"
14 #include "ExternalAI/Interface/SAIInterfaceLibrary.h"
15 #include "ExternalAI/Interface/SAIInterfaceCallback.h"
16 #include "ExternalAI/Interface/SSkirmishAILibrary.h"
17 #include "ExternalAI/Interface/SSkirmishAICallback.h"
18 #include "System/SafeCStrings.h"
19 #include "lib/streflop/streflopC.h"
20
21 #include <jni.h>
22
23 #include <string.h> // strlen(), strcat(), strcpy()
24 #include <stdlib.h> // malloc(), calloc(), free()
25
26 struct Properties {
27 size_t size;
28 const char** keys;
29 const char** values;
30 };
31
32 static int interfaceId = -1;
33 static const struct SAIInterfaceCallback* callback = NULL;
34 static struct Properties* jvmCfgProps = NULL;
35
36 static size_t skirmishAIId_sizeMax = 0;
37 static size_t* skirmishAIId_skirmishAiImpl;
38
39 static size_t skirmishAiImpl_sizeMax = 0;
40 static size_t skirmishAiImpl_size = 0;
41 static char** skirmishAiImpl_className;
42 static jobject* skirmishAiImpl_instance;
43 static jobject* skirmishAiImpl_classLoader;
44
45 // vars used to integrate the JVM
46 // it is loaded at runtime, not at loadtime
47 static sharedLib_t jvmSharedLib = NULL;
48 typedef jint (JNICALL JNI_GetDefaultJavaVMInitArgs_t)(void* vmArgs);
49 typedef jint (JNICALL JNI_CreateJavaVM_t)(JavaVM** vm, void** jniEnv, void* vmArgs);
50 typedef jint (JNICALL JNI_GetCreatedJavaVMs_t)(JavaVM** vms, jsize vms_sizeMax, jsize* vms_size);
51 static JNI_GetDefaultJavaVMInitArgs_t* JNI_GetDefaultJavaVMInitArgs_f;
52 static JNI_CreateJavaVM_t* JNI_CreateJavaVM_f;
53 static JNI_GetCreatedJavaVMs_t* JNI_GetCreatedJavaVMs_f;
54
55
56 // ### JNI global vars ###
57
58 /// Java VM instance reference
59 static JavaVM* g_jvm = NULL;
60
61 /// AI Callback class
62 static jclass g_cls_aiCallback = NULL;
63 /// AI Callback Constructor: AICallback(int skirmishAIId)
64 static jmethodID g_m_aiCallback_ctor_I = NULL;
65
66 /// Basic AI interface.
67 static jclass g_cls_ai_int = NULL;
68
69
70 // ### General helper functions following ###
71
72 /// Sets the FPU state to how spring likes it
java_establishSpringEnv()73 inline void java_establishSpringEnv() {
74
75 //(*g_jvm)->DetachCurrentThread(g_jvm);
76 streflop_init_Simple();
77 }
78 /// The JVM sets the environment it wants automatically, so this is a no-op
java_establishJavaEnv()79 inline void java_establishJavaEnv() {}
80
81
minSize(size_t size1,size_t size2)82 static inline size_t minSize(size_t size1, size_t size2) {
83 return (size1 < size2) ? size1 : size2;
84 }
85
java_getValueByKey(const struct Properties * props,const char * key)86 static const char* java_getValueByKey(const struct Properties* props, const char* key) {
87 return util_map_getValueByKey(props->size, props->keys, props->values, key);
88 }
89
90 // /**
91 // * Called when the JVM loads this native library.
92 // */
93 // JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
94 // return JNI_VERSION_1_6;
95 // }
96 //
97 // /**
98 // * Called when the JVM unloads this native library.
99 // */
100 // JNIEXPORT void JNICALL JNI_OnUnload(JavaVM* vm, void* reserved) {}
101
102
103
104
105 // ### JNI helper functions following ###
106
107 /**
108 * Creates the AI Interface global Java class path.
109 *
110 * It will consist of the following:
111 * {spring-data-dir}/{AI_INTERFACES_DATA_DIR}/Java/{version}/AIInterface.jar
112 * {spring-data-dir}/{AI_INTERFACES_DATA_DIR}/Java/{version}/(j)?config/
113 * {spring-data-dir}/{AI_INTERFACES_DATA_DIR}/Java/{version}/(j)?config/[*].jar
114 * {spring-data-dir}/{AI_INTERFACES_DATA_DIR}/Java/{version}/(j)?resources/
115 * {spring-data-dir}/{AI_INTERFACES_DATA_DIR}/Java/{version}/(j)?resources/[*].jar
116 * {spring-data-dir}/{AI_INTERFACES_DATA_DIR}/Java/{version}/(j)?script/
117 * {spring-data-dir}/{AI_INTERFACES_DATA_DIR}/Java/{version}/(j)?script/[*].jar
118 * {spring-data-dir}/{AI_INTERFACES_DATA_DIR}/Java/{version}/jlib/
119 * {spring-data-dir}/{AI_INTERFACES_DATA_DIR}/Java/{version}/jlib/[*].jar
120 * TODO: {spring-data-dir}/{AI_INTERFACES_DATA_DIR}/Java/common/jlib/
121 * TODO: {spring-data-dir}/{AI_INTERFACES_DATA_DIR}/Java/common/jlib/[*].jar
122 */
java_createClassPath(char * classPathStr,const size_t classPathStr_sizeMax)123 static size_t java_createClassPath(char* classPathStr, const size_t classPathStr_sizeMax) {
124
125 // the dirs and .jar files in the following array
126 // will be concatenated with intermediate path separators
127 // to form the classPathStr
128 static const size_t classPath_sizeMax = 128;
129 char** classPath = (char**) calloc(classPath_sizeMax, sizeof(char*));
130 size_t classPath_size = 0;
131
132 // the Java AI Interfaces java library file path (.../AIInterface.jar)
133 // We need to search for this jar, instead of looking only where
134 // the AIInterface.so/InterfaceInfo.lua is, because on some systems
135 // (eg. Debian), the .so is in /usr/lib, and the .jar's are in /usr/shared.
136 char* mainJarPath = callback->DataDirs_allocatePath(interfaceId,
137 JAVA_AI_INTERFACE_LIBRARY_FILE_NAME,
138 false, false, false, false);
139 classPath[classPath_size++] = util_allocStrCpy(mainJarPath);
140
141 bool ok = util_getParentDir(mainJarPath);
142 if (!ok) {
143 simpleLog_logL(SIMPLELOG_LEVEL_ERROR,
144 "Retrieving the parent dir of the path to AIInterface.jar (%s) failed.",
145 mainJarPath);
146 }
147 char* jarsDataDir = mainJarPath;
148 mainJarPath = NULL;
149
150 // the directories in the following list will be searched for .jar files
151 // which will then be added to the classPathStr, plus the dirs will be added
152 // to the classPathStr directly, so you can keep .class files in there
153 static const size_t jarDirs_sizeMax = 128;
154 char** jarDirs = (char**) calloc(jarDirs_sizeMax, sizeof(char*));
155 size_t jarDirs_size = 0;
156
157 // add to classpath:
158 // {spring-data-dir}/Interfaces/Java/0.1/${x}/
159 jarDirs[jarDirs_size++] = util_allocStrCatFSPath(2, jarsDataDir, "jconfig");
160 jarDirs[jarDirs_size++] = util_allocStrCatFSPath(2, jarsDataDir, "config");
161 jarDirs[jarDirs_size++] = util_allocStrCatFSPath(2, jarsDataDir, "jresources");
162 jarDirs[jarDirs_size++] = util_allocStrCatFSPath(2, jarsDataDir, "resources");
163 jarDirs[jarDirs_size++] = util_allocStrCatFSPath(2, jarsDataDir, "jscript");
164 jarDirs[jarDirs_size++] = util_allocStrCatFSPath(2, jarsDataDir, "script");
165 jarDirs[jarDirs_size++] = util_allocStrCatFSPath(2, jarsDataDir, "jlib");
166 // "lib" is for native libs only
167
168 // add the jar dirs (for .class files) and all contained .jars recursively
169 size_t jd, jf;
170 for (jd=0; (jd < jarDirs_size) && (classPath_size < classPath_sizeMax); ++jd) {
171 if (util_fileExists(jarDirs[jd])) {
172 // add the dir directly
173 // For this to work properly with URLClassPathHandler,
174 // we have to ensure there is a '/' at the end,
175 // for the class-path-part to be recognized as a directory.
176 classPath[classPath_size++] = util_allocStrCat(2, jarDirs[jd], "/");
177
178 // add the contained jars recursively
179 static const size_t jarFiles_sizeMax = 128;
180 char** jarFiles = (char**) calloc(jarFiles_sizeMax, sizeof(char*));
181 const size_t jarFiles_size = util_listFiles(jarDirs[jd], ".jar",
182 jarFiles, true, jarFiles_sizeMax);
183 for (jf=0; (jf < jarFiles_size) && (classPath_size < classPath_sizeMax); ++jf) {
184 classPath[classPath_size++] = util_allocStrCatFSPath(2,
185 jarDirs[jd], jarFiles[jf]);
186 FREE(jarFiles[jf]);
187 }
188 FREE(jarFiles);
189 }
190 FREE(jarDirs[jd]);
191 }
192 FREE(jarDirs);
193
194 // concat the classpath entries
195 classPathStr[0] = '\0';
196 if (classPath[0] != NULL) {
197 STRCAT_T(classPathStr, classPathStr_sizeMax, classPath[0]);
198 FREE(classPath[0]);
199 }
200 size_t cp;
201 for (cp=1; cp < classPath_size; ++cp) {
202 if (classPath[cp] != NULL) {
203 STRCAT_T(classPathStr, classPathStr_sizeMax, ENTRY_DELIM);
204 STRCAT_T(classPathStr, classPathStr_sizeMax, classPath[cp]);
205 FREE(classPath[cp]);
206 }
207 }
208 FREE(classPath);
209
210 return classPath_size;
211 }
212
213 /**
214 * Creates a Skirmish AI local Java class path.
215 *
216 * It will consist of the following:
217 * {spring-data-dir}/{SKIRMISH_AI_DATA_DIR}/{ai-name}/{ai-version}/SkirmishAI.jar
218 * {spring-data-dir}/{SKIRMISH_AI_DATA_DIR}/{ai-name}/{ai-version}/(j)?config/
219 * {spring-data-dir}/{SKIRMISH_AI_DATA_DIR}/{ai-name}/{ai-version}/(j)?config/[*].jar
220 * {spring-data-dir}/{SKIRMISH_AI_DATA_DIR}/{ai-name}/{ai-version}/(j)?resources/
221 * {spring-data-dir}/{SKIRMISH_AI_DATA_DIR}/{ai-name}/{ai-version}/(j)?resources/[*].jar
222 * {spring-data-dir}/{SKIRMISH_AI_DATA_DIR}/{ai-name}/{ai-version}/(j)?script/
223 * {spring-data-dir}/{SKIRMISH_AI_DATA_DIR}/{ai-name}/{ai-version}/(j)?script/[*].jar
224 * {spring-data-dir}/{SKIRMISH_AI_DATA_DIR}/{ai-name}/{ai-version}/jlib/
225 * {spring-data-dir}/{SKIRMISH_AI_DATA_DIR}/{ai-name}/{ai-version}/jlib/[*].jar
226 * {spring-data-dir}/{SKIRMISH_AI_DATA_DIR}/{ai-name}/common/(j)?config/
227 * {spring-data-dir}/{SKIRMISH_AI_DATA_DIR}/{ai-name}/common/(j)?config/[*].jar
228 * {spring-data-dir}/{SKIRMISH_AI_DATA_DIR}/{ai-name}/common/(j)?resources/
229 * {spring-data-dir}/{SKIRMISH_AI_DATA_DIR}/{ai-name}/common/(j)?resources/[*].jar
230 * {spring-data-dir}/{SKIRMISH_AI_DATA_DIR}/{ai-name}/common/(j)?script/
231 * {spring-data-dir}/{SKIRMISH_AI_DATA_DIR}/{ai-name}/common/(j)?script/[*].jar
232 * {spring-data-dir}/{SKIRMISH_AI_DATA_DIR}/{ai-name}/common/jlib/
233 * {spring-data-dir}/{SKIRMISH_AI_DATA_DIR}/{ai-name}/common/jlib/[*].jar
234 */
java_createAIClassPath(const char * shortName,const char * version,char ** classPathParts,const size_t classPathParts_sizeMax)235 static size_t java_createAIClassPath(const char* shortName, const char* version,
236 char** classPathParts, const size_t classPathParts_sizeMax) {
237
238 size_t classPathParts_size = 0;
239
240 // the .jar files in the following list will be added to the classpath
241 const size_t jarFiles_sizeMax = classPathParts_sizeMax;
242 char** jarFiles = (char**) calloc(jarFiles_sizeMax, sizeof(char*));
243 size_t jarFiles_size = 0;
244
245 const char* const skirmDD =
246 callback->SkirmishAIs_Info_getValueByKey(interfaceId,
247 shortName, version,
248 SKIRMISH_AI_PROPERTY_DATA_DIR);
249 if (skirmDD == NULL) {
250 simpleLog_logL(SIMPLELOG_LEVEL_ERROR,
251 "Retrieving the data-dir of Skirmish AI %s-%s failed.",
252 shortName, version);
253 }
254 // {spring-data-dir}/{SKIRMISH_AI_DATA_DIR}/{ai-name}/{ai-version}/SkirmishAI.jar
255 jarFiles[jarFiles_size++] = util_allocStrCatFSPath(2,
256 skirmDD, "SkirmishAI.jar");
257
258 // the directories in the following list will be searched for .jar files
259 // which then will be added to the classpath, plus they will be added
260 // to the classpath directly, so you can keep .class files in there
261 const size_t jarDirs_sizeMax = classPathParts_sizeMax;
262 char** jarDirs = (char**) calloc(jarDirs_sizeMax, sizeof(char*));
263 size_t jarDirs_size = 0;
264
265 // add to classpath ...
266
267 // {spring-data-dir}/Skirmish/MyJavaAI/0.1/SkirmishAI/
268 // this can be usefull for AI devs while testing,
269 // if they do not want to put everything into a jar all the time
270 jarDirs[jarDirs_size++] = util_allocStrCatFSPath(2, skirmDD, "SkirmishAI");
271
272 // add to classpath:
273 // {spring-data-dir}/Skirmish/MyJavaAI/0.1/${x}/
274 jarDirs[jarDirs_size++] = util_allocStrCatFSPath(2, skirmDD, "jconfig");
275 jarDirs[jarDirs_size++] = util_allocStrCatFSPath(2, skirmDD, "config");
276 jarDirs[jarDirs_size++] = util_allocStrCatFSPath(2, skirmDD, "jresources");
277 jarDirs[jarDirs_size++] = util_allocStrCatFSPath(2, skirmDD, "resources");
278 jarDirs[jarDirs_size++] = util_allocStrCatFSPath(2, skirmDD, "jscript");
279 jarDirs[jarDirs_size++] = util_allocStrCatFSPath(2, skirmDD, "script");
280 jarDirs[jarDirs_size++] = util_allocStrCatFSPath(2, skirmDD, "jlib");
281 // "lib" is for native libs only
282
283 // add the dir common for all versions of the Skirmish AI,
284 // if it is specified and exists
285 const char* const skirmDDCommon =
286 callback->SkirmishAIs_Info_getValueByKey(interfaceId,
287 shortName, version,
288 SKIRMISH_AI_PROPERTY_DATA_DIR_COMMON);
289 if (skirmDDCommon != NULL) {
290 // add to classpath:
291 // {spring-data-dir}/Skirmish/MyJavaAI/common/${x}/
292 jarDirs[jarDirs_size++] = util_allocStrCatFSPath(2, skirmDDCommon, "jconfig");
293 jarDirs[jarDirs_size++] = util_allocStrCatFSPath(2, skirmDDCommon, "config");
294 jarDirs[jarDirs_size++] = util_allocStrCatFSPath(2, skirmDDCommon, "jresources");
295 jarDirs[jarDirs_size++] = util_allocStrCatFSPath(2, skirmDDCommon, "resources");
296 jarDirs[jarDirs_size++] = util_allocStrCatFSPath(2, skirmDDCommon, "jscript");
297 jarDirs[jarDirs_size++] = util_allocStrCatFSPath(2, skirmDDCommon, "script");
298 jarDirs[jarDirs_size++] = util_allocStrCatFSPath(2, skirmDDCommon, "jlib");
299 // "lib" is for native libs only
300 }
301
302 // add the directly specified .jar files
303 size_t jf;
304 for (jf = 0; (jf < jarFiles_size) && (classPathParts_size < classPathParts_sizeMax); ++jf) {
305 classPathParts[classPathParts_size++] = util_allocStrCpy(jarFiles[jf]);
306 FREE(jarFiles[jf]);
307 }
308
309 // add the dirs and the contained .jar files
310 size_t jd, sjf;
311 for (jd = 0; (jd < jarDirs_size) && (classPathParts_size < classPathParts_sizeMax); ++jd)
312 {
313 if (jarDirs[jd] != NULL && util_fileExists(jarDirs[jd])) {
314 // add the jar dir (for .class files)
315 // For this to work properly with URLClassPathHandler,
316 // we have to ensure there is a '/' at the end,
317 // for the class-path-part to be recognized as a directory.
318 classPathParts[classPathParts_size++] = util_allocStrCat(2, jarDirs[jd], "/");
319
320 // add the jars in the dir
321 const size_t subJarFiles_sizeMax = classPathParts_sizeMax - classPathParts_size;
322 char** subJarFiles = (char**) calloc(subJarFiles_sizeMax, sizeof(char*));
323 const size_t subJarFiles_size = util_listFiles(jarDirs[jd], ".jar",
324 subJarFiles, true, subJarFiles_sizeMax);
325 for (sjf = 0; (sjf < subJarFiles_size) && (classPathParts_size < classPathParts_sizeMax); ++sjf) {
326 // .../[*].jar
327 classPathParts[classPathParts_size++] =
328 util_allocStrCatFSPath(2, jarDirs[jd], subJarFiles[sjf]);
329 FREE(subJarFiles[sjf]);
330 }
331 FREE(subJarFiles);
332 }
333 FREE(jarDirs[jd]);
334 }
335
336 FREE(jarDirs);
337 FREE(jarFiles);
338
339 return classPathParts_size;
340 }
341
java_createAIClassLoader(JNIEnv * env,const char * shortName,const char * version)342 static jobject java_createAIClassLoader(JNIEnv* env,
343 const char* shortName, const char* version) {
344
345
346 jobject o_jClsLoader = NULL;
347
348 static const size_t classPathParts_sizeMax = 512;
349 char** classPathParts = (char**) calloc(classPathParts_sizeMax, sizeof(char*));
350 const size_t classPathParts_size =
351 java_createAIClassPath(shortName, version, classPathParts, classPathParts_sizeMax);
352
353 jobjectArray o_cppURLs = jniUtil_createURLArray(env, classPathParts_size);
354 if (o_cppURLs != NULL) {
355 #ifdef _WIN32
356 static const char* FILE_URL_PREFIX = "file:///";
357 #else // _WIN32
358 static const char* FILE_URL_PREFIX = "file://";
359 #endif // _WIN32
360 size_t cpp;
361 for (cpp = 0; cpp < classPathParts_size; ++cpp) {
362 #ifdef _WIN32
363 // we can not use windows path separators in file URLs
364 util_strReplaceChar(classPathParts[cpp], '\\', '/');
365 #endif
366
367 char* str_fileUrl = util_allocStrCat(2, FILE_URL_PREFIX, classPathParts[cpp]);
368 // TODO: check/test if this is allowed/ok
369 FREE(classPathParts[cpp]);
370 simpleLog_logL(SIMPLELOG_LEVEL_FINE,
371 "Skirmish AI %s %s class-path part %i: \"%s\"",
372 shortName, version, cpp, str_fileUrl);
373 jobject jurl_fileUrl = jniUtil_createURLObject(env, str_fileUrl);
374 FREE(str_fileUrl);
375 if (jurl_fileUrl == NULL) {
376 simpleLog_logL(SIMPLELOG_LEVEL_ERROR,
377 "Skirmish AI %s %s class-path part %i (\"%s\"): failed to create a URL",
378 shortName, version, cpp, str_fileUrl);
379 o_cppURLs = NULL;
380 break;
381 }
382 const bool inserted = jniUtil_insertURLIntoArray(env, o_cppURLs, cpp, jurl_fileUrl);
383 if (!inserted) {
384 simpleLog_logL(SIMPLELOG_LEVEL_ERROR,
385 "Skirmish AI %s %s class-path part %i (\"%s\"): failed to insert",
386 shortName, version, cpp, str_fileUrl);
387 o_cppURLs = NULL;
388 break;
389 }
390 }
391 }
392
393 if (o_cppURLs != NULL) {
394 o_jClsLoader = jniUtil_createURLClassLoader(env, o_cppURLs);
395 if (o_jClsLoader != NULL) {
396 o_jClsLoader = jniUtil_makeGlobalRef(env, o_jClsLoader, "Skirmish AI class-loader");
397 }
398 }
399
400 FREE(classPathParts);
401
402 return o_jClsLoader;
403 }
404
405 /**
406 * Load the interfaces JVM properties file.
407 */
java_readJvmCfgFile(struct Properties * props)408 static bool java_readJvmCfgFile(struct Properties* props) {
409
410 bool read = false;
411
412 const size_t props_sizeMax = 256;
413 props->size = 0;
414 props->keys = (const char**) calloc(props_sizeMax, sizeof(char*));
415 props->values = (const char**) calloc(props_sizeMax, sizeof(char*));
416
417 // ### read JVM options config file ###
418 char* jvmPropFile = callback->DataDirs_allocatePath(interfaceId,
419 JVM_PROPERTIES_FILE, false, false, false, false);
420 if (jvmPropFile == NULL) {
421 // if the version specific file does not exist,
422 // try to get the common one
423 jvmPropFile = callback->DataDirs_allocatePath(interfaceId,
424 JVM_PROPERTIES_FILE, false, false, false, true);
425 }
426
427 if (jvmPropFile != NULL) {
428 props->size = util_parsePropertiesFile(jvmPropFile,
429 props->keys, props->values, props_sizeMax);
430 read = true;
431 simpleLog_logL(SIMPLELOG_LEVEL_FINE,
432 "JVM: arguments loaded from: %s", jvmPropFile);
433 } else {
434 props->size = 0;
435 read = false;
436 simpleLog_logL(SIMPLELOG_LEVEL_FINE,
437 "JVM: arguments NOT loaded from: %s", jvmPropFile);
438 }
439
440 FREE(jvmPropFile);
441
442 return read;
443 }
444
445 /**
446 * Creates the Java library path.
447 * -> where native shared libraries are searched
448 *
449 * It will consist of the following:
450 * {spring-data-dir}/{AI_INTERFACES_DATA_DIR}/Java/{version}/
451 * {spring-data-dir}/{AI_INTERFACES_DATA_DIR}/Java/{version}/lib/
452 * {spring-data-dir}/{AI_INTERFACES_DATA_DIR}/Java/common/
453 * {spring-data-dir}/{AI_INTERFACES_DATA_DIR}/Java/common/lib/
454 */
java_createNativeLibsPath(char * libraryPath,const size_t libraryPath_sizeMax)455 static bool java_createNativeLibsPath(char* libraryPath, const size_t libraryPath_sizeMax) {
456
457 // {spring-data-dir}/{AI_INTERFACES_DATA_DIR}/Java/{version}/
458 const char* const dd_r =
459 callback->AIInterface_Info_getValueByKey(interfaceId,
460 AI_INTERFACE_PROPERTY_DATA_DIR);
461 if (dd_r == NULL) {
462 simpleLog_logL(SIMPLELOG_LEVEL_ERROR,
463 "Unable to find read-only data-dir.");
464 return false;
465 } else {
466 STRCPY_T(libraryPath, libraryPath_sizeMax, dd_r);
467 }
468
469 // {spring-data-dir}/{AI_INTERFACES_DATA_DIR}/Java/{version}/lib/
470 char* dd_lib_r = callback->DataDirs_allocatePath(interfaceId,
471 NATIVE_LIBS_DIR, false, false, true, false);
472 if (dd_lib_r == NULL) {
473 simpleLog_logL(SIMPLELOG_LEVEL_NORMAL,
474 "Unable to find read-only native libs data-dir (optional): %s",
475 NATIVE_LIBS_DIR);
476 } else {
477 STRCAT_T(libraryPath, libraryPath_sizeMax, ENTRY_DELIM);
478 STRCAT_T(libraryPath, libraryPath_sizeMax, dd_lib_r);
479 FREE(dd_lib_r);
480 }
481
482 // {spring-data-dir}/{AI_INTERFACES_DATA_DIR}/Java/common/
483 const char* const dd_r_common =
484 callback->AIInterface_Info_getValueByKey(interfaceId,
485 AI_INTERFACE_PROPERTY_DATA_DIR_COMMON);
486 if (dd_r_common == NULL || !util_fileExists(dd_r_common)) {
487 simpleLog_logL(SIMPLELOG_LEVEL_NORMAL,
488 "Unable to find common read-only data-dir (optional).");
489 } else {
490 STRCAT_T(libraryPath, libraryPath_sizeMax, ENTRY_DELIM);
491 STRCAT_T(libraryPath, libraryPath_sizeMax, dd_r_common);
492 }
493
494 // {spring-data-dir}/{AI_INTERFACES_DATA_DIR}/Java/common/lib/
495 if (dd_r_common != NULL) {
496 char* dd_lib_r_common = callback->DataDirs_allocatePath(interfaceId,
497 NATIVE_LIBS_DIR, false, false, true, true);
498 if (dd_lib_r_common == NULL || !util_fileExists(dd_lib_r_common)) {
499 simpleLog_logL(SIMPLELOG_LEVEL_NORMAL,
500 "Unable to find common read-only native libs data-dir (optional).");
501 } else {
502 STRCAT_T(libraryPath, libraryPath_sizeMax, ENTRY_DELIM);
503 STRCAT_T(libraryPath, libraryPath_sizeMax, dd_lib_r_common);
504 FREE(dd_lib_r_common);
505 }
506 }
507
508 return true;
509 }
java_createJavaVMInitArgs(struct JavaVMInitArgs * vm_args,const struct Properties * jvmProps)510 static bool java_createJavaVMInitArgs(struct JavaVMInitArgs* vm_args, const struct Properties* jvmProps) {
511
512 // ### evaluate JNI version to use ###
513 //jint jniVersion = JNI_VERSION_1_1;
514 //jint jniVersion = JNI_VERSION_1_2;
515 jint jniVersion = JNI_VERSION_1_4;
516 //jint jniVersion = JNI_VERSION_1_6;
517 if (jvmProps != NULL) {
518 const char* jniVersionFromCfg = java_getValueByKey(jvmProps,
519 "jvm.jni.version");
520 if (jniVersionFromCfg != NULL) {
521 unsigned long int jniVersion_tmp =
522 strtoul(jniVersionFromCfg, NULL, 16);
523 if (jniVersion_tmp != 0/* && jniVersion_tmp != ULONG_MAX*/) {
524 jniVersion = (jint) jniVersion_tmp;
525 }
526 }
527 }
528 simpleLog_logL(SIMPLELOG_LEVEL_FINE, "JVM: JNI version: %#x", jniVersion);
529 vm_args->version = jniVersion;
530
531 // ### check if debug related JVM options should be used ###
532 // if false, the JVM creation will fail if an
533 // unknown or invalid option was specified
534 bool useDebugOptions = true;
535 const char* useDebugOptionsStr = "auto";
536 if (jvmProps != NULL) {
537 const char* useDebugOptionsFromCfg =
538 util_map_getValueByKey(
539 jvmProps->size, jvmProps->keys, jvmProps->values,
540 "jvm.useDebugOptions");
541 if (useDebugOptionsFromCfg != NULL) {
542 useDebugOptionsStr = useDebugOptionsFromCfg;
543 }
544 }
545 {
546 if (strcmp(useDebugOptionsStr, "auto") == 0
547 || strcmp(useDebugOptionsStr, "Auto") == 0
548 || strcmp(useDebugOptionsStr, "AUTO") == 0
549 || strcmp(useDebugOptionsStr, "a") == 0
550 || strcmp(useDebugOptionsStr, "A") == 0)
551 {
552 // auto
553 #if defined DEBUG
554 useDebugOptions = true;
555 #else // defined DEBUG
556 useDebugOptions = false;
557 #endif // defined DEBUG
558 } else {
559 // true or false
560 useDebugOptions = util_strToBool(useDebugOptionsStr);
561 }
562 }
563
564 // ### check if unrecognized JVM options should be ignored ###
565 // if false, the JVM creation will fail if an
566 // unknown or invalid option was specified
567 bool ignoreUnrecognized = true;
568 if (jvmProps != NULL) {
569 const char* ignoreUnrecognizedFromCfg = java_getValueByKey(jvmProps,
570 "jvm.arguments.ignoreUnrecognized");
571 if (ignoreUnrecognizedFromCfg != NULL
572 && !util_strToBool(ignoreUnrecognizedFromCfg)) {
573 ignoreUnrecognized = false;
574 }
575 }
576 if (ignoreUnrecognized) {
577 simpleLog_logL(SIMPLELOG_LEVEL_FINE,
578 "JVM: ignoring unrecognized options");
579 vm_args->ignoreUnrecognized = JNI_TRUE;
580 } else {
581 simpleLog_logL(SIMPLELOG_LEVEL_FINE,
582 "JVM: NOT ignoring unrecognized options");
583 vm_args->ignoreUnrecognized = JNI_FALSE;
584 }
585
586 // ### create the Java class-path option ###
587 // autogenerate the class path
588 static const size_t classPath_sizeMax = 8 * 1024;
589 char* classPath = util_allocStr(classPath_sizeMax);
590 // ..., autogenerate it ...
591 if (!java_createClassPath(classPath, classPath_sizeMax)) {
592 simpleLog_logL(SIMPLELOG_LEVEL_ERROR,
593 "Failed creating Java class-path.");
594 FREE(classPath);
595 return false;
596 }
597 if (jvmProps != NULL) {
598 // ..., and append the part from the jvm options properties file,
599 // if it is specified there
600 const char* clsPathFromCfg = java_getValueByKey(jvmProps,
601 "jvm.option.java.class.path");
602 if (clsPathFromCfg != NULL) {
603 STRCAT_T(classPath, classPath_sizeMax, ENTRY_DELIM);
604 STRCAT_T(classPath, classPath_sizeMax, clsPathFromCfg);
605 }
606 }
607 // create the java.class.path option
608 static const size_t classPathOpt_sizeMax = 8 * 1024;
609 char* classPathOpt = util_allocStr(classPathOpt_sizeMax);
610 STRCPY_T(classPathOpt, classPathOpt_sizeMax, "-Djava.class.path=");
611 STRCAT_T(classPathOpt, classPathOpt_sizeMax, classPath);
612 FREE(classPath);
613
614 // ### create the Java library-path option ###
615 // autogenerate the java library path
616 static const size_t libraryPath_sizeMax = 4 * 1024;
617 char* libraryPath = util_allocStr(libraryPath_sizeMax);
618 // ..., autogenerate it ...
619 if (!java_createNativeLibsPath(libraryPath, libraryPath_sizeMax)) {
620 simpleLog_logL(SIMPLELOG_LEVEL_ERROR,
621 "Failed creating Java library-path.");
622 FREE(libraryPath);
623 return false;
624 }
625 if (jvmProps != NULL) {
626 // ..., and append the part from the jvm options properties file,
627 // if it is specified there
628 const char* libPathFromCfg = java_getValueByKey(jvmProps,
629 "jvm.option.java.library.path");
630 if (libPathFromCfg != NULL) {
631 STRCAT_T(libraryPath, libraryPath_sizeMax, ENTRY_DELIM);
632 STRCAT_T(libraryPath, libraryPath_sizeMax, libPathFromCfg);
633 }
634 }
635 // create the java.library.path option ...
636 // autogenerate it, and append the part from the jvm options file,
637 // if it is specified there
638 static const size_t libraryPathOpt_sizeMax = 4 * 1024;
639 char* libraryPathOpt = util_allocStr(libraryPathOpt_sizeMax);
640 STRCPY_T(libraryPathOpt, libraryPathOpt_sizeMax, "-Djava.library.path=");
641 STRCAT_T(libraryPathOpt, libraryPathOpt_sizeMax, libraryPath);
642 FREE(libraryPath);
643
644 // ### create and set all JVM options ###
645 static const size_t strOptions_sizeMax = 64;
646 const char* strOptions[strOptions_sizeMax];
647 size_t op = 0;
648
649 strOptions[op++] = classPathOpt;
650 strOptions[op++] = libraryPathOpt;
651
652 static const char* const JCPVAL = "-Djava.class.path=";
653 const size_t JCPVAL_size = strlen(JCPVAL);
654 static const char* const JLPVAL = "-Djava.library.path=";
655 const size_t JLPVAL_size = strlen(JCPVAL);
656 if (jvmProps != NULL) {
657 // ### add string options from the JVM config file with property name "jvm.option.x" ###
658 int i;
659 for (i=0; i < jvmProps->size; ++i) {
660 if (strcmp(jvmProps->keys[i], "jvm.option.x") == 0 ||
661 (useDebugOptions && (strcmp(jvmProps->keys[i], "jvm.option.debug.x") == 0))) {
662 const char* const val = jvmProps->values[i];
663 const size_t val_size = strlen(val);
664 // ignore "-Djava.class.path=..."
665 // and "-Djava.library.path=..." options
666 if (strncmp(val, JCPVAL, minSize(val_size, JCPVAL_size)) != 0 &&
667 strncmp(val, JLPVAL, minSize(val_size, JLPVAL_size)) != 0) {
668 strOptions[op++] = val;
669 }
670 }
671 }
672 } else {
673 // ### ... or set default ones, if the JVM config file was not found ###
674 simpleLog_logL(SIMPLELOG_LEVEL_WARNING, "JVM: properties file ("JVM_PROPERTIES_FILE") not found; using default options.");
675
676 strOptions[op++] = "-Xms4M";
677 strOptions[op++] = "-Xmx64M";
678 strOptions[op++] = "-Xss512K";
679 strOptions[op++] = "-Xoss400K";
680
681 #if defined DEBUG
682 strOptions[op++] = "-Xcheck:jni";
683 strOptions[op++] = "-verbose:jni";
684 strOptions[op++] = "-XX:+UnlockDiagnosticVMOptions";
685 strOptions[op++] = "-XX:+LogVMOutput";
686
687 strOptions[op++] = "-Xdebug";
688 strOptions[op++] = "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=7777";
689 // disable JIT (required for debugging under the classical VM)
690 strOptions[op++] = "-Djava.compiler=NONE";
691 // disable old JDB
692 strOptions[op++] = "-Xnoagent";
693 #endif // defined DEBUG
694 }
695
696 const size_t options_size = op;
697
698 vm_args->options = (struct JavaVMOption*) calloc(options_size, sizeof(struct JavaVMOption));
699
700 // fill strOptions into the JVM options
701 simpleLog_logL(SIMPLELOG_LEVEL_FINE, "JVM: options:", options_size);
702 char* dd_rw = callback->DataDirs_allocatePath(interfaceId,
703 "", true, true, true, false);
704 size_t i;
705 jint nOptions = 0;
706 for (i = 0; i < options_size; ++i) {
707 char* tmpOptionString = util_allocStrReplaceStr(strOptions[i],
708 "${home-dir}", dd_rw);
709 // do not add empty options
710 if (tmpOptionString != NULL) {
711 if (strlen(tmpOptionString) > 0) {
712 vm_args->options[nOptions].optionString = tmpOptionString;
713 vm_args->options[nOptions].extraInfo = NULL;
714 simpleLog_logL(SIMPLELOG_LEVEL_FINE, "JVM option %ul: %s", nOptions, tmpOptionString);
715 nOptions++;
716 } else {
717 free(tmpOptionString);
718 tmpOptionString = NULL;
719 }
720 }
721 }
722 vm_args->nOptions = nOptions;
723
724 FREE(dd_rw);
725 FREE(classPathOpt);
726 FREE(libraryPathOpt);
727 simpleLog_logL(SIMPLELOG_LEVEL_FINE, "");
728
729 return true;
730 }
731
732
733
java_reattachCurrentThread()734 static JNIEnv* java_reattachCurrentThread() {
735
736 JNIEnv* env = NULL;
737
738 simpleLog_logL(SIMPLELOG_LEVEL_FINEST, "Reattaching current thread...");
739 //const jint res = (*g_jvm)->AttachCurrentThreadAsDaemon(g_jvm, (void**) &env, NULL);
740 const jint res = (*g_jvm)->AttachCurrentThread(g_jvm, (void**) &env, NULL);
741 if (res != 0) {
742 env = NULL;
743 simpleLog_logL(SIMPLELOG_LEVEL_ERROR,
744 "Failed attaching JVM to current thread(2): %i - %s",
745 res, jniUtil_getJniRetValDescription(res));
746 }
747
748 return env;
749 }
750
java_getJNIEnv()751 static JNIEnv* java_getJNIEnv() {
752
753 JNIEnv* ret = NULL;
754
755 if (g_jvm == NULL) {
756 simpleLog_logL(SIMPLELOG_LEVEL_FINE, "Creating the JVM.");
757
758 JNIEnv* env = NULL;
759 JavaVM* jvm = NULL;
760 struct JavaVMInitArgs vm_args;
761 jint res = 0;
762
763 if (!java_createJavaVMInitArgs(&vm_args, jvmCfgProps)) {
764 simpleLog_logL(SIMPLELOG_LEVEL_ERROR,
765 "Failed initializing JVM init-arguments.");
766 goto end;
767 }
768
769 // Looking for existing JVMs might be problematic,
770 // cause they could be initialized with other
771 // JVM-arguments then we need.
772 // But as we can not use DestroyJavaVM, cause it makes
773 // creating a new one for hte same process impossible
774 // (a (SUN?) JVM limitation), we have to do it this way,
775 // to support /aireload and /aicontrol for Java Skirmish AIs.
776 simpleLog_logL(SIMPLELOG_LEVEL_FINE, "looking for existing JVMs ...");
777 jsize numJVMsFound = 0;
778
779 // jint JNI_GetCreatedJavaVMs(JavaVM **vmBuf, jsize bufLen, jsize *nVMs);
780 // Returns all Java VMs that have been created.
781 // Pointers to VMs are written in the buffer vmBuf,
782 // in the order they were created.
783 static const int maxVmsToFind = 1;
784 res = JNI_GetCreatedJavaVMs_f(&jvm, maxVmsToFind, &numJVMsFound);
785 if (res != 0) {
786 jvm = NULL;
787 simpleLog_logL(SIMPLELOG_LEVEL_ERROR,
788 "Failed fetching list of running JVMs: %i - %s",
789 res, jniUtil_getJniRetValDescription(res));
790 goto end;
791 }
792 simpleLog_logL(SIMPLELOG_LEVEL_FINE, "number of existing JVMs: %i", numJVMsFound);
793
794 if (numJVMsFound > 0) {
795 simpleLog_logL(SIMPLELOG_LEVEL_FINE, "using an already running JVM.");
796 } else {
797 simpleLog_logL(SIMPLELOG_LEVEL_FINE, "creating JVM...");
798 res = JNI_CreateJavaVM_f(&jvm, (void**) &env, &vm_args);
799 if (res != 0 || (*env)->ExceptionCheck(env)) {
800 simpleLog_logL(SIMPLELOG_LEVEL_ERROR,
801 "Failed to create Java VM: %i - %s",
802 res, jniUtil_getJniRetValDescription(res));
803 goto end;
804 }
805 }
806
807 // free the JavaVMInitArgs content
808 jint i;
809 for (i = 0; i < vm_args.nOptions; ++i) {
810 FREE(vm_args.options[i].optionString);
811 }
812 FREE(vm_args.options);
813
814 //res = (*jvm)->AttachCurrentThreadAsDaemon(jvm, (void**) &env, NULL);
815 res = (*jvm)->AttachCurrentThread(jvm, (void**) &env, NULL);
816 if (res < 0 || (*env)->ExceptionCheck(env)) {
817 if ((*env)->ExceptionCheck(env)) {
818 (*env)->ExceptionDescribe(env);
819 }
820 simpleLog_logL(SIMPLELOG_LEVEL_ERROR,
821 "Failed to attach JVM to current thread: %i - %s",
822 res, jniUtil_getJniRetValDescription(res));
823 goto end;
824 }
825
826 end:
827 if (env == NULL || jvm == NULL || (*env)->ExceptionCheck(env)
828 || res != 0) {
829 simpleLog_logL(SIMPLELOG_LEVEL_ERROR, "JVM: Failed creating.");
830 if (env != NULL && (*env)->ExceptionCheck(env)) {
831 (*env)->ExceptionDescribe(env);
832 }
833 if (jvm != NULL) {
834 // Never destroy the JVM, because it can not be created again
835 // for the same thread; would allways fail with return value -1
836 //res = (*jvm)->DestroyJavaVM(jvm);
837 }
838 g_jvm = NULL;
839 ret = NULL;
840 } else {
841 g_jvm = jvm;
842 ret = env;
843 }
844 } else {
845 ret = java_reattachCurrentThread();
846 }
847
848 return ret;
849 }
850
851
java_unloadJNIEnv()852 bool java_unloadJNIEnv() {
853
854 if (g_jvm != NULL) {
855 simpleLog_logL(SIMPLELOG_LEVEL_FINE, "JVM: Unloading ...");
856
857 //JNIEnv* env = java_getJNIEnv();
858
859 // We have to be the ONLY running thread (native and Java)
860 // this may not help, but will not hurt either
861 //jint res = (*g_jvm)->AttachCurrentThreadAsDaemon(g_jvm,
862 // (void**) &g_jniEnv, NULL);
863 //res = jvm->AttachCurrentThread((void**) & jniEnv, NULL);
864 /*if (res < 0 || (*g_jniEnv)->ExceptionCheck(g_jniEnv)) {
865 if ((*g_jniEnv)->ExceptionCheck(g_jniEnv)) {
866 (*g_jniEnv)->ExceptionDescribe(g_jniEnv);
867 }
868 simpleLog_logL(SIMPLELOG_LEVEL_ERROR,
869 "JVM: Can not Attach to the current thread: %i - %s",
870 res, jniUtil_getJniRetValDescription(res));
871 return false;
872 }*/
873
874 const jint res = (*g_jvm)->DetachCurrentThread(g_jvm);
875 if (res != 0) {
876 simpleLog_logL(SIMPLELOG_LEVEL_ERROR,
877 "JVM: Failed detaching current thread: %i - %s",
878 res, jniUtil_getJniRetValDescription(res));
879 return false;
880 }
881
882 // Never destroy the JVM, because it can not be created again
883 // for the same thread; would allways fail with return value -1,
884 // which would be a problem when using the /aicontrol or /aireload
885 // commands on java AIs
886 /*res = (*g_jvm)->DestroyJavaVM(g_jvm);
887 if (res != 0) {
888 simpleLog_logL(SIMPLELOG_LEVEL_ERROR,
889 "JVM: Failed destroying: %i - %s",
890 res, jniUtil_getJniRetValDescription(res));
891 return false;
892 } else {
893 simpleLog_logL(SIMPLELOG_LEVEL_NORMAL,
894 "JVM: Successfully destroyed");
895 g_jvm = NULL;
896 }*/
897 java_establishSpringEnv();
898 }
899
900 return true;
901 }
902
903
java_preloadJNIEnv()904 bool java_preloadJNIEnv() {
905
906 java_establishJavaEnv();
907 JNIEnv* env = java_getJNIEnv();
908 java_establishSpringEnv();
909
910 return env != NULL;
911 }
912
913
java_initStatic(int _interfaceId,const struct SAIInterfaceCallback * _callback)914 bool java_initStatic(int _interfaceId,
915 const struct SAIInterfaceCallback* _callback) {
916
917 interfaceId = _interfaceId;
918 callback = _callback;
919
920 // Read the jvm properties config file
921 jvmCfgProps = (struct Properties*) malloc(sizeof(struct Properties));
922 java_readJvmCfgFile(jvmCfgProps);
923
924 skirmishAIId_sizeMax = callback->SkirmishAIs_getSize(interfaceId);
925 skirmishAiImpl_sizeMax = skirmishAIId_sizeMax;
926 skirmishAiImpl_size = 0;
927
928 skirmishAIId_skirmishAiImpl = (size_t*) calloc(skirmishAIId_sizeMax, sizeof(size_t));
929 size_t t;
930 for (t = 0; t < skirmishAIId_sizeMax; ++t) {
931 skirmishAIId_skirmishAiImpl[t] = 999999;
932 }
933
934 skirmishAiImpl_className = (char**) calloc(skirmishAiImpl_sizeMax, sizeof(char*));
935 skirmishAiImpl_instance = (jobject*) calloc(skirmishAiImpl_sizeMax, sizeof(jobject));
936 skirmishAiImpl_classLoader = (jobject*) calloc(skirmishAiImpl_sizeMax, sizeof(jobject));
937 size_t sai;
938 for (sai = 0; sai < skirmishAiImpl_sizeMax; ++sai) {
939 skirmishAiImpl_className[sai] = NULL;
940 skirmishAiImpl_instance[sai] = NULL;
941 skirmishAiImpl_classLoader[sai] = NULL;
942 }
943
944 // dynamically load the JVM
945 char* jreLocationFile = callback->DataDirs_allocatePath(interfaceId,
946 JRE_LOCATION_FILE, false, false, false, false);
947
948 static const size_t jrePath_sizeMax = 1024;
949 char jrePath[jrePath_sizeMax];
950 bool jreFound = GetJREPath(jrePath, jrePath_sizeMax, jreLocationFile, NULL);
951 if (!jreFound) {
952 simpleLog_logL(SIMPLELOG_LEVEL_ERROR,
953 "Failed locating a JRE installation"
954 ", you may specify the JAVA_HOME env var.");
955 return false;
956 } else {
957 simpleLog_logL(SIMPLELOG_LEVEL_NORMAL,
958 "Using JRE (can be changed with JAVA_HOME): %s", jrePath);
959 }
960 FREE(jreLocationFile);
961
962 #if defined __arch64__
963 static const char* defJvmType = "server";
964 #else
965 static const char* defJvmType = "client";
966 #endif
967 const char* jvmType = java_getValueByKey(jvmCfgProps, "jvm.type");
968 if (jvmType == NULL) {
969 jvmType = defJvmType;
970 }
971
972 static const size_t jvmLibPath_sizeMax = 1024;
973 char jvmLibPath[jvmLibPath_sizeMax];
974 bool jvmLibFound = GetJVMPath(jrePath, jvmType, jvmLibPath,
975 jvmLibPath_sizeMax, NULL);
976 if (!jvmLibFound) {
977 simpleLog_logL(SIMPLELOG_LEVEL_ERROR,
978 "Failed locating the %s version of the JVM, please contact spring devs.", jvmType);
979 return false;
980 }
981
982 jvmSharedLib = sharedLib_load(jvmLibPath);
983 if (!sharedLib_isLoaded(jvmSharedLib)) {
984 simpleLog_logL(SIMPLELOG_LEVEL_ERROR,
985 "Failed to load the JVM at \"%s\".", jvmLibPath);
986 return false;
987 } else {
988 simpleLog_logL(SIMPLELOG_LEVEL_NORMAL,
989 "Successfully loaded the JVM shared library at \"%s\".", jvmLibPath);
990
991 JNI_GetDefaultJavaVMInitArgs_f = (JNI_GetDefaultJavaVMInitArgs_t*)
992 sharedLib_findAddress(jvmSharedLib, "JNI_GetDefaultJavaVMInitArgs");
993 if (JNI_GetDefaultJavaVMInitArgs_f == NULL) {
994 simpleLog_logL(SIMPLELOG_LEVEL_ERROR,
995 "Failed to load the JVM, function \"%s\" not exported.", "JNI_GetDefaultJavaVMInitArgs");
996 return false;
997 }
998
999 JNI_CreateJavaVM_f = (JNI_CreateJavaVM_t*)
1000 sharedLib_findAddress(jvmSharedLib, "JNI_CreateJavaVM");
1001 if (JNI_CreateJavaVM_f == NULL) {
1002 simpleLog_logL(SIMPLELOG_LEVEL_ERROR,
1003 "Failed to load the JVM, function \"%s\" not exported.", "JNI_CreateJavaVM");
1004 return false;
1005 }
1006
1007 JNI_GetCreatedJavaVMs_f = (JNI_GetCreatedJavaVMs_t*)
1008 sharedLib_findAddress(jvmSharedLib, "JNI_GetCreatedJavaVMs");
1009 if (JNI_GetCreatedJavaVMs_f == NULL) {
1010 simpleLog_logL(SIMPLELOG_LEVEL_ERROR,
1011 "Failed to load the JVM, function \"%s\" not exported.", "JNI_GetCreatedJavaVMs");
1012 return false;
1013 }
1014 }
1015
1016 java_establishJavaEnv();
1017 JNIEnv* env = java_getJNIEnv();
1018 bool success = (env != NULL);
1019 if (success) {
1020 const int res = eventsJniBridge_initStatic(env, skirmishAIId_sizeMax);
1021 success = (res == 0);
1022 }
1023 java_establishSpringEnv();
1024
1025 return success;
1026 }
1027
java_createAICallback(JNIEnv * env,const struct SSkirmishAICallback * aiCallback,int skirmishAIId)1028 static jobject java_createAICallback(JNIEnv* env, const struct SSkirmishAICallback* aiCallback, int skirmishAIId) {
1029
1030 jobject o_clb = NULL;
1031
1032 // initialize the AI Callback class, if not yet done
1033 if (g_cls_aiCallback == NULL) {
1034 // get the AI Callback class
1035 g_cls_aiCallback = jniUtil_findClass(env, CLS_AI_CALLBACK);
1036 if (g_cls_aiCallback == NULL) { return NULL; }
1037 g_cls_aiCallback = jniUtil_makeGlobalRef(env, g_cls_aiCallback,
1038 CLS_AI_CALLBACK);
1039 if (g_cls_aiCallback == NULL) { return NULL; }
1040
1041 // get (int skirmishAIId) constructor
1042 g_m_aiCallback_ctor_I = jniUtil_getMethodID(env, g_cls_aiCallback, "<init>", "(I)V");
1043 if (g_m_aiCallback_ctor_I == NULL) { return NULL; }
1044 }
1045
1046 o_clb = (*env)->NewObject(env, g_cls_aiCallback, g_m_aiCallback_ctor_I, skirmishAIId);
1047 if (jniUtil_checkException(env, "Failed creating Java AI Callback instance")) {
1048 o_clb = NULL;
1049 }/* else {
1050 o_clb = jniUtil_makeGlobalRef(env, o_clb, "AI callback instance");
1051 }*/
1052
1053 return o_clb;
1054 }
1055
1056
java_releaseStatic()1057 bool java_releaseStatic() {
1058
1059 FREE(skirmishAIId_skirmishAiImpl);
1060
1061 FREE(skirmishAiImpl_className);
1062 FREE(skirmishAiImpl_instance);
1063 FREE(skirmishAiImpl_classLoader);
1064
1065 sharedLib_unload(jvmSharedLib);
1066 jvmSharedLib = NULL;
1067
1068 FREE(jvmCfgProps->keys);
1069 FREE(jvmCfgProps->values);
1070 FREE(jvmCfgProps);
1071
1072 return true;
1073 }
1074
1075
java_loadSkirmishAI(JNIEnv * env,const char * shortName,const char * version,const char * className,jobject * o_ai,jobject * o_aiClassLoader)1076 static bool java_loadSkirmishAI(JNIEnv* env,
1077 const char* shortName, const char* version, const char* className,
1078 jobject* o_ai, jobject* o_aiClassLoader) {
1079
1080 /* // convert className from "com.myai.AI" to "com/myai/AI"
1081 const size_t classNameP_sizeMax = strlen(className) + 1;
1082 char classNameP[classNameP_sizeMax];
1083 STRCPY_T(classNameP, classNameP_sizeMax, className);
1084 util_strReplaceChar(classNameP, '.', '/');*/
1085
1086 // get the AIs private class-loader
1087 jobject o_global_aiClassLoader =
1088 java_createAIClassLoader(env, shortName, version);
1089 if (o_global_aiClassLoader == NULL) { return false; }
1090 *o_aiClassLoader = o_global_aiClassLoader;
1091
1092 // get the AI interface (from AIInterface.jar)
1093 if (g_cls_ai_int == NULL) {
1094 g_cls_ai_int = jniUtil_findClass(env, INT_AI);
1095 if (g_cls_ai_int == NULL) { return false; }
1096 g_cls_ai_int = jniUtil_makeGlobalRef(env, g_cls_ai_int, "AI interface class");
1097 if (g_cls_ai_int == NULL) { return false; }
1098 }
1099
1100 // get the AI implementation class (from SkirmishAI.jar)
1101 jclass cls_ai = jniUtil_findClassThroughLoader(env, o_global_aiClassLoader,
1102 className);
1103 if (cls_ai == NULL) { return false; }
1104
1105 const bool implementsAIInt = (bool) (*env)->IsAssignableFrom(env, cls_ai, g_cls_ai_int);
1106 bool hasException = (*env)->ExceptionCheck(env);
1107 if (!implementsAIInt || hasException) {
1108 simpleLog_logL(SIMPLELOG_LEVEL_ERROR,
1109 "AI class not assignable from interface "INT_AI": %s", className);
1110 simpleLog_logL(SIMPLELOG_LEVEL_ERROR, "possible reasons (this list could be incomplete):");
1111 simpleLog_logL(SIMPLELOG_LEVEL_ERROR, "* "INT_AI" interface not implemented");
1112 simpleLog_logL(SIMPLELOG_LEVEL_ERROR, "* The AI is not compiled for the Java AI Interface version in use");
1113 if (hasException) {
1114 (*env)->ExceptionDescribe(env);
1115 }
1116 return false;
1117 }
1118
1119 // get factory no-arg ctor
1120 jmethodID m_ai_ctor = jniUtil_getMethodID(env, cls_ai, "<init>", "()V");
1121 if (m_ai_ctor == NULL) { return false; }
1122
1123 // get AI instance
1124 jobject o_local_ai = (*env)->NewObject(env, cls_ai, m_ai_ctor);
1125 hasException = (*env)->ExceptionCheck(env);
1126 if (!o_local_ai || hasException) {
1127 simpleLog_logL(SIMPLELOG_LEVEL_ERROR,
1128 "Failed fetching AI instance for class: %s", className);
1129 if (hasException) {
1130 (*env)->ExceptionDescribe(env);
1131 }
1132 return false;
1133 }
1134
1135 // make the AI a global reference,
1136 // so it will not be garbage collected,
1137 // even after this method returned
1138 *o_ai = jniUtil_makeGlobalRef(env, o_local_ai, "AI instance");
1139
1140 return true;
1141 }
1142
1143
java_initSkirmishAIClass(const char * const shortName,const char * const version,const char * const className,int skirmishAIId)1144 bool java_initSkirmishAIClass(
1145 const char* const shortName,
1146 const char* const version,
1147 const char* const className,
1148 int skirmishAIId) {
1149
1150 bool success = false;
1151
1152 // see if an AI for className is instantiated already
1153 size_t sai;
1154 size_t firstFree = skirmishAiImpl_size;
1155 for (sai = 0; sai < skirmishAiImpl_size; ++sai) {
1156 if (skirmishAiImpl_className[sai] == NULL) {
1157 firstFree = sai;
1158 }
1159 }
1160 // sai is now either the instantiated one, or a free one
1161
1162 // instantiate AI (if needed)
1163 if (skirmishAiImpl_className[sai] == NULL) {
1164 sai = firstFree;
1165 java_establishJavaEnv();
1166 JNIEnv* env = java_getJNIEnv();
1167
1168 jobject instance = NULL;
1169 jobject classLoader = NULL;
1170
1171 success = java_loadSkirmishAI(env, shortName, version, className,
1172 &(instance), &(classLoader));
1173 java_establishSpringEnv();
1174
1175 if (success) {
1176 skirmishAiImpl_instance[sai] = instance;
1177 skirmishAiImpl_classLoader[sai] = classLoader;
1178 skirmishAiImpl_className[sai] = util_allocStrCpy(className);
1179 if (firstFree == skirmishAiImpl_size) {
1180 skirmishAiImpl_size++;
1181 }
1182 } else {
1183 simpleLog_logL(SIMPLELOG_LEVEL_ERROR,
1184 "Class loading failed for class: %s", className);
1185 }
1186 } else {
1187 success = true;
1188 }
1189
1190 if (success) {
1191 skirmishAIId_skirmishAiImpl[skirmishAIId] = sai;
1192 }
1193
1194 return success;
1195 }
1196
java_releaseSkirmishAIClass(const char * className)1197 bool java_releaseSkirmishAIClass(const char* className) {
1198
1199 bool success = false;
1200
1201 // see if an AI for className is instantiated
1202 size_t sai;
1203 for (sai = 0; sai < skirmishAiImpl_size; ++sai) {
1204 if ((skirmishAiImpl_className[sai] != NULL) &&
1205 (strcmp(skirmishAiImpl_className[sai], className) == 0)) {
1206 break;
1207 }
1208 }
1209 // sai is now either the instantiated one, or a free one
1210
1211 // release AI (if needed)
1212 if (skirmishAiImpl_className[sai] != NULL) {
1213 java_establishJavaEnv();
1214 JNIEnv* env = java_getJNIEnv();
1215
1216 bool successPart;
1217
1218 // delete the AI class-loader global reference,
1219 // so it will be garbage collected
1220 successPart = jniUtil_deleteGlobalRef(env,
1221 skirmishAiImpl_classLoader[sai], "AI class-loader");
1222 success = successPart;
1223
1224 // delete the AI global reference,
1225 // so it will be garbage collected
1226 successPart = jniUtil_deleteGlobalRef(env, skirmishAiImpl_instance[sai],
1227 "AI instance");
1228 success = success && successPart;
1229 java_establishSpringEnv();
1230
1231 if (success) {
1232 skirmishAiImpl_classLoader[sai] = NULL;
1233 skirmishAiImpl_instance[sai] = NULL;
1234 FREE(skirmishAiImpl_className[sai]);
1235 // if it is the last implementation
1236 if (sai+1 == skirmishAiImpl_size) {
1237 skirmishAiImpl_size--;
1238 }
1239 }
1240 }
1241
1242 return success;
1243 }
java_releaseAllSkirmishAIClasses()1244 bool java_releaseAllSkirmishAIClasses() {
1245
1246 bool success = true;
1247
1248 const char* className;
1249 size_t sai;
1250 for (sai = 0; sai < skirmishAiImpl_size; ++sai) {
1251 className = skirmishAiImpl_className[sai];
1252 if (className != NULL) {
1253 success = success && java_releaseSkirmishAIClass(className);
1254 }
1255 }
1256
1257 return success;
1258 }
1259
1260
java_skirmishAI_init(int skirmishAIId,const struct SSkirmishAICallback * aiCallback)1261 int java_skirmishAI_init(int skirmishAIId,
1262 const struct SSkirmishAICallback* aiCallback) {
1263
1264 int res = -1;
1265
1266 java_establishJavaEnv();
1267 JNIEnv* env = java_getJNIEnv();
1268 jobject global_javaAICallback = java_createAICallback(env, aiCallback, skirmishAIId);
1269 if (global_javaAICallback != NULL) {
1270 res = eventsJniBridge_initAI(env, skirmishAIId, global_javaAICallback);
1271 }
1272 java_establishSpringEnv();
1273
1274 return res;
1275 }
1276
java_skirmishAI_release(int skirmishAIId)1277 int java_skirmishAI_release(int skirmishAIId) {
1278
1279 int res = 0;
1280
1281 return res;
1282 }
1283
java_skirmishAI_handleEvent(int skirmishAIId,int topic,const void * data)1284 int java_skirmishAI_handleEvent(int skirmishAIId, int topic, const void* data) {
1285
1286 java_establishJavaEnv();
1287 JNIEnv* env = java_getJNIEnv();
1288 const size_t sai = skirmishAIId_skirmishAiImpl[skirmishAIId];
1289 jobject aiInstance = skirmishAiImpl_instance[sai];
1290 int res = eventsJniBridge_handleEvent(env, aiInstance, skirmishAIId, topic, data);
1291 java_establishSpringEnv();
1292
1293 return res;
1294 }
1295