| /* | 
 |  * Copyright 2008 Google Inc. | 
 |  *  | 
 |  * Licensed 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. | 
 |  */ | 
 |  | 
 | #include <string> | 
 | #include <sstream> | 
 | #include "jni.h" | 
 | #include "java-dispatch.h" | 
 | #include "trace.h" | 
 | #include "JStringWrap.h" | 
 |  | 
 | namespace gwt { | 
 |  | 
 |   /* | 
 |    * Declarations for private functions. | 
 |    */ | 
 |   JSClassRef DispatchObjectClassCreate(); | 
 |  | 
 |   JSClassRef DispatchMethodClassCreate(); | 
 |  | 
 |   JSValueRef DispatchObjectGetProperty(JSContextRef, JSObjectRef, JSStringRef, | 
 |                                        JSValueRef*); | 
 |  | 
 |   JSValueRef DispatchObjectToString(JSContextRef, JSObjectRef, JSObjectRef, | 
 |                                     size_t, const JSValueRef*, JSValueRef*); | 
 |  | 
 |   bool DispatchObjectSetProperty(JSContextRef, JSObjectRef, JSStringRef, | 
 |                                  JSValueRef, JSValueRef*); | 
 |  | 
 |   void DispatchObjectFinalize(JSObjectRef); | 
 |  | 
 |   JSValueRef DispatchMethodCallAsFunction(JSContextRef, JSObjectRef, | 
 |                                           JSObjectRef, size_t, | 
 |                                           const JSValueRef*, JSValueRef*); | 
 |  | 
 |   JSValueRef DispatchMethodGetToString(JSContextRef, JSObjectRef, JSStringRef, | 
 |                                        JSValueRef*); | 
 |  | 
 |   JSValueRef DispatchMethodToString(JSContextRef, JSObjectRef, JSObjectRef, | 
 |                                     size_t, const JSValueRef*, JSValueRef*); | 
 |  | 
 |   void DispatchMethodFinalize(JSObjectRef); | 
 |  | 
 |   /* | 
 |    * Call this when an underlying Java Object should be freed. | 
 |    */ | 
 |   void ReleaseJavaObject(jobject jObject); | 
 |  | 
 |  | 
 |   /* | 
 |    * The class definition stuct for DispatchObjects. | 
 |    */ | 
 |   static JSClassDefinition _dispatchObjectClassDef = { 0, | 
 |       kJSClassAttributeNone, "DispatchObject", 0, 0, 0, 0, | 
 |       DispatchObjectFinalize, 0, DispatchObjectGetProperty, | 
 |       DispatchObjectSetProperty, 0, 0, 0, 0, 0, 0 }; | 
 |  | 
 |   /* | 
 |    * The class definition structs for DispatchMethods. | 
 |    */ | 
 |   static JSStaticValue _dispatchMethodStaticValues[] = { | 
 |     { "toString", DispatchMethodGetToString, 0, kJSPropertyAttributeNone }, | 
 |     { 0, 0, 0, 0 } | 
 |   }; | 
 |   static JSClassDefinition _dispatchMethodClassDef = { 0, | 
 |       kJSClassAttributeNoAutomaticPrototype, "DispatchMethod", 0, | 
 |       _dispatchMethodStaticValues, 0, 0, DispatchMethodFinalize, 0, 0, 0, 0, | 
 |       0, DispatchMethodCallAsFunction, 0, 0, 0 }; | 
 |  | 
 |   /* | 
 |    * The classes used to create DispatchObjects and DispatchMethods. | 
 |    */ | 
 |   static JSClassRef _dispatchObjectClass = DispatchObjectClassCreate(); | 
 |   static JSClassRef _dispatchMethodClass = DispatchMethodClassCreate(); | 
 |  | 
 |   /* | 
 |    * Java class and method references needed to do delegation. | 
 |    */ | 
 |    | 
 |   /* | 
 |    * The main JVM, used by foreign threads to attach. | 
 |    */ | 
 |   static JavaVM* _javaVM; | 
 |  | 
 |   /* | 
 |    * Only valid for the main thread!  WebKit can finalized on a foreign thread. | 
 |    */ | 
 |   static JNIEnv* _javaEnv; | 
 |  | 
 |   static jclass _javaDispatchObjectClass; | 
 |   static jclass _javaDispatchMethodClass; | 
 |   static jclass _lowLevelSafClass; | 
 |   static jmethodID _javaDispatchObjectSetFieldMethod; | 
 |   static jmethodID _javaDispatchObjectGetFieldMethod; | 
 |   static jmethodID _javaDispatchMethodInvokeMethod; | 
 |   static jmethodID _javaDispatchObjectToStringMethod; | 
 |   static jmethodID _lowLevelSafReleaseObject; | 
 |  | 
 |   /* | 
 |    * Structure to hold DispatchMethod private data. | 
 |    * | 
 |    * NOTE: utf8Name is defensively copied. | 
 |    */ | 
 |   class DispatchMethodData { | 
 |    public: | 
 |     DispatchMethodData(jobject jObject, std::string& utf8Name) | 
 |         : _jObject(jObject), _utf8Name(utf8Name) { } | 
 |     ~DispatchMethodData() { | 
 |       ReleaseJavaObject(_jObject); | 
 |     } | 
 |     jobject _jObject; | 
 |     std::string _utf8Name; | 
 |   }; | 
 |  | 
 | /* | 
 |  * The following takes the prototype from the Function constructor, this allows | 
 |  * us to easily support call and apply on our objects that support CallAsFunction. | 
 |  * | 
 |  * NOTE: The return value is not protected. | 
 |  */ | 
 | JSValueRef GetFunctionPrototype(JSContextRef jsContext, JSValueRef* exception) { | 
 |   TR_ENTER(); | 
 |   JSObjectRef globalObject = JSContextGetGlobalObject(jsContext); | 
 |   JSStringRef fnPropName= JSStringCreateWithUTF8CString("Function"); | 
 |   JSValueRef fnCtorValue = JSObjectGetProperty(jsContext, globalObject, | 
 |       fnPropName, exception); | 
 |   JSStringRelease(fnPropName); | 
 |   if (!fnCtorValue) { | 
 |     return JSValueMakeUndefined(jsContext); | 
 |   } | 
 |  | 
 |   JSObjectRef fnCtorObject = JSValueToObject(jsContext, fnCtorValue, exception); | 
 |   if (!fnCtorObject) { | 
 |     return JSValueMakeUndefined(jsContext); | 
 |   } | 
 |  | 
 |   JSStringRef protoPropName = JSStringCreateWithUTF8CString("prototype"); | 
 |   JSValueRef fnPrototype = JSObjectGetProperty(jsContext, fnCtorObject, | 
 |       protoPropName, exception); | 
 |   JSStringRelease(protoPropName); | 
 |   if (!fnPrototype) { | 
 |     return JSValueMakeUndefined(jsContext); | 
 |   } | 
 |  | 
 |   TR_LEAVE(); | 
 |   return fnPrototype; | 
 | } | 
 |  | 
 | /* | 
 |  * | 
 |  */ | 
 | JSClassRef GetDispatchObjectClass() { | 
 |   TR_ENTER(); | 
 |   TR_LEAVE(); | 
 |   return _dispatchObjectClass; | 
 | } | 
 |  | 
 | /* | 
 |  * | 
 |  */ | 
 | JSClassRef GetDispatchMethodClass() { | 
 |   TR_ENTER(); | 
 |   TR_LEAVE(); | 
 |   return _dispatchMethodClass; | 
 | } | 
 |  | 
 | /* | 
 |  * | 
 |  */ | 
 | JSClassRef DispatchObjectClassCreate() { | 
 |   TR_ENTER(); | 
 |   JSClassRef dispClass = JSClassCreate(&_dispatchObjectClassDef); | 
 |   JSClassRetain(dispClass); | 
 |   TR_LEAVE(); | 
 |   return dispClass; | 
 | } | 
 |  | 
 | /* | 
 |  * | 
 |  */ | 
 | JSClassRef DispatchMethodClassCreate() { | 
 |   TR_ENTER(); | 
 |   JSClassRef dispClass = JSClassCreate(&_dispatchMethodClassDef); | 
 |   JSClassRetain(dispClass); | 
 |   TR_LEAVE(); | 
 |   return dispClass; | 
 | } | 
 |  | 
 | /* | 
 |  * NOTE: The object returned from this function is not protected. | 
 |  */ | 
 | JSObjectRef DispatchObjectCreate(JSContextRef jsContext, jobject jObject) { | 
 |   TR_ENTER(); | 
 |   JSObjectRef dispInst = JSObjectMake(jsContext, _dispatchObjectClass, | 
 |       _javaEnv->NewGlobalRef(jObject)); | 
 |   TR_LEAVE(); | 
 |   return dispInst; | 
 | } | 
 |  | 
 | /* | 
 |  * NOTE: The object returned from this function is not protected. | 
 |  */ | 
 | JSObjectRef DispatchMethodCreate(JSContextRef jsContext, std::string& name, | 
 |     jobject jObject) { | 
 |   TR_ENTER(); | 
 |   | 
 |   JSObjectRef dispInst = JSObjectMake(jsContext, _dispatchMethodClass, | 
 |       new DispatchMethodData(_javaEnv->NewGlobalRef(jObject), name)); | 
 |  | 
 |   // This could only be cached relative to jsContext. | 
 |   JSValueRef fnProtoValue = GetFunctionPrototype(jsContext, NULL); | 
 |   JSObjectSetPrototype(jsContext, dispInst, fnProtoValue); | 
 |   TR_LEAVE(); | 
 |   return dispInst; | 
 | } | 
 |  | 
 | /* | 
 |  * NOTE: The value returned from this function is not protected, but all | 
 |  * JSValues that are passed into Java are protected before the invocation. | 
 |  */ | 
 | JSValueRef DispatchObjectGetProperty(JSContextRef jsContext, | 
 |     JSObjectRef jsObject, JSStringRef jsPropertyName, | 
 |     JSValueRef* jsException) { | 
 |   TR_ENTER(); | 
 |  | 
 |   // If you call toString on a DispatchObject, you should get the results | 
 |   // of the java object's toString invcation. | 
 |   if (JSStringIsEqualToUTF8CString(jsPropertyName, "toString")) { | 
 |     JSObjectRef jsFunction = JSObjectMakeFunctionWithCallback(jsContext, | 
 |         jsPropertyName, DispatchObjectToString); | 
 |     return jsFunction; | 
 |   } | 
 |  | 
 |   // The class check is omitted because it should not be possible to tear off | 
 |   // a getter. | 
 |   jobject jObject = reinterpret_cast<jobject>(JSObjectGetPrivate(jsObject)); | 
 |  | 
 |   jstring jPropertyName = _javaEnv->NewString( | 
 |       static_cast<const jchar*>(JSStringGetCharactersPtr(jsPropertyName)), | 
 |       static_cast<jsize>(JSStringGetLength(jsPropertyName))); | 
 |   if (!jObject || !jPropertyName || _javaEnv->ExceptionCheck()) { | 
 |     TR_FAIL(); | 
 |     _javaEnv->ExceptionClear(); | 
 |     return JSValueMakeUndefined(jsContext); | 
 |   } | 
 |  | 
 |   JSValueRef jsResult = reinterpret_cast<JSValueRef>( | 
 |       _javaEnv->CallIntMethod(jObject, _javaDispatchObjectGetFieldMethod, | 
 |       reinterpret_cast<jint>(jsContext), | 
 |       jPropertyName)); | 
 |   if (!jsResult || _javaEnv->ExceptionCheck()) { | 
 |     TR_FAIL(); | 
 |     _javaEnv->ExceptionClear(); | 
 |     return JSValueMakeUndefined(jsContext); | 
 |   } | 
 |  | 
 |   // Java left us an extra reference to eat. | 
 |   JSValueUnprotectChecked(jsContext, jsResult); | 
 |   TR_LEAVE(); | 
 |   return jsResult; | 
 | } | 
 |  | 
 | /* | 
 |  * | 
 |  */ | 
 | bool DispatchObjectSetProperty(JSContextRef jsContext, JSObjectRef jsObject, | 
 |     JSStringRef jsPropertyName, JSValueRef jsValue, JSValueRef* jsException) { | 
 |   TR_ENTER(); | 
 |  | 
 |   // The class check is omitted because it should not be possible to tear off | 
 |   // a getter. | 
 |   jobject jObject = reinterpret_cast<jobject>(JSObjectGetPrivate(jsObject)); | 
 |  | 
 |   jstring jPropertyName = _javaEnv->NewString( | 
 |       static_cast<const jchar*>(JSStringGetCharactersPtr(jsPropertyName)), | 
 |       static_cast<jsize>(JSStringGetLength(jsPropertyName))); | 
 |   if (!jObject || !jPropertyName || _javaEnv->ExceptionCheck()) { | 
 |     _javaEnv->ExceptionClear(); | 
 |     return false; | 
 |   } | 
 |  | 
 |   JSValueProtectChecked(jsContext, jsValue); | 
 |  | 
 |   _javaEnv->CallIntMethod(jObject, _javaDispatchObjectSetFieldMethod, | 
 |       reinterpret_cast<jint>(jsContext), jPropertyName, | 
 |       reinterpret_cast<jint>(jsValue)); | 
 |   if (_javaEnv->ExceptionCheck()) { | 
 |     _javaEnv->ExceptionClear(); | 
 |     return false; | 
 |   } | 
 |  | 
 |   TR_LEAVE(); | 
 |   return true; | 
 | } | 
 |  | 
 | /* | 
 |  * | 
 |  */ | 
 | void DispatchObjectFinalize(JSObjectRef jsObject) { | 
 |   TR_ENTER(); | 
 |   jobject jObject = reinterpret_cast<jobject>(JSObjectGetPrivate(jsObject)); | 
 |   ReleaseJavaObject(jObject); | 
 |   TR_LEAVE(); | 
 | } | 
 |  | 
 | /* | 
 |  * | 
 |  */ | 
 | void DispatchMethodFinalize(JSObjectRef jsObject) { | 
 |   TR_ENTER(); | 
 |   DispatchMethodData* data = reinterpret_cast<DispatchMethodData*>( | 
 |       JSObjectGetPrivate(jsObject)); | 
 |   delete data; | 
 |   TR_LEAVE(); | 
 | } | 
 |  | 
 | /* | 
 |  * NOTE: The value returned from this function is not protected. | 
 |  */ | 
 | JSValueRef DispatchObjectToString(JSContextRef jsContext, JSObjectRef, | 
 |     JSObjectRef jsThis, size_t, const JSValueRef*, JSValueRef*) { | 
 |   TR_ENTER(); | 
 |  | 
 |   // This function cannot be torn off and applied to any JSValue. If this does | 
 |   // not reference a DispatchObject, return undefined. | 
 |   if (!JSValueIsObjectOfClass(jsContext, jsThis, GetDispatchObjectClass())) { | 
 |     return JSValueMakeUndefined(jsContext); | 
 |   } | 
 |  | 
 |   jobject jObject = reinterpret_cast<jobject>(JSObjectGetPrivate(jsThis)); | 
 |   jstring jResult = reinterpret_cast<jstring>( | 
 |       _javaEnv->CallObjectMethod(jObject, _javaDispatchObjectToStringMethod)); | 
 |   if (_javaEnv->ExceptionCheck()) { | 
 |     return JSValueMakeUndefined(jsContext); | 
 |   } else if (!jResult) { | 
 |     return JSValueMakeNull(jsContext); | 
 |   } else { | 
 |     JStringWrap result(_javaEnv, jResult); | 
 |     JSStringRef resultString = JSStringCreateWithCharacters( | 
 |         static_cast<const JSChar*>(result.jstr()), | 
 |         static_cast<size_t>(result.length())); | 
 |     JSValueRef jsResultString = JSValueMakeString(jsContext, resultString); | 
 |     JSStringRelease(resultString); | 
 |     return jsResultString; | 
 |   } | 
 |   TR_LEAVE(); | 
 | } | 
 |  | 
 | /* | 
 |  * | 
 |  */ | 
 | JSValueRef DispatchMethodCallAsFunction(JSContextRef jsContext, | 
 |     JSObjectRef jsFunction, JSObjectRef jsThis, size_t argumentCount, | 
 |     const JSValueRef arguments[], JSValueRef* exception) { | 
 |   TR_ENTER(); | 
 |  | 
 |   // We don't need to check the class here because we take the private | 
 |   // data from jsFunction and not jsThis. | 
 |  | 
 |   DispatchMethodData* data = reinterpret_cast<DispatchMethodData*>( | 
 |       JSObjectGetPrivate(jsFunction)); | 
 |   jobject jObject = data->_jObject; | 
 |  | 
 |   jintArray jArguments = _javaEnv->NewIntArray(argumentCount); | 
 |   if (!jArguments || _javaEnv->ExceptionCheck()) { | 
 |     return JSValueMakeUndefined(jsContext); | 
 |   } | 
 |  | 
 |   // This single element int array will be passed into the java call to allow the | 
 |   // called java method to raise an exception. We will check for a non-null value | 
 |   // after the call is dispatched. | 
 |   jintArray jException = _javaEnv->NewIntArray(1); | 
 |   if (!jException || _javaEnv->ExceptionCheck()) { | 
 |     return JNI_FALSE; | 
 |   } | 
 |  | 
 |   for (size_t i = 0; i < argumentCount; ++i) { | 
 |     JSValueRef arg = arguments[i]; | 
 |     // Java will take ownership of the arguments. | 
 |     JSValueProtectChecked(jsContext, arg); | 
 |     _javaEnv->SetIntArrayRegion(jArguments, i, 1, reinterpret_cast<jint*>(&arg)); | 
 |     if (_javaEnv->ExceptionCheck()) { | 
 |       return JSValueMakeUndefined(jsContext); | 
 |     } | 
 |   } | 
 |  | 
 |   // Java will take ownership of this. | 
 |   JSValueProtectChecked(jsContext, jsThis); | 
 |  | 
 |   JSValueRef jsResult = reinterpret_cast<JSValueRef>(_javaEnv->CallIntMethod(jObject, | 
 |       _javaDispatchMethodInvokeMethod, reinterpret_cast<jint>(jsContext), | 
 |       reinterpret_cast<jint>(jsThis), jArguments, jException)); | 
 |   if (_javaEnv->ExceptionCheck()) { | 
 |     return JSValueMakeUndefined(jsContext); | 
 |   } | 
 |  | 
 |   JSValueRef jsException = NULL; | 
 |   _javaEnv->GetIntArrayRegion(jException, 0, 1, reinterpret_cast<jint*>(&jsException)); | 
 |   if (!_javaEnv->ExceptionCheck() && jsException) { | 
 |     // If the java dispatch set an exception, then we pass it back to our caller. | 
 | 	if (exception) { | 
 |       *exception = jsException; | 
 | 	} | 
 |     // Java left us an extra reference to eat. | 
 |     JSValueUnprotectChecked(jsContext, jsException); | 
 |   } | 
 |  | 
 |   // Java left us an extra reference to eat. | 
 |   JSValueUnprotectChecked(jsContext, jsResult); | 
 |   TR_LEAVE(); | 
 |   return jsResult; | 
 | } | 
 |  | 
 | /* | 
 |  * NOTE: The object returned from this function is not protected. | 
 |  */ | 
 | JSValueRef DispatchMethodToString(JSContextRef jsContext, JSObjectRef, | 
 |     JSObjectRef thisObject, size_t, const JSValueRef*, JSValueRef*) { | 
 |   TR_ENTER(); | 
 |    | 
 |   // This function cannot be torn off and applied to any JSValue. If this does | 
 |   // not reference a DispatchMethod, return undefined. | 
 |   if (!JSValueIsObjectOfClass(jsContext, thisObject, GetDispatchMethodClass())) { | 
 |     return JSValueMakeUndefined(jsContext); | 
 |   } | 
 |  | 
 |   std::ostringstream ss; | 
 |   DispatchMethodData* data = reinterpret_cast<DispatchMethodData*>( | 
 |       JSObjectGetPrivate(thisObject)); | 
 |   ss << "function " << data->_utf8Name << "() {\n    [native code]\n}\n"; | 
 |   JSStringRef stringRep = JSStringCreateWithUTF8CString(ss.str().c_str()); | 
 |   JSValueRef jsStringRep = JSValueMakeString(jsContext, stringRep); | 
 |   JSStringRelease(stringRep); | 
 |   TR_LEAVE(); | 
 |   return jsStringRep; | 
 | } | 
 |  | 
 | /* | 
 |  * NOTE: The object returned from this function is not protected. | 
 |  */ | 
 | JSValueRef DispatchMethodGetToString(JSContextRef jsContext, | 
 |     JSObjectRef jsObject, JSStringRef jsPropertyName, JSValueRef* jsException) { | 
 |   TR_ENTER(); | 
 |   JSObjectRef toStringFn = JSObjectMakeFunctionWithCallback(jsContext, | 
 |       jsPropertyName, DispatchMethodToString); | 
 |   TR_LEAVE(); | 
 |   return toStringFn; | 
 | } | 
 |  | 
 | /* | 
 |  * | 
 |  */ | 
 | bool Initialize(JNIEnv* javaEnv, jclass javaDispatchObjectClass, | 
 |     jclass javaDispatchMethodClass, jclass lowLevelSafClass) { | 
 |   TR_ENTER(); | 
 |   if (!javaEnv || !javaDispatchObjectClass || !javaDispatchMethodClass | 
 |       || !lowLevelSafClass) { | 
 |     return false; | 
 |   } | 
 |  | 
 |   _javaVM = 0; | 
 |   javaEnv->GetJavaVM(&_javaVM); | 
 |  | 
 |   _javaEnv = javaEnv; | 
 |   _javaDispatchObjectClass = static_cast<jclass>( | 
 |       javaEnv->NewGlobalRef(javaDispatchObjectClass)); | 
 |   _javaDispatchMethodClass = static_cast<jclass>( | 
 |       javaEnv->NewGlobalRef(javaDispatchMethodClass)); | 
 |   _lowLevelSafClass = static_cast<jclass>( | 
 |       javaEnv->NewGlobalRef(lowLevelSafClass)); | 
 |   _javaDispatchObjectSetFieldMethod = javaEnv->GetMethodID( | 
 |       javaDispatchObjectClass, "setField", "(ILjava/lang/String;I)V"); | 
 |   _javaDispatchObjectGetFieldMethod = javaEnv->GetMethodID( | 
 |       javaDispatchObjectClass, "getField", "(ILjava/lang/String;)I"); | 
 |   _javaDispatchMethodInvokeMethod = javaEnv->GetMethodID( | 
 |       javaDispatchMethodClass, "invoke", "(II[I[I)I"); | 
 |   _javaDispatchObjectToStringMethod = javaEnv->GetMethodID( | 
 |       javaDispatchObjectClass, "toString", "()Ljava/lang/String;"); | 
 |   _lowLevelSafReleaseObject = javaEnv->GetStaticMethodID( | 
 |       lowLevelSafClass, "releaseObject", "(Ljava/lang/Object;)V"); | 
 |  | 
 |   if (!_javaVM | 
 |       || !_javaDispatchObjectSetFieldMethod || !_javaDispatchObjectGetFieldMethod | 
 |       || !_javaDispatchMethodInvokeMethod || !_javaDispatchObjectToStringMethod | 
 |       || !_lowLevelSafReleaseObject || javaEnv->ExceptionCheck()) { | 
 |     return false; | 
 |   } | 
 |  | 
 |   TR_LEAVE(); | 
 |   return true; | 
 | } | 
 |  | 
 | void ReleaseJavaObject(jobject jObject) { | 
 |   // Tricky: this call may be on a foreign thread. | 
 |   JNIEnv* javaEnv = 0; | 
 |   if ((_javaVM->AttachCurrentThreadAsDaemon(reinterpret_cast<void**>(&javaEnv), | 
 |       NULL) < 0) || !javaEnv) { | 
 |     TR_FAIL(); | 
 |     return; | 
 |   } | 
 |  | 
 |   // Tell the Java code we're done with this object. | 
 |   javaEnv->CallStaticVoidMethod(_lowLevelSafClass, _lowLevelSafReleaseObject, | 
 |       jObject); | 
 |   if (javaEnv->ExceptionCheck()) { | 
 |     TR_FAIL(); | 
 |     return; | 
 |   } | 
 |   javaEnv->DeleteGlobalRef(jObject); | 
 | } | 
 |  | 
 | } // namespace gwt |