/* * Copyright 2011 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. */ package com.google.gwt.lang; import com.google.gwt.core.client.JavaScriptObject; import javaemul.internal.annotations.ForceInline; /** * Utility class that provides methods for dealing with the runtime representation of Java classes * and bootstraping code. */ public class Runtime { /** * Holds a map from typeIds to prototype objects. */ private static JavaScriptObject prototypesByTypeId; /** * If not already created it creates the prototype for the class and stores it in * {@code prototypesByTypeId}. If superTypeIdOrPrototype is null, it means that the class being * defined is the topmost class (i.e. java.lang.Object) and creates an empty prototype for it. * Otherwise it creates the prototype for the class by calling {@code createSubclassPrototype()}. * It also assigns the castable type map and sets the constructors prototype field to the * current prototype. * Finally adds the class literal if it was created before the call to {@code defineClass}. * Class literals might be created before the call to {@code defineClass} if they are in separate * code-split fragments. In that case Class.createFor* methods will have created a placeholder and * stored in {@code prototypesByTypeId} the class literal. * <p> * As a prerequisite if superTypeIdOrPrototype is not null, it is assumed that defineClass for the * supertype has already been called. * <p> * This method has the effect of assigning the newly created prototype to the global temp variable * '_'. */ public static native void defineClass(JavaScriptObject typeId, JavaScriptObject superTypeIdOrPrototype, JavaScriptObject castableTypeMap) /*-{ // Setup aliases for (horribly long) JSNI references. var prototypesByTypeId = @Runtime::prototypesByTypeId; // end of alias definitions. var prototype = prototypesByTypeId[typeId]; var clazz = @Runtime::maybeGetClassLiteralFromPlaceHolder(*)(prototype); if (prototype && !clazz) { // not a placeholder entry setup by Class.setClassLiteral _ = prototype; } else { _ = @Runtime::createSubclassPrototype(*)(superTypeIdOrPrototype); _.@Object::castableTypeMap = castableTypeMap; if (!superTypeIdOrPrototype) { // Set the typeMarker on java.lang.Object's prototype, implicitly setting it for all // Java subclasses (String and Arrays have special handling in Cast and Array respectively). _.@Object::typeMarker = @Runtime::typeMarkerFn(*); } prototypesByTypeId[typeId] = _; } for (var i = 3; i < arguments.length; ++i) { // Assign the type prototype to each constructor. arguments[i].prototype = _; } if (clazz) { _.@Object::___clazz = clazz; } }-*/; private static native JavaScriptObject portableObjCreate(JavaScriptObject obj) /*-{ function F() {}; F.prototype = obj || {}; return new F(); }-*/; /** * Create a subclass prototype. */ private static native JavaScriptObject createSubclassPrototype( JavaScriptObject superTypeIdOrPrototype) /*-{ var superPrototype = superTypeIdOrPrototype && superTypeIdOrPrototype.prototype; if (!superPrototype) { // If it is not a prototype, then it should be a type id. superPrototype = @Runtime::prototypesByTypeId[superTypeIdOrPrototype]; } return @Runtime::portableObjCreate(*)(superPrototype); }-*/; public static native void copyObjectProperties(JavaScriptObject from, JavaScriptObject to) /*-{ for (var property in from) { if (to[property] === undefined) { to[property] = from[property]; } } }-*/; /** * Retrieves the class literal if stored in a place holder, {@code null} otherwise. */ private static native JavaScriptObject maybeGetClassLiteralFromPlaceHolder( JavaScriptObject entry) /*-{ // TODO(rluble): Relies on Class.createFor*() storing the class literal wrapped as an array // to distinguish it from an actual prototype. return (entry instanceof Array) ? entry[0] : null; }-*/; /** * Creates a JS namespace to attach exported classes to. * @param namespace a dotted js namespace string * @return a nested object literal representing the namespace */ public static native JavaScriptObject provide(JavaScriptObject namespace, JavaScriptObject optCtor) /*-{ var cur = $wnd; if (namespace === '') { return cur; } // borrowed from Closure's base.js var parts = namespace.split('.'); // Internet Explorer exhibits strange behavior when throwing errors from // methods externed in this manner. See the testExportSymbolExceptions in // base_test.html for an example. if (!(parts[0] in cur) && cur.execScript) { cur.execScript('var ' + parts[0]); } if(optCtor) { var clazz = optCtor.prototype.@Object::___clazz; clazz.@Class::jsConstructor = optCtor; } // Certain browsers cannot parse code in the form for((a in b); c;); // This pattern is produced by the JSCompiler when it collapses the // statement above into the conditional loop below. To prevent this from // happening, use a for-loop and reserve the init logic as below. // Parentheses added to eliminate strict JS warning in Firefox. for (var part; parts.length && (part = parts.shift());) { // assign the constructor to the last node (when parts is empty) cur = cur[part] = cur[part] || !parts.length && optCtor || {}; } return cur; }-*/; /** * Create a function that applies the specified samMethod on itself, and whose __proto__ points to * <code>instance</code>. */ public static native JavaScriptObject makeLambdaFunction( JavaScriptObject samMethod, JavaScriptObject ctor, JavaScriptObject ctorArguments) /*-{ var lambda = function() { return samMethod.apply(lambda, arguments); } ctor.apply(lambda, ctorArguments); return lambda; }-*/; /** * Called at the beginning to setup required structures before any of the classes is defined. * The code written in and invoked by this method should be plain JavaScript. */ public static native void bootstrap() /*-{ @Runtime::prototypesByTypeId = {}; // Do polyfills for all methods expected in a modern browser. // Required by IE8 if (!Array.isArray) { Array.isArray = function (vArg) { return Object.prototype.toString.call(vArg) === "[object Array]"; }; } // Required by IE8 if (!Date.now) { Date.now = function now() { return new Date().getTime(); }; } }-*/; /** * Retrieves the prototype for a type if it exists, null otherwise. */ public static native JavaScriptObject getClassPrototype(JavaScriptObject typeId) /*-{ return @Runtime::prototypesByTypeId[typeId]; }-*/; /** * Marker function. All Java Objects (except Strings) have a typeMarker field pointing to * this function. */ static native void typeMarkerFn() /*-{ }-*/; /** * A global noop function. Replaces clinits after execution. */ static native void emptyMethod() /*-{ }-*/; @ForceInline static native JavaScriptObject uniqueId(String id) /*-{ return jsinterop.closure.getUniqueId(id); }-*/; static native void defineProperties( JavaScriptObject proto, JavaScriptObject propertyDefinition) /*-{ for (var key in propertyDefinition) { propertyDefinition[key]['configurable'] = true; } Object.defineProperties(proto, propertyDefinition); }-*/; public static native String toString(Object object) /*-{ if (Array.isArray(object) && @Util::hasTypeMarker(*)(object)) { return @Object::toString(Ljava/lang/Object;)(object); } return object.toString(); }-*/; }