/*
* Smart GWT (GWT for SmartClient)
* Copyright 2008 and beyond, Isomorphic Software, Inc.
*
* Smart GWT is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License version 3
* is published by the Free Software Foundation. Smart GWT is also
* available under typical commercial license terms - see
* http://smartclient.com/license
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package com.smartgwt.client;
import java.util.Set;
import com.google.gwt.event.shared.UmbrellaException;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.Window;
import com.smartgwt.client.util.I18nUtil;
import com.smartgwt.client.util.LogUtil;
import com.smartgwt.client.bean.BeanFactory;
import com.smartgwt.client.util.SC;
/**
* Internal Smart GWT Entry point class where framework level initialization code executes
* before the users EntryPoint is run.
*/
public class SmartGwtEntryPoint implements EntryPoint {
private static boolean initialized = false;
private static native void init() /*-{
// If we can't find window.isc, the JavaScript libs are not present.
if ($wnd.isc == null) {
var message = "Core SmartClient JavaScript libraries appear not to be loaded.\nIf inheriting the NoScript SmartGWT modules, verify that " +
"the HTML file includes <script src=...> tags to load the SmartClient module .js files from the appropriate location within the " +
"WAR.\nBy default these files are present under [GWT app name]/sc/modules/. ";
@com.google.gwt.core.client.GWT::log(Ljava/lang/String;Ljava/lang/Throwable;)(message, @com.smartgwt.client.core.JsObject.SGWT_WARN::new(Ljava/lang/String;)(message));
return;
}
var asBuiltSCVersionNumber = @com.smartgwt.client.Version::getSCVersionNumber()();
if ($wnd.isc.versionNumber != asBuiltSCVersionNumber && $wnd.isc.versionNumber.indexOf("${")==-1 ) {
var message = "This build of Smart GWT " + @com.smartgwt.client.Version::getVersion()() + " was built for SmartClient version " + asBuiltSCVersionNumber +
" but SmartClient version " + $wnd.isc.versionNumber + " is loaded.\n\nTo correct this problem, clear GWT's unitcache, run a GWT compile, " +
"restart the browser, and clear the browser's cache before visiting any pages.";
@com.google.gwt.core.client.GWT::log(Ljava/lang/String;Ljava/lang/Throwable;)(message, @com.smartgwt.client.core.JsObject.SGWT_WARN::new(Ljava/lang/String;)(message));
}
// these must be called after we verify the SC libs are loaded
@com.smartgwt.client.util.LogUtil::setJSNIErrorHandler()();
@com.smartgwt.client.util.LogUtil::addSGWTLoggerCategories()();
//pre GWT 2.0 fallback
if(typeof $entry === "undefined") {
$entry = function(jsFunction) {
return jsFunction;
};
}
if ($wnd.isc.Browser.isIE && $wnd.isc.Browser.version >= 7) {
$wnd.isc.EventHandler._IECanSetKeyCode = {};
}
// Debox Javascript objects that wrap primitives for the benefit of Java in hosted mode.
// E.g. JS Boolean or Number must be converted to primitives to return them from JSNI.
// Note: Java boxing (e.g. java.lang.Boolean) is separate & requires adding extra code.
$debox = function(val) {
return @com.google.gwt.core.client.GWT::isScript()() ? val : function() {
var v = val.apply(this, arguments);
// Dates can just be returned without deboxing
if ($wnd.isc.isA.Date(v)) return v;
return v == undefined || v == null ? null : v.valueOf();
}};
// use a new Record as the array loading marker (this will allow new Record(...) to work with unloaded rows)
var loadingRecord = @com.smartgwt.client.data.Record::new()();
$wnd.Array.LOADING = loadingRecord.@com.smartgwt.client.data.Record::getJsObj()();
$wnd.Array.LOADING.loadingMarker = true;
// Set a flag so SC code can easily determine that SGWT is running
$wnd.isc.Browser.isSGWT = true;
//convert javascript data types into corresponding Java wrapper types
//int -> Integer, float -> Float, boolean -> Boolean and date - > java.util.Date
$wnd.SmartGWT ={};
// In JSNI, we may be passed GWT Java Object references.
// These typically can not be directly manipulated -- this check will test for such objects.
$wnd.SmartGWT.isNativeJavaObject = function (object) {
// From observation "typeof" reports "function" for native Java objects in Firefox in development mode
// and in OmniWeb, in development mode
// In all other browsers, typeof reports as "object"
var type = typeof object;
if (type != "function" && type != "object") return false;
if (@com.smartgwt.client.util.JSOHelper::isJSO(Ljava/lang/Object;)(object)) return false;
return true;
};
// provide a way to check whether an SC.REF is really a SGWT Tab or some other widget
$wnd.SmartGWT.isTab = function (target) {
return @com.smartgwt.client.widgets.tab.Tab::isTab(Ljava/lang/Object;)(target);
}
// Helper JSNI method to throw an exception if attempting to convert an unconvertible native Java object to JavaScript
// This may be called from SmartClient framework code. Useful for the case where a developer has applied a native Java
// object to a DS transaction's data via 'setAttribute' or similar, and we want to throw a clear exception when
// attempting to serialize this data and send to the server.
$wnd.SmartGWT.throwUnconvertibleObjectException = function (object, message) {
@com.smartgwt.client.util.JSOHelper::throwUnconvertibleObjectException(Ljava/lang/Object;Ljava/lang/String;)(object,message);
}
if(!@com.google.gwt.core.client.GWT::isScript()()){
$wnd.isc.Log.addClassMethods({
warningLogged : function (message) {
@com.google.gwt.core.client.GWT::log(Ljava/lang/String;Ljava/lang/Throwable;)(message, @com.smartgwt.client.core.JsObject.SGWT_WARN::new(Ljava/lang/String;)(message));
}
});
//support option of triggering JS debugger by default in hosted mode if JS error is encountered
@com.smartgwt.client.util.SC::setEnableJSDebugger(Z)(true);
// Log a warning about the known issues with Chrome / Hosted Mode
if ($wnd.isc.Browser.isChrome) {
$wnd.isc.Log.logWarn("WARNING: due to bugs in Chrome, GWT development mode in Chrome is not reliable and should not be used. " +
"This does not affect compiled mode in Chrome, which works. Note that the same bug makes GWT development " +
"mode in Chrome very slow as well, so other browsers will be faster as " +
"well. More details including links to Chrome bugs here: " +
"http://forums.smartclient.com/showthread.php?t=8159#aChrome");
}
$wnd.isc.isA.FUNCTION_STR = '[object Function]';
$wnd.isc.isA.DATE_STR = '[object Date]';
$wnd.isc.isA.ARRAY_STR = '[object Array]';
$wnd.isc.isA.Function = function (object) {
if (object == null) return false;
if ($wnd.SmartGWT.isNativeJavaObject(object)) return false;
return Object.prototype.toString.apply(object) === this.FUNCTION_STR;
};
$wnd.isc.isA.String = function (object) {
if (object == null) return false;
if ($wnd.SmartGWT.isNativeJavaObject(object)) return false;
if (object.Class != null && object.Class == 'String') return true;
return typeof object == "string";
};
$wnd.isc.isA.Number = function (object) {
if (object == null) return false;
if ($wnd.SmartGWT.isNativeJavaObject(object)) return false;
if (object.Class != null && object.Class == 'Number') return true;
return typeof object === 'number' && isFinite(object);
};
$wnd.isc.isA.Boolean = function (object) {
if (object == null) return false;
if ($wnd.SmartGWT.isNativeJavaObject(object)) return false;
return typeof object == "boolean";
};
$wnd.isc.isA.Date = function (object) {
if (object == null) return false;
if ($wnd.SmartGWT.isNativeJavaObject(object)) return false;
return Object.prototype.toString.apply(object) === this.DATE_STR;
};
$wnd.isc.isA.Array = function (object) {
if (object == null) return false;
if ($wnd.SmartGWT.isNativeJavaObject(object)) return false;
return Object.prototype.toString.apply(object) === this.ARRAY_STR;
};
$wnd.isc.Canvas.validateFieldNames = true;
}
// helper routine for convertToJavaType(); not wrapped with $entry()
$wnd.SmartGWT._convertToJavaArrayType = function (obj, type) {
if ($wnd.isc.SimpleType.inheritsFrom(type, "text")) {
return @com.smartgwt.client.util.JSOHelper::convertToJavaStringArray(Lcom/google/gwt/core/client/JavaScriptObject;)(obj);
} else if ($wnd.isc.SimpleType.inheritsFrom(type, "date")) {
return @com.smartgwt.client.util.JSOHelper::convertToJavaDateArray(Lcom/google/gwt/core/client/JavaScriptObject;)(obj);
} else if ($wnd.isc.SimpleType.inheritsFrom(type, "boolean")) {
return @com.smartgwt.client.util.JSOHelper::convertToJavaBooleanArray(Lcom/google/gwt/core/client/JavaScriptObject;)(obj);
} else if ($wnd.isc.SimpleType.inheritsFrom(type, "integer")) {
return @com.smartgwt.client.util.JSOHelper::convertToJavaIntegerArray(Lcom/google/gwt/core/client/JavaScriptObject;)(obj);
} else if ($wnd.isc.SimpleType.inheritsFrom(type, "float")) {
return @com.smartgwt.client.util.JSOHelper::convertToJavaDoubleArray(Lcom/google/gwt/core/client/JavaScriptObject;)(obj);
} else {
return @com.smartgwt.client.util.JSOHelper::convertToJavaObjectArray(Lcom/google/gwt/core/client/JavaScriptObject;)(obj);
}
};
//> @method convertToJavaType()
// Converts a JS object to a Java object. The type argument is optional
// and only used when isc.isAn.Array(obj) is true. The type is determined
// by inspecting the object in all other cases.
// Note: If null is explicitly passed for type, conversion to a Java array
// (if applicable) will be skipped and a JavaScriptObject will be returned.
// @param obj (object) the JS object to be converted
// @param [type] (String) type of the field as in +link{DataSourceField.type} (optional)
//<
$wnd.SmartGWT.convertToJavaType = $entry(function(obj, type) {
if(obj == null) return null;
var objType = typeof obj;
if(objType == 'string') {
return obj;
} else if (objType == 'number') {
if(obj.toString().indexOf('.') == -1) {
if(obj <= @java.lang.Integer::MAX_VALUE && obj >= @java.lang.Integer::MIN_VALUE) {
return @com.smartgwt.client.util.JSOHelper::toInteger(I)(obj);
} else {
return @com.smartgwt.client.util.JSOHelper::toLong(D)(obj);
}
} else {
// Convert non-integral JS numbers to Java `Double's to prevent a loss
// of precision in dev mode and other issues where certain numbers can
// be printed with extra spurious precision.
// See the comment in JSOHelper.doubleValue().
return @com.smartgwt.client.util.JSOHelper::toDouble(D)(obj);
}
} else if(objType == 'boolean') {
return @com.smartgwt.client.util.JSOHelper::toBoolean(Z)(obj);
// We may already be looking at a native java object. If so, just return it
// (Note that attempting to look at properties such as _constructor will crash on a native java object)
} else if ($wnd.SmartGWT.isNativeJavaObject(obj)) {
return obj;
} else if($wnd.isc.isA.Date(obj)) {
return @com.smartgwt.client.util.JSOHelper::convertToJavaDate(Lcom/google/gwt/core/client/JavaScriptObject;)(obj);
} else if (obj._constructor && obj._constructor == 'DateRange') {
return @com.smartgwt.client.widgets.form.fields.DateRangeItem::convertToDateRange(Lcom/google/gwt/core/client/JavaScriptObject;)(obj);
} else if($wnd.isc.isA.Array(obj) && type !== null) {
return this._convertToJavaArrayType(obj, type);
} else if(@com.smartgwt.client.util.JSOHelper::isJSO(Ljava/lang/Object;)(obj)) {
return obj;
} else {
// We were unable to determine the type - return the object unmodified.
return obj;
}
});
$wnd.SmartGWT.convertToJavaObject = $entry(function (object, listAsArray, forceMap) {
if (object == null) return null;
var refProperty = @com.smartgwt.client.util.SC::REF;
if (!$wnd.isc.isA.Object(object)) {
return $wnd.SmartGWT.convertToJavaType(object);
} else if ($wnd.isc.isA.Date(object)) {
return @com.smartgwt.client.util.JSOHelper::convertToJavaDate(Lcom/google/gwt/core/client/JavaScriptObject;)(object);
} else if ($wnd.isc.isAn.Array(object)) {
var convertedArray = [];
for (var i = 0; i < object.length; i++) {
convertedArray[i] = $wnd.SmartGWT.convertToJavaObject(object[i], false, false);
}
// now we've converted all our members and we need to return a Java array or List
if (listAsArray) {
return @com.smartgwt.client.util.JSOHelper::convertToJavaObjectArray(Lcom/google/gwt/core/client/JavaScriptObject;)(convertedArray);
} else {
var javaList = @java.util.ArrayList::new()();
for (var i = 0; i < convertedArray.length; i++) {
javaList.@java.util.ArrayList::add(Ljava/lang/Object;)(convertedArray[i]);
}
return javaList;
}
} else {
if (forceMap !== true) {
// Check for a POJO.
if (!@com.smartgwt.client.util.JSOHelper::isJSO(Ljava/lang/Object;)(object)) {
return object;
}
if (object[refProperty] != null) {
return object[refProperty];
}
if ($wnd.isc.isA.Canvas(object)) {
return @com.smartgwt.client.widgets.Canvas::getById(Ljava/lang/String;)(object.getID());
}
if ($wnd.isc.isA.String(object.name) && $wnd.isc.isA.DynamicForm(object.form)) {
var formJ = @com.smartgwt.client.widgets.form.DynamicForm::getOrCreateRef(Lcom/google/gwt/core/client/JavaScriptObject;)(object.form);
return formJ.@com.smartgwt.client.widgets.form.DynamicForm::getField(Ljava/lang/String;)(object.name);
}
if ($wnd.isc.isA.String(object._constructor)) {
var objectConstructor = object._constructor;
if (objectConstructor == "RelativeDate") {
return (object[refProperty] = @com.smartgwt.client.data.RelativeDate::new(Lcom/google/gwt/core/client/JavaScriptObject;)(object));
}
// Don't convert `AdvancedCriteria' here; we want this API to return
// a `Map' for the advanced criteria JSO.
}
if ($wnd.isc.isAn.Instance(object) && object.getClassName != null) {
return (object[refProperty] = @com.smartgwt.client.util.ObjectFactory::createInstance(Ljava/lang/String;Lcom/google/gwt/core/client/JavaScriptObject;)(object.getClassName(), object));
}
} else {
if (object[refProperty] != null) {
if (@com.smartgwt.client.util.JSOHelper::isJavaMap(Ljava/lang/Object;)(object[refProperty])) {
return object[refProperty];
}
}
}
// convert to a map
var javaMap = @java.util.LinkedHashMap::new()();
// If it's a tree node, clean it up before converting otherwise we may end up serializing out
// all parents and children!
var treeProp = $wnd.isc.Tree.getPrototype().treeProperty;
if (object[treeProp] != null) {
object = $wnd.isc.Tree.getCleanNodeData(object);
}
// remove SGWT backrefs if appropriate
// see http://forums.smartclient.com/showthread.php?p=127912
if (this._cleanSgwtProperties) {
delete object.__ref;
delete object.__module;
}
for (var fieldName in object) {
// Not sure whether this could really happen
if(!$wnd.isc.isA.String(fieldName)){
continue;
}
// Don't convert the GWT module created by BeanFactory
if (fieldName == $wnd.isc.gwtModule) continue;
var val = object[fieldName];
//if the field name is '__ref', the the value is already a GWT java object reference
var convertedVal = (fieldName == refProperty || this.isNativeJavaObject(val) ? val : $wnd.SmartGWT.convertToJavaObject(val, false, false));
@com.smartgwt.client.util.JSOHelper::doAddToMap(Ljava/util/Map;Ljava/lang/String;Ljava/lang/Object;)(javaMap, fieldName, convertedVal);
}
return javaMap;
}
});
// Given a GWT Java Object such as a java.lang.Integer, convert to the primitive type (an int)
// so we can manipulate the value directly in JavaScript
$wnd.SmartGWT.convertToPrimitiveType = $entry(function (object) {
if (object == null) return null;
//With the exception of java.lang.String, touching any property on a GWT object ref like java.lang.Long or other GWT java class within JSNI causes
//an exception to be raised in FF hosted mode
//See http://code.google.com/p/google-web-toolkit/issues/detail?id=4946
//The above issue seemed to have disappeared with a certain combination of FF, GWT and the GWT FF plugin. However it has now resurfaced with FF4.
//Calling an API like $wnd.isc.isA.String(object) where object is a GWT Java object like java.lang.Long raises an exception
//since the function $wnd.isc.isA.String tests (touches) properties on the object.
//However calling 'typeof object' on a GWT object returns "function" (in FF4, Safari 5) or "object" (in FF 3) and it does not raise an exception in FF hosted mode
//Note that typeof on a java.lang.String returns 'string'
//test to see if possibly a GWT primitive object type, and if so convert to its JS counterpart object, else treat it as a JS compatible object type
// (eg GWT primitive int, boolean, long types)
var objType = typeof object;
if (objType == 'function' || objType == 'object') {
if(@com.smartgwt.client.util.JSOHelper::isJavaNumber(Ljava/lang/Object;)(object)) return @com.smartgwt.client.util.JSOHelper::doubleValue(Ljava/lang/Number;)(object);
if(@com.smartgwt.client.util.JSOHelper::isJavaBoolean(Ljava/lang/Object;)(object)) return object.@java.lang.Boolean::booleanValue()();
if(@com.smartgwt.client.util.JSOHelper::isJavaDate(Ljava/lang/Object;)(object)) return @com.smartgwt.client.util.JSOHelper::convertToJavaScriptDate(Ljava/util/Date;)(object);
//in certain browser versions, GWT hosted mode returns 'typeof obj' as object even for strings that originated from a object.toString() call in GWT java
//see http://code.google.com/p/google-web-toolkit/issues/detail?id=4301 workaround this issue by explicitly casting to a String object
if(@com.smartgwt.client.util.JSOHelper::isJavaString(Ljava/lang/Object;)(object)) return @com.smartgwt.client.util.JSOHelper::convertToString(Ljava/lang/Object;)(object);
}
return object;
});
if ($wnd.isc.RPCManager.__fireReplyCallback == null) {
$wnd.isc.RPCManager.__fireReplyCallback = $wnd.isc.RPCManager.fireReplyCallback;
$wnd.isc.RPCManager.fireReplyCallback = function (callback, request, response, data) {
// convert primitives (number / bool) to Objects before firing callbacks
if (data != null && $wnd.isc.isA.Number(data) || $wnd.isc.isA.Boolean(data)) {
data = response.data = $wnd.SmartGWT.convertToJavaType(data);
}
return this.__fireReplyCallback(callback, request, response, data);
}
}
}-*/;
private boolean hasUncaughtExceptions;
public void onModuleLoad() {
// added boolean init check flag because GWT for some reason invokes this entry point
// class twice in hosted mode even though it appears only once in the load
// hierarchy. Check with GWT team.
if (!initialized) {
init();
I18nUtil.init();
// install a default UEH that displays the error message in an alert when in
// development mode so that is is not overlooked by the user during development
GWT.setUncaughtExceptionHandler(new GWT.UncaughtExceptionHandler() {
public void onUncaughtException(Throwable t) {
String exceptionSummary = "Uncaught exception escaped: " +
t.getClass().getName() + "\n" + t.getMessage();
if (GWT.isScript()) {
// In production mode, log detailed exception content,
// including stack traces, to the developer console.
if (t instanceof UmbrellaException) {
Set<Throwable> causes = ((UmbrellaException) t).getCauses();
Throwable[] exceptions = causes.toArray(new Throwable[0]);
String message = "";
for (int i = 0; i < exceptions.length; i++) {
if (i > 0) message += "\n";
message += SmartGwtExceptionUtil.toString(exceptions[i]);
}
SC.logWarn(message);
} else {
SC.logWarn(SmartGwtExceptionUtil.toString(exceptionSummary, t));
}
} else {
// In development mode, details are sent to the GWT development
// console (in Eclipse or equivalent) by the GWT.log call below.
Window.alert(exceptionSummary + "\nSee the GWT exception log for " +
"details.\nRegister a GWT.setUncaughtExceptionHandler(..) " +
"for custom uncaught exception handling."
);
// Unfortunately, all developer console logs show up in the GWT development
// mode console as well. So to avoid confusion and duplication, just log a
// heads-up message to the developer console to alert user to check Eclipse.
if (!hasUncaughtExceptions) {
SC.logWarn("GWT uncaught exceptions have been encountered. " +
"Check the Development Mode console for more details.");
}
// GWT.log no-ops in production mode
GWT.log("Uncaught exception escaped", t);
}
hasUncaughtExceptions = true;
}
});
// Trigger generation of BeanFactories for any classes annotated with BeanFactory.Generate
GWT.create(BeanFactory.AnnotationMetaFactory.class);
initialized = true;
}
}
}