| /* |
| * 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; |
| |
| /** |
| * Utility class for defining class prototyes to setup an equivalent to the Java class hierarchy in |
| * JavaScript. |
| */ |
| public class JavaClassHierarchySetupUtil { |
| /** |
| * Holds a map from typeIds to prototype objects. |
| */ |
| private static JavaScriptObject prototypesByTypeId = JavaScriptObject.createObject(); |
| |
| /** |
| * 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 = @JavaClassHierarchySetupUtil::prototypesByTypeId; |
| // end of alias definitions. |
| |
| var prototype = prototypesByTypeId[typeId]; |
| var clazz = @JavaClassHierarchySetupUtil::maybeGetClassLiteralFromPlaceHolder(*)(prototype); |
| if (prototype && !clazz) { |
| // not a placeholder entry setup by Class.setClassLiteral |
| _ = prototype; |
| } else { |
| _ = @JavaClassHierarchySetupUtil::createSubclassPrototype(*)(superTypeIdOrPrototype); |
| _.@Object::castableTypeMap = castableTypeMap; |
| _.constructor = _; |
| 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 = @JavaClassHierarchySetupUtil::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 = @JavaClassHierarchySetupUtil::prototypesByTypeId[superTypeIdOrPrototype]; |
| } |
| return @JavaClassHierarchySetupUtil::portableObjCreate(*)(superPrototype); |
| }-*/; |
| |
| /** |
| * 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]); |
| } |
| |
| // 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());) { |
| if (cur[part]) { |
| cur = cur[part]; |
| } else { |
| cur = cur[part] = 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 instance) /*-{ |
| var lambda = function() { return samMethod.apply(lambda, arguments); } |
| lambda.__proto__ = instance; |
| return lambda; |
| }-*/; |
| |
| /** |
| * If the parameter o is a Javascript object, return the bridge method reference, |
| * otherwise return the nonbridge reference. |
| * @param o an instance object we want to invoke a method on |
| * @param bridgeRef a reference to an exported, bridgereference or jstype method |
| * @param nonbridgeRef the internal reference to Java obfuscated method |
| * @return |
| */ |
| public static native boolean trampolineBridgeMethod(Object o, Object bridgeRef, |
| Object nonbridgeRef) /*-{ |
| return @com.google.gwt.lang.Cast::isJavaScriptObject(Ljava/lang/Object;)(o) |
| ? bridgeRef : nonbridgeRef; |
| }-*/; |
| |
| /** |
| * Do polyfills for all methods expected in a modern browser. |
| */ |
| public static native void modernizeBrowser() /*-{ |
| // Patch up Array.isArray for browsers that don't support the fast native check. |
| if (!Array.isArray) { |
| Array.isArray = function (vArg) { |
| return Object.prototype.toString.call(vArg) === "[object Array]"; |
| }; |
| } |
| }-*/; |
| |
| /** |
| * Retrieves the prototype for a type if it exists, null otherwise. |
| */ |
| public static native JavaScriptObject getClassPrototype(JavaScriptObject typeId) /*-{ |
| return @JavaClassHierarchySetupUtil::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() /*-{ |
| }-*/; |
| |
| static native JavaScriptObject uniqueId(String id) /*-{ |
| return jsinterop.closure.getUniqueId(id); |
| }-*/; |
| |
| /** |
| * Converts an input object (LongEmul) to a double, otherwise return the value. |
| */ |
| public static native Object coerceToLong(Object o) /*-{ |
| if (typeof(o) == 'number') { |
| return @com.google.gwt.lang.LongLib::fromDouble(D)(o); |
| } |
| return o; |
| }-*/; |
| |
| /** |
| * Convert a double to a long. |
| */ |
| public static native Object coerceFromLong(Object o) /*-{ |
| return @com.google.gwt.lang.LongLib::toDouble(Lcom/google/gwt/lang/LongLibBase$LongEmul;)(o); |
| }-*/; |
| } |