001 /* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
002 *
003 * The contents of this file are subject to the Netscape Public
004 * License Version 1.1 (the "License"); you may not use this file
005 * except in compliance with the License. You may obtain a copy of
006 * the License at http://www.mozilla.org/NPL/
007 *
008 * Software distributed under the License is distributed on an "AS
009 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
010 * implied. See the License for the specific language governing
011 * rights and limitations under the License.
012 *
013 * The Original Code is Rhino code, released
014 * May 6, 1999.
015 *
016 * The Initial Developer of the Original Code is Netscape
017 * Communications Corporation. Portions created by Netscape are
018 * Copyright (C) 1997-2000 Netscape Communications Corporation. All
019 * Rights Reserved.
020 *
021 * Contributor(s):
022 *
023 * Patrick Beard
024 * Norris Boyd
025 * Igor Bukanov
026 * Brendan Eich
027 * Roger Lawrence
028 * Mike McCabe
029 * Ian D. Stewart
030 * Andi Vajda
031 * Andrew Wason
032 * Kemal Bayram
033 *
034 * Alternatively, the contents of this file may be used under the
035 * terms of the GNU Public License (the "GPL"), in which case the
036 * provisions of the GPL are applicable instead of those above.
037 * If you wish to allow use of your version of this file only
038 * under the terms of the GPL and not to allow others to use your
039 * version of this file under the NPL, indicate your decision by
040 * deleting the provisions above and replace them with the notice
041 * and other provisions required by the GPL. If you do not delete
042 * the provisions above, a recipient may use your version of this
043 * file under either the NPL or the GPL.
044 */
045 // Modified by Google
046
047 // API class
048
049 package com.google.gwt.dev.js.rhino;
050
051 import org.jetbrains.annotations.NotNull;
052
053 import java.beans.PropertyChangeEvent;
054 import java.beans.PropertyChangeListener;
055 import java.lang.reflect.Method;
056 import java.text.MessageFormat;
057 import java.util.Hashtable;
058 import java.util.Locale;
059 import java.util.MissingResourceException;
060
061 /**
062 * This class represents the runtime context of an executing script.
063 *
064 * Before executing a script, an instance of Context must be created
065 * and associated with the thread that will be executing the script.
066 * The Context will be used to store information about the executing
067 * of the script such as the call stack. Contexts are associated with
068 * the current thread using the <a href="#enter()">enter()</a> method.<p>
069 *
070 * The behavior of the execution engine may be altered through methods
071 * such as <a href="#setLanguageVersion>setLanguageVersion</a> and
072 * <a href="#setErrorReporter>setErrorReporter</a>.<p>
073 *
074 * Different forms of script execution are supported. Scripts may be
075 * evaluated from the source directly, or first compiled and then later
076 * executed. Interactive execution is also supported.<p>
077 *
078 * Some aspects of script execution, such as type conversions and
079 * object creation, may be accessed directly through methods of
080 * Context.
081 */
082 public class Context {
083
084 /**
085 * Create a new Context.
086 *
087 * Note that the Context must be associated with a thread before
088 * it can be used to execute a script.
089 */
090 public Context() {
091 setLanguageVersion(VERSION_DEFAULT);
092 }
093
094 /**
095 * Get a context associated with the current thread, creating
096 * one if need be.
097 *
098 * The Context stores the execution state of the JavaScript
099 * engine, so it is required that the context be entered
100 * before execution may begin. Once a thread has entered
101 * a Context, then getCurrentContext() may be called to find
102 * the context that is associated with the current thread.
103 * <p>
104 * Calling <code>enter()</code> will
105 * return either the Context currently associated with the
106 * thread, or will create a new context and associate it
107 * with the current thread. Each call to <code>enter()</code>
108 * must have a matching call to <code>exit()</code>. For example,
109 * <pre>
110 * Context cx = Context.enter();
111 * try {
112 * ...
113 * cx.evaluateString(...);
114 * }
115 * finally { Context.exit(); }
116 * </pre>
117 * @return a Context associated with the current thread
118 */
119 public static Context enter() {
120 return enter(null);
121 }
122
123 /**
124 * Get a Context associated with the current thread, using
125 * the given Context if need be.
126 * <p>
127 * The same as <code>enter()</code> except that <code>cx</code>
128 * is associated with the current thread and returned if
129 * the current thread has no associated context and <code>cx</code>
130 * is not associated with any other thread.
131 * @param cx a Context to associate with the thread if possible
132 * @return a Context associated with the current thread
133 */
134 public static Context enter(Context cx) {
135
136 Context old = getCurrentContext();
137
138 if (cx == null) {
139 if (old != null) {
140 cx = old;
141 } else {
142 cx = new Context();
143 setThreadContext(cx);
144 }
145 } else {
146 if (cx.enterCount != 0) {
147 // The suplied context must be the context for
148 // the current thread if it is already entered
149 if (cx != old) {
150 throw new RuntimeException
151 ("Cannot enter Context active on another thread");
152 }
153 } else {
154 if (old != null) {
155 cx = old;
156 } else {
157 setThreadContext(cx);
158 }
159 }
160 }
161
162 ++cx.enterCount;
163
164 return cx;
165 }
166
167 /**
168 * Exit a block of code requiring a Context.
169 *
170 * Calling <code>exit()</code> will remove the association between
171 * the current thread and a Context if the prior call to
172 * <code>enter()</code> on this thread newly associated a Context
173 * with this thread.
174 * Once the current thread no longer has an associated Context,
175 * it cannot be used to execute JavaScript until it is again associated
176 * with a Context.
177 */
178 public static void exit() {
179 boolean released = false;
180 Context cx = getCurrentContext();
181 if (cx == null) {
182 throw new RuntimeException
183 ("Calling Context.exit without previous Context.enter");
184 }
185 if (Context.check && cx.enterCount < 1) Context.codeBug();
186 --cx.enterCount;
187 if (cx.enterCount == 0) {
188 released = true;
189 setThreadContext(null);
190 }
191 }
192
193 /**
194 * Get the current Context.
195 *
196 * The current Context is per-thread; this method looks up
197 * the Context associated with the current thread. <p>
198 *
199 * @return the Context associated with the current thread, or
200 * null if no context is associated with the current
201 * thread.
202 */
203 public static Context getCurrentContext() {
204 if (threadLocalCx != null) {
205 try {
206 return (Context)threadLocalGet.invoke(threadLocalCx, (Object[]) null);
207 } catch (Exception ex) { }
208 }
209 Thread t = Thread.currentThread();
210 return (Context) threadContexts.get(t);
211 }
212
213 private static void setThreadContext(Context cx) {
214 if (threadLocalCx != null) {
215 try {
216 threadLocalSet.invoke(threadLocalCx, new Object[] { cx });
217 return;
218 } catch (Exception ex) { }
219 }
220 Thread t = Thread.currentThread();
221 if (cx != null) {
222 threadContexts.put(t, cx);
223 } else {
224 threadContexts.remove(t);
225 }
226 }
227
228 /**
229 * Language versions
230 *
231 * All integral values are reserved for future version numbers.
232 */
233
234 /**
235 * The unknown version.
236 */
237 public static final int VERSION_UNKNOWN = -1;
238
239 /**
240 * The default version.
241 */
242 public static final int VERSION_DEFAULT = 0;
243
244 /**
245 * JavaScript 1.0
246 */
247 public static final int VERSION_1_0 = 100;
248
249 /**
250 * JavaScript 1.1
251 */
252 public static final int VERSION_1_1 = 110;
253
254 /**
255 * JavaScript 1.2
256 */
257 public static final int VERSION_1_2 = 120;
258
259 /**
260 * JavaScript 1.3
261 */
262 public static final int VERSION_1_3 = 130;
263
264 /**
265 * JavaScript 1.4
266 */
267 public static final int VERSION_1_4 = 140;
268
269 /**
270 * JavaScript 1.5
271 */
272 public static final int VERSION_1_5 = 150;
273
274 /**
275 * Get the current language version.
276 * <p>
277 * The language version number affects JavaScript semantics as detailed
278 * in the overview documentation.
279 *
280 * @return an integer that is one of VERSION_1_0, VERSION_1_1, etc.
281 */
282 public int getLanguageVersion() {
283 return version;
284 }
285
286 /**
287 * Set the language version.
288 *
289 * <p>
290 * Setting the language version will affect functions and scripts compiled
291 * subsequently. See the overview documentation for version-specific
292 * behavior.
293 *
294 * @param version the version as specified by VERSION_1_0, VERSION_1_1, etc.
295 */
296 public void setLanguageVersion(int version) {
297 this.version = version;
298 }
299
300 /**
301 * Get the current error reporter.
302 */
303 public ErrorReporter getErrorReporter() {
304 return errorReporter;
305 }
306
307 /**
308 * Change the current error reporter.
309 *
310 * @return the previous error reporter
311 */
312 public ErrorReporter setErrorReporter(ErrorReporter reporter) {
313 errorReporter = reporter;
314 return reporter;
315 }
316
317 /**
318 * Get the current locale. Returns the default locale if none has
319 * been set.
320 *
321 * @see java.util.Locale
322 */
323
324 public Locale getLocale() {
325 if (locale == null)
326 locale = Locale.getDefault();
327 return locale;
328 }
329
330 public static void reportWarning(@NotNull String message, @NotNull CodePosition startPosition, @NotNull CodePosition endPosition)
331 {
332 Context cx = Context.getContext();
333 cx.getErrorReporter().warning(message, startPosition, endPosition);
334 }
335
336 public static void reportError(@NotNull String message, @NotNull CodePosition startPosition, @NotNull CodePosition endPosition)
337 {
338 Context cx = getCurrentContext();
339 if (cx != null) {
340 cx.errorCount++;
341 cx.getErrorReporter().error(message, startPosition, endPosition);
342 } else {
343 throw new EvaluatorException(message);
344 }
345 }
346
347 /**
348 * if hasFeature(FEATURE_NON_ECMA_GET_YEAR) returns true,
349 * Date.prototype.getYear subtructs 1900 only if 1900 <= date < 2000
350 * in deviation with Ecma B.2.4
351 */
352 public static final int FEATURE_NON_ECMA_GET_YEAR = 1;
353
354 /**
355 * if hasFeature(FEATURE_MEMBER_EXPR_AS_FUNCTION_NAME) returns true,
356 * allow 'function <MemberExpression>(...) { ... }' to be syntax sugar for
357 * '<MemberExpression> = function(...) { ... }', when <MemberExpression>
358 * is not simply identifier.
359 * See Ecma-262, section 11.2 for definition of <MemberExpression>
360 */
361 public static final int FEATURE_MEMBER_EXPR_AS_FUNCTION_NAME = 2;
362
363 /**
364 * if hasFeature(RESERVED_KEYWORD_AS_IDENTIFIER) returns true,
365 * treat future reserved keyword (see Ecma-262, section 7.5.3) as ordinary
366 * identifiers but warn about this usage
367 */
368 public static final int FEATURE_RESERVED_KEYWORD_AS_IDENTIFIER = 3;
369
370 /**
371 * if hasFeature(FEATURE_TO_STRING_AS_SOURCE) returns true,
372 * calling toString on JS objects gives JS source with code to create an
373 * object with all enumeratable fields of the original object instead of
374 * printing "[object <object-type>]".
375 * By default {@link #hasFeature(int)} returns true only if
376 * the current JS version is set to {@link #VERSION_1_2}.
377 */
378 public static final int FEATURE_TO_STRING_AS_SOURCE = 4;
379
380 /**
381 * Controls certain aspects of script semantics.
382 * Should be overwritten to alter default behavior.
383 * @param featureIndex feature index to check
384 * @return true if the <code>featureIndex</code> feature is turned on
385 * @see #FEATURE_NON_ECMA_GET_YEAR
386 * @see #FEATURE_MEMBER_EXPR_AS_FUNCTION_NAME
387 * @see #FEATURE_RESERVED_KEYWORD_AS_IDENTIFIER
388 * @see #FEATURE_TO_STRING_AS_SOURCE
389 */
390 public boolean hasFeature(int featureIndex) {
391 switch (featureIndex) {
392 case FEATURE_NON_ECMA_GET_YEAR:
393 /*
394 * During the great date rewrite of 1.3, we tried to track the
395 * evolving ECMA standard, which then had a definition of
396 * getYear which always subtracted 1900. Which we
397 * implemented, not realizing that it was incompatible with
398 * the old behavior... now, rather than thrash the behavior
399 * yet again, we've decided to leave it with the - 1900
400 * behavior and point people to the getFullYear method. But
401 * we try to protect existing scripts that have specified a
402 * version...
403 */
404 return (version == Context.VERSION_1_0
405 || version == Context.VERSION_1_1
406 || version == Context.VERSION_1_2);
407
408 case FEATURE_MEMBER_EXPR_AS_FUNCTION_NAME:
409 return false;
410
411 case FEATURE_RESERVED_KEYWORD_AS_IDENTIFIER:
412 return false;
413
414 case FEATURE_TO_STRING_AS_SOURCE:
415 return version == VERSION_1_2;
416 }
417 // It is a bug to call the method with unknown featureIndex
418 throw new IllegalArgumentException();
419 }
420
421 /********** end of API **********/
422
423 static String getMessage0(String messageId) {
424 return getMessage(messageId, null);
425 }
426
427 static String getMessage2(String messageId, Object arg1, Object arg2) {
428 Object[] arguments = {arg1, arg2};
429 return getMessage(messageId, arguments);
430 }
431
432 /**
433 * Internal method that reports an error for missing calls to
434 * enter().
435 */
436 static Context getContext() {
437 Context cx = getCurrentContext();
438 if (cx == null) {
439 throw new RuntimeException(
440 "No Context associated with current Thread");
441 }
442 return cx;
443 }
444
445 static String getMessage(String messageId, Object[] arguments) {
446 String formatString;
447 try {
448 formatString = messages.getString(messageId);
449 } catch (MissingResourceException mre) {
450 throw new RuntimeException("No message resource found for message property " + messageId);
451 }
452
453 MessageFormat formatter = new MessageFormat(formatString);
454 return formatter.format(arguments);
455 }
456
457 // debug flags
458 static final boolean printTrees = true;
459 static final boolean printICode = true;
460
461 // Rudimentary support for Design-by-Contract
462 static void codeBug() {
463 throw new RuntimeException("FAILED ASSERTION");
464 }
465
466 static final boolean check = true;
467
468 private static MessagesBundle messages = new MessagesBundle();
469 private static Hashtable threadContexts = new Hashtable(11);
470 private static Object threadLocalCx;
471 private static Method threadLocalGet;
472 private static Method threadLocalSet;
473
474 int version;
475 int errorCount;
476
477 private ErrorReporter errorReporter;
478 private Locale locale;
479 private boolean generatingDebug;
480 private boolean generatingDebugChanged;
481 private boolean generatingSource=true;
482 private boolean compileFunctionsWithDynamicScopeFlag;
483 private int enterCount;
484 private Object[] listeners;
485 private Hashtable hashtable;
486 private ClassLoader applicationClassLoader;
487
488 /**
489 * This is the list of names of objects forcing the creation of
490 * function activation records.
491 */
492 private Hashtable activationNames;
493
494 // For instruction counting (interpreter only)
495 int instructionCount;
496 int instructionThreshold;
497 }