-
Notifications
You must be signed in to change notification settings - Fork 604
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Java binding using the existing SWIG infrastructure
CMakeLists.txt: Add NLOPT_JAVA option. If set, check for C++, JNI, and Java. src/swig/nlopt-java.i: New SWIG input file (not standalone, but to be included into nlopt.i). Contains the Java-specific declarations. src/swig/nlopt.i: Add syntax highlighting declaration for KatePart-based editors. Instantiate vector<double> as doublevector instead of nlopt_doublevector for SWIGJAVA, because everything is in an nlopt package in Java, so the nlopt_ part is redundant. Ignore nlopt_get_initial_step because at least for Java, SWIG wants to generate a binding for it, using SWIGTYPE_p_* types that cannot be practically used. (It is in-place just like nlopt::opt::get_initial_step, which is already ignored.) For SWIGJAVA, %include nlopt-java.i. src/swig/CMakeLists.txt: Set UseSWIG_MODULE_VERSION to 2 so that UseSWIG cleans up old generated source files before running SWIG, useful for Java. (In particular, it prevents obsolete .java files from old .i file versions, which might not even compile anymore, from being included in the Java compilation and the JAR.) If JNI, Java, and SWIG were found, generate the Java binding with SWIG, and compile the JNI library with the C++ compiler and the Java JAR with the Java compiler. src/swig/glob_java.cmake: New helper script whose only purpose is to invoke the CMake file(GLOB ...) command at the correct stage of the build process, after the SWIG run, not when CMake is run on the main CMakeLists.txt, because the latter happens before anything at all is built. The script is invoked through cmake -P by an add_custom_command in CMakeLists.txt, whose dependencies order it to the correct spot of the build. This is the only portable way to automatically determine which *.java files SWIG has generated from the *.i files. The result is written to java_sources.txt, which is dynamically read by the add_jar command thanks to the @ indirection. test/t_java.java: New test. Java port of t_tutorial.cxx/t_python.py. test/CMakeLists.txt: If JNI, Java >= 1.8, and SWIG were found, run the t_java test program with the algorithms 23 (MMA), 24 (COBYLA), 30 (augmented Lagrangian), and 39 (SLSQP). All 4 tests pass. (Java < 1.8 should be supported by the binding itself, but not by the test.) This code is mostly unrelated to the old unmaintained nlopt4j binding (https://github.com/dibyendumajumdar/nlopt4j), which used handwritten JNI code. Instead, it reuses the existing SWIG binding infrastructure, adding Java as a supported language to that. Only the code in the func_java and mfunc_java wrappers is loosely inspired by the nlopt4j code. The rest of the code in this binding relies mostly on SWIG features and uses very little handwritten JNI code, so nlopt4j was not useful as a reference for it. This binding is also backwards-incompatible with nlopt4j due to differing naming conventions. Note that this binding maps the C++ class and method names identically to Java. As a result, it does not use idiomatic Java case conventions, but uses snake_case for both class and method names instead of the usual UpperCamelCase for class names and lowerCamelCase for method names in Java. The C++ namespace nlopt is mapped to the Java package nlopt.
- Loading branch information
Showing
7 changed files
with
351 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
# This file(GLOB ...) must run at build (make) time, after the SWIG run. So it | ||
# cannot be invoked directly from CMakeLists.txt, but must be invoked through | ||
# cmake -P at the correct spot of the build, using add_custom_command. | ||
file(GLOB JAVA_SOURCES ${BINARY_DIR}/java/nlopt/*.java) | ||
list(JOIN JAVA_SOURCES "\n" JAVA_SOURCES_LINES) | ||
file(WRITE ${BINARY_DIR}/java_sources.txt ${JAVA_SOURCES_LINES}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,220 @@ | ||
// -*- C++ -*- | ||
// kate: hl c++ | ||
|
||
// use proper Java enums | ||
%include "enums.swg" | ||
// use Java code for the constants in the enums instead of calling a C function | ||
%javaconst(1); | ||
|
||
// pointer-based API not supported, use version_{major,minor,bugfix} instead | ||
%ignore version; | ||
// pointer-based API not supported, use the other overload instead | ||
%ignore optimize(std::vector<double> &, double &); | ||
// unsupported function APIs, use the ones with nlopt_munge instead | ||
%ignore set_min_objective(func, void *); | ||
%ignore set_min_objective(vfunc, void *); | ||
%ignore set_min_objective(functor_type); | ||
%ignore set_max_objective(func, void *); | ||
%ignore set_max_objective(vfunc, void *); | ||
%ignore set_max_objective(functor_type); | ||
%ignore add_inequality_constraint(func, void *); | ||
%ignore add_inequality_constraint(func, void *, double); | ||
%ignore add_inequality_constraint(vfunc, void *); | ||
%ignore add_inequality_constraint(vfunc, void *, double); | ||
%ignore add_inequality_mconstraint(mfunc, void *, const std::vector<double> &); | ||
%ignore add_equality_constraint(func, void *); | ||
%ignore add_equality_constraint(func, void *, double); | ||
%ignore add_equality_constraint(vfunc, void *); | ||
%ignore add_equality_constraint(vfunc, void *, double); | ||
%ignore add_equality_mconstraint(mfunc, void *, const std::vector<double> &); | ||
|
||
// Munge function types | ||
%extend nlopt::opt { | ||
%proxycode { | ||
public static interface func { | ||
public double apply(double[] x, double[] gradient); | ||
} | ||
|
||
public static interface mfunc { | ||
public double[] apply(double[] x, double[] gradient); | ||
} | ||
} | ||
} | ||
|
||
%{ | ||
struct jfunc { | ||
JNIEnv *jenv; | ||
jobject func; | ||
jmethodID method; | ||
}; | ||
|
||
static void *free_jfunc(void *p) { | ||
((jfunc *) p)->jenv->DeleteGlobalRef(((jfunc *) p)->func); | ||
delete (jfunc *) p; | ||
return (void *) 0; | ||
} | ||
|
||
static void *dup_jfunc(void *p) { | ||
jfunc *q = new jfunc; | ||
q->jenv = ((jfunc *) p)->jenv; | ||
q->func = q->jenv->NewGlobalRef(((jfunc *) p)->func); | ||
q->method = ((jfunc *) p)->method; | ||
return (void *) q; | ||
} | ||
|
||
static double func_java(unsigned n, const double *x, double *grad, void *f) | ||
{ | ||
JNIEnv *jenv = ((jfunc *) f)->jenv; | ||
jobject func = ((jfunc *) f)->func; | ||
jmethodID method = ((jfunc *) f)->method; | ||
|
||
jdoubleArray jx = jenv->NewDoubleArray(n); | ||
if (!jx || jenv->ExceptionCheck()) { | ||
throw nlopt::forced_stop(); | ||
} | ||
jenv->SetDoubleArrayRegion(jx, 0, n, x); | ||
jdoubleArray jgrad = (jdoubleArray) 0; | ||
if (grad) { | ||
jgrad = jenv->NewDoubleArray(n); | ||
if (!jgrad || jenv->ExceptionCheck()) { | ||
jenv->DeleteLocalRef(jx); | ||
throw nlopt::forced_stop(); | ||
} | ||
jenv->SetDoubleArrayRegion(jgrad, 0, n, grad); | ||
} | ||
|
||
jdouble res = jenv->CallDoubleMethod(func, method, jx, jgrad); | ||
jenv->DeleteLocalRef(jx); | ||
|
||
if (jenv->ExceptionCheck()) { | ||
if (jgrad) { | ||
jenv->DeleteLocalRef(jgrad); | ||
} | ||
throw nlopt::forced_stop(); | ||
} | ||
|
||
if (grad) { | ||
jenv->GetDoubleArrayRegion(jgrad, 0, n, grad); | ||
jenv->DeleteLocalRef(jgrad); | ||
} | ||
|
||
return res; | ||
} | ||
|
||
static void mfunc_java(unsigned m, double *result, | ||
unsigned n, const double *x, double *grad, void *f) | ||
{ | ||
JNIEnv *jenv = ((jfunc *) f)->jenv; | ||
jobject func = ((jfunc *) f)->func; | ||
jmethodID method = ((jfunc *) f)->method; | ||
|
||
jdoubleArray jx = jenv->NewDoubleArray(n); | ||
if (!jx || jenv->ExceptionCheck()) { | ||
throw nlopt::forced_stop(); | ||
} | ||
jenv->SetDoubleArrayRegion(jx, 0, n, x); | ||
jdoubleArray jgrad = (jdoubleArray) 0; | ||
if (grad) { | ||
jgrad = jenv->NewDoubleArray(m * n); | ||
if (!jgrad || jenv->ExceptionCheck()) { | ||
jenv->DeleteLocalRef(jx); | ||
throw nlopt::forced_stop(); | ||
} | ||
jenv->SetDoubleArrayRegion(jgrad, 0, m * n, grad); | ||
} | ||
|
||
jdoubleArray res = (jdoubleArray) jenv->CallObjectMethod(func, method, jx, jgrad); | ||
jenv->DeleteLocalRef(jx); | ||
|
||
if (!res || jenv->ExceptionCheck()) { | ||
if (jgrad) { | ||
jenv->DeleteLocalRef(jgrad); | ||
} | ||
if (res) { | ||
jenv->DeleteLocalRef(res); | ||
} | ||
throw nlopt::forced_stop(); | ||
} | ||
|
||
jenv->GetDoubleArrayRegion(res, 0, m, result); | ||
jenv->DeleteLocalRef(res); | ||
|
||
if (grad) { | ||
jenv->GetDoubleArrayRegion(jgrad, 0, m * n, grad); | ||
jenv->DeleteLocalRef(jgrad); | ||
} | ||
} | ||
%} | ||
|
||
%typemap(jni)(nlopt::func f, void *f_data, nlopt_munge md, nlopt_munge mc) "jobject" | ||
%typemap(jtype)(nlopt::func f, void *f_data, nlopt_munge md, nlopt_munge mc) "java.lang.Object" | ||
%typemap(jstype)(nlopt::func f, void *f_data, nlopt_munge md, nlopt_munge mc) "func" | ||
%typemap(in)(nlopt::func f, void *f_data, nlopt_munge md, nlopt_munge mc) { | ||
$1 = func_java; | ||
jfunc jf = {jenv, $input, jenv->GetMethodID(jenv->FindClass("nlopt/opt$func"), "apply", "([D[D)D")}; | ||
$2 = dup_jfunc((void *) &jf); | ||
$3 = free_jfunc; | ||
$4 = dup_jfunc; | ||
} | ||
%typemap(javain)(nlopt::func f, void *f_data, nlopt_munge md, nlopt_munge mc) "$javainput" | ||
|
||
%typemap(jni)(nlopt::mfunc mf, void *f_data, nlopt_munge md, nlopt_munge mc) "jobject" | ||
%typemap(jtype)(nlopt::mfunc mf, void *f_data, nlopt_munge md, nlopt_munge mc) "java.lang.Object" | ||
%typemap(jstype)(nlopt::mfunc mf, void *f_data, nlopt_munge md, nlopt_munge mc) "mfunc" | ||
%typemap(in)(nlopt::mfunc mf, void *f_data, nlopt_munge md, nlopt_munge mc) { | ||
$1 = mfunc_java; | ||
jfunc jf = {jenv, $input, jenv->GetMethodID(jenv->FindClass("nlopt/opt$mfunc"), "apply", "([D[D)[D")}; | ||
$2 = dup_jfunc((void *) &jf); | ||
$3 = free_jfunc; | ||
$4 = dup_jfunc; | ||
} | ||
%typemap(javain)(nlopt::mfunc mf, void *f_data, nlopt_munge md, nlopt_munge mc) "$javainput" | ||
|
||
// Make exception classes Java-compliant | ||
%typemap(javabase) nlopt::forced_stop "java.lang.RuntimeException" | ||
%typemap(javabody) nlopt::forced_stop "" | ||
%typemap(javadestruct) nlopt::forced_stop "" | ||
%typemap(javafinalize) nlopt::forced_stop "" | ||
%ignore nlopt::forced_stop::forced_stop; | ||
%extend nlopt::forced_stop { | ||
%proxycode { | ||
public forced_stop(String message) { | ||
super(message); | ||
} | ||
} | ||
} | ||
%typemap(javabase) nlopt::roundoff_limited "java.lang.RuntimeException" | ||
%typemap(javabody) nlopt::roundoff_limited "" | ||
%typemap(javadestruct) nlopt::roundoff_limited "" | ||
%typemap(javafinalize) nlopt::roundoff_limited "" | ||
%ignore nlopt::roundoff_limited::roundoff_limited; | ||
%extend nlopt::roundoff_limited { | ||
%proxycode { | ||
public roundoff_limited(String message) { | ||
super(message); | ||
} | ||
} | ||
} | ||
|
||
// Map exceptions | ||
%typemap(throws) std::bad_alloc %{ | ||
SWIG_JavaThrowException(jenv, SWIG_JavaOutOfMemoryError, $1.what()); | ||
return $null; | ||
%} | ||
|
||
%typemap(throws) nlopt::forced_stop %{ | ||
jenv->ExceptionClear(); | ||
jclass excep = jenv->FindClass("nlopt/forced_stop"); | ||
if (excep) | ||
jenv->ThrowNew(excep, $1.what()); | ||
return $null; | ||
%} | ||
|
||
%typemap(throws) nlopt::roundoff_limited %{ | ||
jenv->ExceptionClear(); | ||
jclass excep = jenv->FindClass("nlopt/roundoff_limited"); | ||
if (excep) | ||
jenv->ThrowNew(excep, $1.what()); | ||
return $null; | ||
%} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.