/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /* $Rev$ $Date$ */ #ifndef tuscany_java_eval_hpp #define tuscany_java_eval_hpp /** * Java component implementation evaluation logic. */ #include #include "list.hpp" #include "value.hpp" namespace tuscany { namespace java { /** * Handle differences between various JNI APIs. */ #ifdef JAVA_HARMONY_VM #define JNI_VERSION JNI_VERSION_1_4 #else #define JNI_VERSION JNI_VERSION_1_6 #endif /** * Represent a Java VM runtime. */ jobject JNICALL nativeInvoke(JNIEnv *env, jobject self, jobject proxy, jobject method, jobjectArray args); jobject JNICALL nativeUUID(JNIEnv *env); class JavaRuntime { public: JavaRuntime() : h(*(new (gc_new()) handles())) { debug("java::javaruntime"); // Get existing JVM jsize nvms = 0; JNI_GetCreatedJavaVMs(&h.jvm, 1, &nvms); if (nvms == 0) { // Create a new JVM JavaVMInitArgs args; args.version = JNI_VERSION; args.ignoreUnrecognized = JNI_FALSE; JavaVMOption options[3]; args.options = options; args.nOptions = 0; // Configure classpath const char* const envcp = getenv("CLASSPATH"); const string cp = string("-Djava.class.path=") + (envcp == NULL? "." : envcp); options[args.nOptions].optionString = const_cast(c_str(cp)); options[args.nOptions++].extraInfo = NULL; #ifdef WANT_MAINTAINER_ASSERT // Enable assertions options[args.nOptions++].optionString = const_cast("-ea"); #endif // Configure Java debugging const char* const jpdaopts = getenv("JPDA_OPTS"); if (jpdaopts != NULL) { options[args.nOptions].optionString = const_cast(jpdaopts); options[args.nOptions++].extraInfo = NULL; } else { const char* const jpdaaddr = getenv("JPDA_ADDRESS"); if (jpdaaddr != NULL) { const string jpda = string("-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=") + jpdaaddr; options[args.nOptions].optionString = const_cast(c_str(jpda)); options[args.nOptions++].extraInfo = NULL; } } // Create the JVM #ifdef JAVA_HARMONY_VM JNI_CreateJavaVM(&h.jvm, &h.env, &args); #else JNI_CreateJavaVM(&h.jvm, (void**)&h.env, &args); #endif } else { // Just point to existing JVM h.jvm->GetEnv((void**)&h.env, JNI_VERSION); } // Lookup System classes and methods h.classClass = h.env->FindClass("java/lang/Class"); h.methodClass = h.env->FindClass("java/lang/reflect/Method"); h.objectClass = h.env->FindClass("java/lang/Object"); h.doubleClass = h.env->FindClass("java/lang/Double"); h.booleanClass = h.env->FindClass("java/lang/Boolean"); h.stringClass = h.env->FindClass("java/lang/String"); h.objectArrayClass = h.env->FindClass("[Ljava/lang/Object;"); h.iterableClass = h.env->FindClass("java/lang/Iterable"); h.classForName = h.env->GetStaticMethodID(h.classClass, "forName", "(Ljava/lang/String;ZLjava/lang/ClassLoader;)Ljava/lang/Class;"); h.doubleValueOf = h.env->GetStaticMethodID(h.doubleClass, "valueOf", "(D)Ljava/lang/Double;"); h.doubleValue = h.env->GetMethodID(h.doubleClass, "doubleValue", "()D"); h.booleanValueOf = h.env->GetStaticMethodID(h.booleanClass, "valueOf", "(Z)Ljava/lang/Boolean;"); h.booleanValue = h.env->GetMethodID(h.booleanClass, "booleanValue", "()Z"); h.declaredMethods = h.env->GetMethodID(h.classClass, "getDeclaredMethods", "()[Ljava/lang/reflect/Method;"); h.methodName = h.env->GetMethodID(h.methodClass, "getName", "()Ljava/lang/String;"); h.parameterTypes = h.env->GetMethodID(h.methodClass, "getParameterTypes", "()[Ljava/lang/Class;"); // Lookup Tuscany classes and methods h.loaderClass = h.env->FindClass("org/apache/tuscany/ClassLoader"); h.loaderValueOf = h.env->GetStaticMethodID(h.loaderClass, "valueOf", "(Ljava/lang/String;)Ljava/lang/ClassLoader;"); h.loaderForName = h.env->GetStaticMethodID(h.loaderClass, "forName", "(Ljava/lang/String;ZLjava/lang/ClassLoader;)Ljava/lang/Class;"); h.invokerClass = h.env->FindClass("org/apache/tuscany/InvocationHandler"); h.invokerValueOf = h.env->GetStaticMethodID(h.invokerClass, "valueOf", "(Ljava/lang/Class;J)Ljava/lang/Object;"); h.invokerStackTrace = h.env->GetStaticMethodID(h.invokerClass, "stackTrace", "(Ljava/lang/Throwable;)Ljava/lang/String;"); h.invokerLambda = h.env->GetFieldID(h.invokerClass, "lambda", "J"); h.iterableUtilClass = h.env->FindClass("org/apache/tuscany/IterableUtil"); h.iterableValueOf = h.env->GetStaticMethodID(h.iterableUtilClass, "list", "([Ljava/lang/Object;)Ljava/lang/Iterable;"); h.iterableIsNil = h.env->GetStaticMethodID(h.iterableUtilClass, "isNil", "(Ljava/lang/Object;)Z"); h.iterableCar = h.env->GetStaticMethodID(h.iterableUtilClass, "car", "(Ljava/lang/Object;)Ljava/lang/Object;"); h.iterableCdr = h.env->GetStaticMethodID(h.iterableUtilClass, "cdr", "(Ljava/lang/Object;)Ljava/lang/Iterable;"); h.uuidClass = h.env->FindClass("org/apache/tuscany/UUIDUtil"); // Register our native invocation handler function JNINativeMethod invokenm; invokenm.name = const_cast("invoke"); invokenm.signature = const_cast("(Ljava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;"); invokenm.fnPtr = (void*)nativeInvoke; h.env->RegisterNatives(h.invokerClass, &invokenm, 1); // Register our native UUID function JNINativeMethod uuidnm; uuidnm.name = const_cast("uuid"); uuidnm.signature = const_cast("()Ljava/lang/String;"); uuidnm.fnPtr = (void*)nativeUUID; h.env->RegisterNatives(h.uuidClass, &uuidnm, 1); } JavaRuntime(const JavaRuntime& c) : h(c.h) { } JavaRuntime& operator=(const JavaRuntime&) = delete; ~JavaRuntime() { } public: class handles { public: handles() { } public: friend class JavaRuntime; JavaVM* jvm; JNIEnv* env; jclass classClass; jclass methodClass; jclass objectClass; jclass doubleClass; jclass booleanClass; jclass stringClass; jclass objectArrayClass; jclass iterableClass; jmethodID doubleValueOf; jmethodID doubleValue; jmethodID booleanValueOf; jmethodID booleanValue; jmethodID declaredMethods; jmethodID methodName; jmethodID parameterTypes; jmethodID classForName; jclass loaderClass; jmethodID loaderValueOf; jmethodID loaderForName; jclass invokerClass; jmethodID invokerValueOf; jmethodID invokerStackTrace; jfieldID invokerLambda; jclass iterableUtilClass; jmethodID iterableValueOf; jmethodID iterableCar; jmethodID iterableCdr; jmethodID iterableIsNil; jclass uuidClass; }; handles& h; }; /** * Return the last exception that occurred in a JVM. */ const string lastException(const JavaRuntime& jr) { if (!jr.h.env->ExceptionCheck()) return "No Exception"; const jthrowable ex = jr.h.env->ExceptionOccurred(); const jstring trace = (jstring)jr.h.env->CallStaticObjectMethod(jr.h.invokerClass, jr.h.invokerStackTrace, ex); const char* const c = jr.h.env->GetStringUTFChars(trace, NULL); const string msg(c); jr.h.env->ReleaseStringUTFChars(trace, c); jr.h.env->ExceptionClear(); return msg; } /** * Declare conversion functions. */ const jobject valueToJobject(const JavaRuntime& jr, const value& jtype, const value& v); const value jobjectToValue(const JavaRuntime& jr, const jobject o); const jobjectArray valuesToJarray(const JavaRuntime& jr, const list& v); const list jarrayToValues(const JavaRuntime& jr, const jobjectArray o); const list jiterableToValues(const JavaRuntime& jr, const jobject o); /** * Convert a Java class name to a JNI class name. */ const bool jniClassNameHelper(char* const to, const char* const from) { if (*from == '\0') { *to = '\0'; return true; } *to = *from == '.'? '/' : *from; return jniClassNameHelper(to + 1, from + 1); } const string jniClassName(const string& from) { char buf[length(from) + 1]; jniClassNameHelper(buf, c_str(from)); return string(buf); } /** * Create a new Java object representing a lambda expression. */ class javaLambda { public: javaLambda(const JavaRuntime& jr, const value& iface, const lvvlambda& func) : jr(jr), iface(iface), func(func) { } const value operator()(const list& expr) const { if (isNil(expr)) return func(expr); const value& op(car(expr)); if (op == "equals") return value(cadr(expr) == this); if (op == "hashCode") return value((double)(long)this); if (op == "toString") { ostringstream os; os << this; return value(string("org.apache.tuscany.InvocationHandler@") + (c_str(str(os)) + 2)); } return func(expr); } public: const JavaRuntime jr; const value iface; const lvvlambda func; }; /** * Native implementation of the InvocationHandler.invoke Java method. * Dispatches the call to the lambda function wrapped in the invocation handler. */ jobject JNICALL nativeInvoke(JNIEnv* env, jobject self, unused jobject proxy, jobject method, jobjectArray args) { // Retrieve the lambda function from the invocation handler const jclass clazz = env->GetObjectClass(self); const jfieldID f = env->GetFieldID(clazz, "lambda", "J"); const javaLambda& jl = *(javaLambda*)(long)env->GetLongField(self, f); // Retrieve the function name const jstring s = (jstring)env->CallObjectMethod(method, jl.jr.h.methodName); const char* c = env->GetStringUTFChars(s, NULL); const value func(c); env->ReleaseStringUTFChars(s, c); // Build the expression to evaluate, either (func, args[0], args[1], args[2]...) // or just args[0] for the special eval(...) function const list expr = func == "eval"? (list)car(jarrayToValues(jl.jr, args)) : cons(func, jarrayToValues(jl.jr, args)); debug(expr, "java::nativeInvoke::expr"); // Invoke the lambda function value result = jl(expr); debug(result, "java::nativeInvoke::result"); // Convert result to a jobject return valueToJobject(jl.jr, nilValue, result); } /** * Native implementation of IterableUtil.uuid. We are providing a native implementation * of this function as java.util.UUID seems to behave differently with different JDKs. */ jobject JNICALL nativeUUID(JNIEnv* env) { const value uuid = mkuuid(); return env->NewStringUTF(c_str(uuid)); } /** * Convert a lambda function to Java proxy. */ const jobject mkJavaLambda(const JavaRuntime& jr, unused const value& iface, const lvvlambda& l) { const gc_ptr jl = new (gc_new()) javaLambda(jr, iface, l); jclass jc = (jclass)(long)(double)iface; const jobject obj = jr.h.env->CallStaticObjectMethod(jr.h.invokerClass, jr.h.invokerValueOf, jc, (long)(javaLambda*)jl); return obj; } /** * Convert a list of values to a Java jobjectArray. */ const jobjectArray valuesToJarrayHelper(const JavaRuntime& jr, jobjectArray a, const list& v, const int i) { if (isNil(v)) return a; jr.h.env->SetObjectArrayElement(a, i, valueToJobject(jr, nilValue, car(v))); return valuesToJarrayHelper(jr, a, cdr(v), i + 1); } const jobjectArray valuesToJarray(const JavaRuntime& jr, const list& v) { jobjectArray a = jr.h.env->NewObjectArray((jsize)length(v), jr.h.objectClass, NULL); return valuesToJarrayHelper(jr, a, v, 0); } /** * Convert a Java jobjectArray to a Java iterable. */ const jobject jarrayToJiterable(const JavaRuntime& jr, const jobjectArray a) { return jr.h.env->CallStaticObjectMethod(jr.h.iterableClass, jr.h.iterableValueOf, a); } /** * Convert a value to a Java jobject. */ const jobject valueToJobject(const JavaRuntime& jr, const value& jtype, const value& v) { switch (type(v)) { case value::List: return jarrayToJiterable(jr, valuesToJarray(jr, v)); case value::Lambda: return mkJavaLambda(jr, jtype, v); case value::Symbol: return jr.h.env->NewStringUTF(c_str(string("'") + (string)v)); case value::String: return jr.h.env->NewStringUTF(c_str(v)); case value::Number: return jr.h.env->CallStaticObjectMethod(jr.h.doubleClass, jr.h.doubleValueOf, (double)v); case value::Bool: return jr.h.env->CallStaticObjectMethod(jr.h.booleanClass, jr.h.booleanValueOf, (bool)v); default: return NULL; } } /** * Convert a list of values to an array of jvalues. */ const jvalue* valuesToJvaluesHelper(const JavaRuntime& jr, jvalue* const a, const list& types, const list& v) { if (isNil(v)) return a; a->l = valueToJobject(jr, car(types), car(v)); return valuesToJvaluesHelper(jr, a + 1, cdr(types), cdr(v)); } const jvalue* valuesToJvalues(const JavaRuntime& jr, const list& types, const list& v) { const size_t n = length(v); jvalue* const a = new (gc_anew(n)) jvalue[n]; valuesToJvaluesHelper(jr, a, types, v); return a; } /** * Convert a Java jobjectArray to a list of values. */ const list jarrayToValuesHelper(const JavaRuntime& jr, const jobjectArray a, const int i, const int size) { if (i == size) return nilListValue; return cons(jobjectToValue(jr, jr.h.env->GetObjectArrayElement(a, i)), jarrayToValuesHelper(jr, a, i + 1, size)); } const list jarrayToValues(const JavaRuntime& jr, const jobjectArray o) { if (o == NULL) return nilListValue; return jarrayToValuesHelper(jr, o, 0, jr.h.env->GetArrayLength(o)); } /** * Convert a Java Iterable to a list of values. */ const list jiterableToValuesHelper(const JavaRuntime& jr, const jobject o) { if ((bool)jr.h.env->CallStaticBooleanMethod(jr.h.iterableUtilClass, jr.h.iterableIsNil, o)) return nilListValue; const jobject car = jr.h.env->CallStaticObjectMethod(jr.h.iterableUtilClass, jr.h.iterableCar, o); const jobject cdr = jr.h.env->CallStaticObjectMethod(jr.h.iterableUtilClass, jr.h.iterableCdr, o); return cons(jobjectToValue(jr, car), jiterableToValuesHelper(jr, cdr)); } const list jiterableToValues(const JavaRuntime& jr, const jobject o) { if (o == NULL) return nilListValue; return jiterableToValuesHelper(jr, o); } /** * Convert a Java jobject to a value. */ const value jobjectToValue(const JavaRuntime& jr, const jobject o) { if (o == NULL) return nilValue; const jclass clazz = jr.h.env->GetObjectClass(o); if ((jr.h.env->IsSameObject(clazz, jr.h.stringClass))) { const char* const s = jr.h.env->GetStringUTFChars((jstring)o, NULL); if (*s == '\'') { const value v(s + 1); jr.h.env->ReleaseStringUTFChars((jstring)o, s); return v; } const value v = string(s); jr.h.env->ReleaseStringUTFChars((jstring)o, s); return v; } if (jr.h.env->IsSameObject(clazz, jr.h.booleanClass)) return value((bool)jr.h.env->CallBooleanMethod(o, jr.h.booleanValue)); if (jr.h.env->IsSameObject(clazz, jr.h.doubleClass)) return value((double)jr.h.env->CallDoubleMethod(o, jr.h.doubleValue)); if (jr.h.env->IsAssignableFrom(clazz, jr.h.iterableClass)) return jiterableToValues(jr, o); if (jr.h.env->IsAssignableFrom(clazz, jr.h.objectArrayClass)) return jarrayToValues(jr, (jobjectArray)o); const lvvlambda javaCallable = [jr](const list& params) -> const value { jobjectArray jargs = valuesToJarray(jr, params); jobject result = jargs; //CallObject(func, jargs); return jobjectToValue(jr, result); }; return value(javaCallable); } /** * Returns a balanced tree of the methods of a class. */ const value parameterTypeToValue(const jobject t) { return value((double)(long)t); } const list parameterTypesToValues(const JavaRuntime& jr, const jobjectArray t, const int i) { if (i == 0) return nilListValue; return cons(parameterTypeToValue(jr.h.env->GetObjectArrayElement(t, i - 1)), parameterTypesToValues(jr, t, i - 1)); } const value methodToValue(const JavaRuntime& jr, const jobject m) { const jobject s = jr.h.env->CallObjectMethod(m, jr.h.methodName); const char* const c = jr.h.env->GetStringUTFChars((jstring)s, NULL); const string& name = string(c); jr.h.env->ReleaseStringUTFChars((jstring)s, c); const jmethodID mid = jr.h.env->FromReflectedMethod(m); const jobjectArray t = (jobjectArray)jr.h.env->CallObjectMethod(m, jr.h.parameterTypes); const list types = reverse(parameterTypesToValues(jr, t, jr.h.env->GetArrayLength(t))); return cons(c_str(name), cons((double)(long)mid, types)); } const list methodsToValues(const JavaRuntime& jr, const jobjectArray m, const int i) { if (i == 0) return nilListValue; return cons(methodToValue(jr, jr.h.env->GetObjectArrayElement(m, i - 1)), methodsToValues(jr, m, i - 1)); } const list methodsToValues(const JavaRuntime& jr, const jclass clazz) { const jobjectArray m = (jobjectArray)jr.h.env->CallObjectMethod(clazz, jr.h.declaredMethods); return methodsToValues(jr, m, jr.h.env->GetArrayLength(m)); } /** * Represents a Java Class. */ class JavaClass { public: JavaClass() : loader(NULL), clazz(NULL), obj(NULL) { } JavaClass(const jobject loader, const jclass clazz, const jobject obj, const list m) : loader(loader), clazz(clazz), obj(obj), m(m) { } const jobject loader; const jclass clazz; const jobject obj; const list m; }; /** * Read a class. */ const failable readClass(const JavaRuntime& jr, const string& path, const string& name) { // Create a class loader from the given path const jobject jpath = jr.h.env->NewStringUTF(c_str(path)); const jobject loader = jr.h.env->CallStaticObjectMethod(jr.h.loaderClass, jr.h.loaderValueOf, jpath); // Load the class const jobject jname = jr.h.env->NewStringUTF(c_str(name)); const jclass clazz = (jclass)jr.h.env->CallStaticObjectMethod(jr.h.loaderClass, jr.h.loaderForName, jname, JNI_TRUE, loader); if (clazz == NULL) return mkfailure(string("Couldn't load class: ") + name + " : " + lastException(jr)); // Create an instance const jmethodID constr = jr.h.env->GetMethodID(clazz, "", "()V"); if (constr == NULL) return mkfailure(string("Couldn't find constructor: ") + name + " : " + lastException(jr)); const jobject obj = jr.h.env->NewObject(clazz, constr); if (obj == NULL) return mkfailure(string("Couldn't construct object: ") + name + " : " + lastException(jr)); return JavaClass(loader, clazz, obj, methodsToValues(jr, clazz)); } /** * Evaluate an expression against a Java class. */ const failable evalClass(const JavaRuntime& jr, const value& expr, const JavaClass jc) { debug(expr, "java::evalClass::expr"); // Lookup the Java function named as the expression operand const list func = assoc(car(expr), jc.m); if (isNil(func)) { // The start, stop, and restart functions are optional const value fn = car(expr); if (fn == "start" || fn == "stop") return value(lvvlambda()); return mkfailure(string("Couldn't find function: ") + (string)car(expr) + " : " + lastException(jr)); } const jmethodID fid = (jmethodID)(long)(double)cadr(func); // Convert args to Java jvalues const jvalue* const args = valuesToJvalues(jr, cddr(func), cdr(expr)); // Call the Java function const jobject result = jr.h.env->CallObjectMethodA(jc.obj, fid, const_cast(args)); if (result == NULL) return mkfailure(string("Function call failed: ") + (string)car(expr) + " : " + lastException(jr)); // Convert Java result to a value const value v = jobjectToValue(jr, result); debug(v, "java::evalClass::result"); return v; } } } #endif /* tuscany_java_eval_hpp */