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