-
Notifications
You must be signed in to change notification settings - Fork 371
/
Runtime.java
218 lines (194 loc) · 7.85 KB
/
Runtime.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
/*
* 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 = {};
}-*/;
/**
* 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();
}-*/;
}