blob: ec41a9204ff13dff3824f66204635248c32b9cff [file] [log] [blame]
/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
*
* The contents of this file are subject to the Netscape Public
* License Version 1.1 (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.mozilla.org/NPL/
*
* Software distributed under the License is distributed on an "AS
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
* implied. See the License for the specific language governing
* rights and limitations under the License.
*
* The Original Code is Rhino code, released
* May 6, 1999.
*
* The Initial Developer of the Original Code is Netscape
* Communications Corporation. Portions created by Netscape are
* Copyright (C) 1997-2000 Netscape Communications Corporation. All
* Rights Reserved.
*
* Contributor(s):
*
* Patrick Beard
* Norris Boyd
* Igor Bukanov
* Brendan Eich
* Roger Lawrence
* Mike McCabe
* Ian D. Stewart
* Andi Vajda
* Andrew Wason
* Kemal Bayram
*
* Alternatively, the contents of this file may be used under the
* terms of the GNU Public License (the "GPL"), in which case the
* provisions of the GPL are applicable instead of those above.
* If you wish to allow use of your version of this file only
* under the terms of the GPL and not to allow others to use your
* version of this file under the NPL, indicate your decision by
* deleting the provisions above and replace them with the notice
* and other provisions required by the GPL. If you do not delete
* the provisions above, a recipient may use your version of this
* file under either the NPL or the GPL.
*/
// Modified by Google
// API class
package com.google.gwt.dev.js.rhino;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.lang.reflect.Method;
import java.text.MessageFormat;
import java.util.Hashtable;
import java.util.Locale;
import java.util.ResourceBundle;
/**
* This class represents the runtime context of an executing script.
*
* Before executing a script, an instance of Context must be created
* and associated with the thread that will be executing the script.
* The Context will be used to store information about the executing
* of the script such as the call stack. Contexts are associated with
* the current thread using the <a href="#enter()">enter()</a> method.<p>
*
* The behavior of the execution engine may be altered through methods
* such as <a href="#setLanguageVersion>setLanguageVersion</a> and
* <a href="#setErrorReporter>setErrorReporter</a>.<p>
*
* Different forms of script execution are supported. Scripts may be
* evaluated from the source directly, or first compiled and then later
* executed. Interactive execution is also supported.<p>
*
* Some aspects of script execution, such as type conversions and
* object creation, may be accessed directly through methods of
* Context.
*
* @see Scriptable
* @author Norris Boyd
* @author Brendan Eich
*/
public class Context {
public static final String languageVersionProperty = "language version";
public static final String errorReporterProperty = "error reporter";
/**
* Create a new Context.
*
* Note that the Context must be associated with a thread before
* it can be used to execute a script.
*
* @see org.mozilla.javascript.Context#enter
*/
public Context() {
setLanguageVersion(VERSION_DEFAULT);
}
/**
* Get a context associated with the current thread, creating
* one if need be.
*
* The Context stores the execution state of the JavaScript
* engine, so it is required that the context be entered
* before execution may begin. Once a thread has entered
* a Context, then getCurrentContext() may be called to find
* the context that is associated with the current thread.
* <p>
* Calling <code>enter()</code> will
* return either the Context currently associated with the
* thread, or will create a new context and associate it
* with the current thread. Each call to <code>enter()</code>
* must have a matching call to <code>exit()</code>. For example,
* <pre>
* Context cx = Context.enter();
* try {
* ...
* cx.evaluateString(...);
* }
* finally { Context.exit(); }
* </pre>
* @return a Context associated with the current thread
* @see org.mozilla.javascript.Context#getCurrentContext
* @see org.mozilla.javascript.Context#exit
*/
public static Context enter() {
return enter(null);
}
/**
* Get a Context associated with the current thread, using
* the given Context if need be.
* <p>
* The same as <code>enter()</code> except that <code>cx</code>
* is associated with the current thread and returned if
* the current thread has no associated context and <code>cx</code>
* is not associated with any other thread.
* @param cx a Context to associate with the thread if possible
* @return a Context associated with the current thread
*/
public static Context enter(Context cx) {
Context old = getCurrentContext();
if (cx == null) {
if (old != null) {
cx = old;
} else {
cx = new Context();
setThreadContext(cx);
}
} else {
if (cx.enterCount != 0) {
// The suplied context must be the context for
// the current thread if it is already entered
if (cx != old) {
throw new RuntimeException
("Cannot enter Context active on another thread");
}
} else {
if (old != null) {
cx = old;
} else {
setThreadContext(cx);
}
}
}
++cx.enterCount;
return cx;
}
/**
* Exit a block of code requiring a Context.
*
* Calling <code>exit()</code> will remove the association between
* the current thread and a Context if the prior call to
* <code>enter()</code> on this thread newly associated a Context
* with this thread.
* Once the current thread no longer has an associated Context,
* it cannot be used to execute JavaScript until it is again associated
* with a Context.
*
* @see org.mozilla.javascript.Context#enter
*/
public static void exit() {
boolean released = false;
Context cx = getCurrentContext();
if (cx == null) {
throw new RuntimeException
("Calling Context.exit without previous Context.enter");
}
if (Context.check && cx.enterCount < 1) Context.codeBug();
--cx.enterCount;
if (cx.enterCount == 0) {
released = true;
setThreadContext(null);
}
}
/**
* Get the current Context.
*
* The current Context is per-thread; this method looks up
* the Context associated with the current thread. <p>
*
* @return the Context associated with the current thread, or
* null if no context is associated with the current
* thread.
* @see org.mozilla.javascript.Context#enter
* @see org.mozilla.javascript.Context#exit
*/
public static Context getCurrentContext() {
if (threadLocalCx != null) {
try {
return (Context)threadLocalGet.invoke(threadLocalCx, (Object[]) null);
} catch (Exception ex) { }
}
Thread t = Thread.currentThread();
return (Context) threadContexts.get(t);
}
private static void setThreadContext(Context cx) {
if (threadLocalCx != null) {
try {
threadLocalSet.invoke(threadLocalCx, new Object[] { cx });
return;
} catch (Exception ex) { }
}
Thread t = Thread.currentThread();
if (cx != null) {
threadContexts.put(t, cx);
} else {
threadContexts.remove(t);
}
}
/**
* Language versions
*
* All integral values are reserved for future version numbers.
*/
/**
* The unknown version.
*/
public static final int VERSION_UNKNOWN = -1;
/**
* The default version.
*/
public static final int VERSION_DEFAULT = 0;
/**
* JavaScript 1.0
*/
public static final int VERSION_1_0 = 100;
/**
* JavaScript 1.1
*/
public static final int VERSION_1_1 = 110;
/**
* JavaScript 1.2
*/
public static final int VERSION_1_2 = 120;
/**
* JavaScript 1.3
*/
public static final int VERSION_1_3 = 130;
/**
* JavaScript 1.4
*/
public static final int VERSION_1_4 = 140;
/**
* JavaScript 1.5
*/
public static final int VERSION_1_5 = 150;
/**
* Get the current language version.
* <p>
* The language version number affects JavaScript semantics as detailed
* in the overview documentation.
*
* @return an integer that is one of VERSION_1_0, VERSION_1_1, etc.
*/
public int getLanguageVersion() {
return version;
}
/**
* Set the language version.
*
* <p>
* Setting the language version will affect functions and scripts compiled
* subsequently. See the overview documentation for version-specific
* behavior.
*
* @param version the version as specified by VERSION_1_0, VERSION_1_1, etc.
*/
public void setLanguageVersion(int version) {
this.version = version;
}
/**
* Get the implementation version.
*
* <p>
* The implementation version is of the form
* <pre>
* "<i>name langVer</i> <code>release</code> <i>relNum date</i>"
* </pre>
* where <i>name</i> is the name of the product, <i>langVer</i> is
* the language version, <i>relNum</i> is the release number, and
* <i>date</i> is the release date for that specific
* release in the form "yyyy mm dd".
*
* @return a string that encodes the product, language version, release
* number, and date.
*/
public String getImplementationVersion() {
return "Rhino 1.5 release 4.1 2003 04 21";
}
/**
* Get the current error reporter.
*
* @see org.mozilla.javascript.ErrorReporter
*/
public ErrorReporter getErrorReporter() {
return errorReporter;
}
/**
* Change the current error reporter.
*
* @return the previous error reporter
* @see org.mozilla.javascript.ErrorReporter
*/
public ErrorReporter setErrorReporter(ErrorReporter reporter) {
errorReporter = reporter;
return reporter;
}
/**
* Get the current locale. Returns the default locale if none has
* been set.
*
* @see java.util.Locale
*/
public Locale getLocale() {
if (locale == null)
locale = Locale.getDefault();
return locale;
}
/**
* Set the current locale.
*
* @see java.util.Locale
*/
public Locale setLocale(Locale loc) {
Locale result = locale;
locale = loc;
return result;
}
/**
* Notify any registered listeners that a bounded property has changed
* @see #addPropertyChangeListener(java.beans.PropertyChangeListener)
* @see #removePropertyChangeListener(java.beans.PropertyChangeListener)
* @see java.beans.PropertyChangeListener
* @see java.beans.PropertyChangeEvent
* @param property the bound property
* @param oldValue the old value
* @param newVale the new value
*/
void firePropertyChange(String property, Object oldValue,
Object newValue)
{
Object[] array = listeners;
if (array != null) {
firePropertyChangeImpl(array, property, oldValue, newValue);
}
}
private void firePropertyChangeImpl(Object[] array, String property,
Object oldValue, Object newValue)
{
for (int i = array.length; i-- != 0;) {
Object obj = array[i];
if (obj instanceof PropertyChangeListener) {
PropertyChangeListener l = (PropertyChangeListener)obj;
l.propertyChange(new PropertyChangeEvent(
this, property, oldValue, newValue));
}
}
}
/**
* Report a warning using the error reporter for the current thread.
*
* @param message the warning message to report
* @param sourceName a string describing the source, such as a filename
* @param lineno the starting line number
* @param lineSource the text of the line (may be null)
* @param lineOffset the offset into lineSource where problem was detected
* @see org.mozilla.javascript.ErrorReporter
*/
public static void reportWarning(String message, String sourceName,
int lineno, String lineSource,
int lineOffset)
{
Context cx = Context.getContext();
cx.getErrorReporter().warning(message, sourceName, lineno,
lineSource, lineOffset);
}
/**
* Report a warning using the error reporter for the current thread.
*
* @param message the warning message to report
* @see org.mozilla.javascript.ErrorReporter
*/
/*
public static void reportWarning(String message) {
int[] linep = { 0 };
String filename = getSourcePositionFromStack(linep);
Context.reportWarning(message, filename, linep[0], null, 0);
}
*/
/**
* Report an error using the error reporter for the current thread.
*
* @param message the error message to report
* @param sourceName a string describing the source, such as a filename
* @param lineno the starting line number
* @param lineSource the text of the line (may be null)
* @param lineOffset the offset into lineSource where problem was detected
* @see org.mozilla.javascript.ErrorReporter
*/
public static void reportError(String message, String sourceName,
int lineno, String lineSource,
int lineOffset)
{
Context cx = getCurrentContext();
if (cx != null) {
cx.errorCount++;
cx.getErrorReporter().error(message, sourceName, lineno,
lineSource, lineOffset);
} else {
throw new EvaluatorException(message);
}
}
/**
* Report an error using the error reporter for the current thread.
*
* @param message the error message to report
* @see org.mozilla.javascript.ErrorReporter
*/
/*
public static void reportError(String message) {
int[] linep = { 0 };
String filename = getSourcePositionFromStack(linep);
Context.reportError(message, filename, linep[0], null, 0);
}
*/
/**
* Report a runtime error using the error reporter for the current thread.
*
* @param message the error message to report
* @param sourceName a string describing the source, such as a filename
* @param lineno the starting line number
* @param lineSource the text of the line (may be null)
* @param lineOffset the offset into lineSource where problem was detected
* @return a runtime exception that will be thrown to terminate the
* execution of the script
* @see org.mozilla.javascript.ErrorReporter
*/
/*
public static EvaluatorException reportRuntimeError(String message,
String sourceName,
int lineno,
String lineSource,
int lineOffset)
{
Context cx = getCurrentContext();
if (cx != null) {
cx.errorCount++;
return cx.getErrorReporter().
runtimeError(message, sourceName, lineno,
lineSource, lineOffset);
} else {
throw new EvaluatorException(message);
}
}
static EvaluatorException reportRuntimeError0(String messageId) {
return reportRuntimeError(getMessage0(messageId));
}
static EvaluatorException reportRuntimeError1
(String messageId, Object arg1)
{
return reportRuntimeError(getMessage1(messageId, arg1));
}
static EvaluatorException reportRuntimeError2
(String messageId, Object arg1, Object arg2)
{
return reportRuntimeError(getMessage2(messageId, arg1, arg2));
}
static EvaluatorException reportRuntimeError3
(String messageId, Object arg1, Object arg2, Object arg3)
{
return reportRuntimeError(getMessage3(messageId, arg1, arg2, arg3));
}
*/
/**
* Report a runtime error using the error reporter for the current thread.
*
* @param message the error message to report
* @see org.mozilla.javascript.ErrorReporter
*/
/*
public static EvaluatorException reportRuntimeError(String message) {
int[] linep = { 0 };
String filename = getSourcePositionFromStack(linep);
return Context.reportRuntimeError(message, filename, linep[0], null, 0);
}
*/
/**
* Get a value corresponding to a key.
* <p>
* Since the Context is associated with a thread it can be
* used to maintain values that can be later retrieved using
* the current thread.
* <p>
* Note that the values are maintained with the Context, so
* if the Context is disassociated from the thread the values
* cannot be retreived. Also, if private data is to be maintained
* in this manner the key should be a java.lang.Object
* whose reference is not divulged to untrusted code.
* @param key the key used to lookup the value
* @return a value previously stored using putThreadLocal.
*/
public final Object getThreadLocal(Object key) {
if (hashtable == null)
return null;
return hashtable.get(key);
}
/**
* Put a value that can later be retrieved using a given key.
* <p>
* @param key the key used to index the value
* @param value the value to save
*/
public void putThreadLocal(Object key, Object value) {
if (hashtable == null)
hashtable = new Hashtable();
hashtable.put(key, value);
}
/**
* Remove values from thread-local storage.
* @param key the key for the entry to remove.
* @since 1.5 release 2
*/
public void removeThreadLocal(Object key) {
if (hashtable == null)
return;
hashtable.remove(key);
}
/**
* Return whether functions are compiled by this context using
* dynamic scope.
* <p>
* If functions are compiled with dynamic scope, then they execute
* in the scope of their caller, rather than in their parent scope.
* This is useful for sharing functions across multiple scopes.
* @since 1.5 Release 1
*/
public final boolean hasCompileFunctionsWithDynamicScope() {
return compileFunctionsWithDynamicScopeFlag;
}
/**
* Set whether functions compiled by this context should use
* dynamic scope.
* <p>
* @param flag if true, compile functions with dynamic scope
* @since 1.5 Release 1
*/
public void setCompileFunctionsWithDynamicScope(boolean flag) {
compileFunctionsWithDynamicScopeFlag = flag;
}
/**
* if hasFeature(FEATURE_NON_ECMA_GET_YEAR) returns true,
* Date.prototype.getYear subtructs 1900 only if 1900 <= date < 2000
* in deviation with Ecma B.2.4
*/
public static final int FEATURE_NON_ECMA_GET_YEAR = 1;
/**
* if hasFeature(FEATURE_MEMBER_EXPR_AS_FUNCTION_NAME) returns true,
* allow 'function <MemberExpression>(...) { ... }' to be syntax sugar for
* '<MemberExpression> = function(...) { ... }', when <MemberExpression>
* is not simply identifier.
* See Ecma-262, section 11.2 for definition of <MemberExpression>
*/
public static final int FEATURE_MEMBER_EXPR_AS_FUNCTION_NAME = 2;
/**
* if hasFeature(RESERVED_KEYWORD_AS_IDENTIFIER) returns true,
* treat future reserved keyword (see Ecma-262, section 7.5.3) as ordinary
* identifiers but warn about this usage
*/
public static final int FEATURE_RESERVED_KEYWORD_AS_IDENTIFIER = 3;
/**
* if hasFeature(FEATURE_TO_STRING_AS_SOURCE) returns true,
* calling toString on JS objects gives JS source with code to create an
* object with all enumeratable fields of the original object instead of
* printing "[object <object-type>]".
* By default {@link #hasFeature(int)} returns true only if
* the current JS version is set to {@link #VERSION_1_2}.
*/
public static final int FEATURE_TO_STRING_AS_SOURCE = 4;
/**
* Controls certain aspects of script semantics.
* Should be overwritten to alter default behavior.
* @param featureIndex feature index to check
* @return true if the <code>featureIndex</code> feature is turned on
* @see #FEATURE_NON_ECMA_GET_YEAR
* @see #FEATURE_MEMBER_EXPR_AS_FUNCTION_NAME
* @see #FEATURE_RESERVED_KEYWORD_AS_IDENTIFIER
* @see #FEATURE_TO_STRING_AS_SOURCE
*/
public boolean hasFeature(int featureIndex) {
switch (featureIndex) {
case FEATURE_NON_ECMA_GET_YEAR:
/*
* During the great date rewrite of 1.3, we tried to track the
* evolving ECMA standard, which then had a definition of
* getYear which always subtracted 1900. Which we
* implemented, not realizing that it was incompatible with
* the old behavior... now, rather than thrash the behavior
* yet again, we've decided to leave it with the - 1900
* behavior and point people to the getFullYear method. But
* we try to protect existing scripts that have specified a
* version...
*/
return (version == Context.VERSION_1_0
|| version == Context.VERSION_1_1
|| version == Context.VERSION_1_2);
case FEATURE_MEMBER_EXPR_AS_FUNCTION_NAME:
return false;
case FEATURE_RESERVED_KEYWORD_AS_IDENTIFIER:
return false;
case FEATURE_TO_STRING_AS_SOURCE:
return version == VERSION_1_2;
}
// It is a bug to call the method with unknown featureIndex
throw new IllegalArgumentException();
}
/********** end of API **********/
static String getMessage0(String messageId) {
return getMessage(messageId, null);
}
static String getMessage1(String messageId, Object arg1) {
Object[] arguments = {arg1};
return getMessage(messageId, arguments);
}
static String getMessage2(String messageId, Object arg1, Object arg2) {
Object[] arguments = {arg1, arg2};
return getMessage(messageId, arguments);
}
static String getMessage3
(String messageId, Object arg1, Object arg2, Object arg3) {
Object[] arguments = {arg1, arg2, arg3};
return getMessage(messageId, arguments);
}
/**
* Internal method that reports an error for missing calls to
* enter().
*/
static Context getContext() {
Context cx = getCurrentContext();
if (cx == null) {
throw new RuntimeException(
"No Context associated with current Thread");
}
return cx;
}
/* OPT there's a noticable delay for the first error! Maybe it'd
* make sense to use a ListResourceBundle instead of a properties
* file to avoid (synchronized) text parsing.
*/
// bruce: removed referenced to the initial "java" package name
// that used to be there due to a build artifact
static final String defaultResource =
"com.google.gwt.dev.js.rhino.Messages";
static String getMessage(String messageId, Object[] arguments) {
Context cx = getCurrentContext();
Locale locale = cx != null ? cx.getLocale() : Locale.getDefault();
// ResourceBundle does cacheing.
ResourceBundle rb = ResourceBundle.getBundle(defaultResource, locale);
String formatString;
try {
formatString = rb.getString(messageId);
} catch (java.util.MissingResourceException mre) {
throw new RuntimeException
("no message resource found for message property "+ messageId);
}
/*
* It's OK to format the string, even if 'arguments' is null;
* we need to format it anyway, to make double ''s collapse to
* single 's.
*/
// TODO: MessageFormat is not available on pJava
MessageFormat formatter = new MessageFormat(formatString);
return formatter.format(arguments);
}
// debug flags
static final boolean printTrees = true;
static final boolean printICode = true;
final boolean isVersionECMA1() {
return version == VERSION_DEFAULT || version >= VERSION_1_3;
}
// Rudimentary support for Design-by-Contract
static void codeBug() {
throw new RuntimeException("FAILED ASSERTION");
}
static final boolean check = true;
private static Hashtable threadContexts = new Hashtable(11);
private static Object threadLocalCx;
private static Method threadLocalGet;
private static Method threadLocalSet;
int version;
int errorCount;
private ErrorReporter errorReporter;
private Locale locale;
private boolean generatingDebug;
private boolean generatingDebugChanged;
private boolean generatingSource=true;
private boolean compileFunctionsWithDynamicScopeFlag;
private int enterCount;
private Object[] listeners;
private Hashtable hashtable;
private ClassLoader applicationClassLoader;
/**
* This is the list of names of objects forcing the creation of
* function activation records.
*/
private Hashtable activationNames;
// For instruction counting (interpreter only)
int instructionCount;
int instructionThreshold;
}