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