com.google.javascript.rhino.Context Maven / Gradle / Ivy
Show all versions of closure-compiler Show documentation
/*
*
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0
*
* The contents of this file are subject to the Mozilla 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/MPL/
*
* 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 the Initial Developer are Copyright (C) 1997-2000
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Norris Boyd
* Brendan Eich
*
* Alternatively, the contents of this file may be used under the terms of
* the GNU General Public License Version 2 or later (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
* MPL, indicate your decision by deleting the provisions above and replacing
* 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 MPL or the GPL.
*
* ***** END LICENSE BLOCK ***** */
// API class
package com.google.javascript.rhino;
import java.beans.*;
import java.io.*;
import java.util.Hashtable;
import java.util.Locale;
/**
* 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 {@link #enter()} method.
*
* 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.
*
* Some aspects of script execution, such as type conversions and
* object creation, may be accessed directly through methods of
* Context.
*
*/
public class Context
{
/**
* 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;
/**
* JavaScript 1.5
*/
public static final int VERSION_1_6 = 160;
/**
* Controls behaviour of Date.prototype.getYear().
* If hasFeature(FEATURE_NON_ECMA_GET_YEAR) returns true,
* Date.prototype.getYear subtructs 1900 only if 1900 <= date < 2000.
* The default behavior of {@link #hasFeature(int)} is always to subtruct
* 1900 as rquired by ECMAScript B.2.4.
*/
public static final int FEATURE_NON_ECMA_GET_YEAR = 1;
/**
* Control if member expression as function name extension is available.
* If hasFeature(FEATURE_MEMBER_EXPR_AS_FUNCTION_NAME) returns
* true, allow function memberExpression(args) { body } to be
* syntax sugar for memberExpression = function(args) { body },
* when memberExpression is not a simple identifier.
* See ECMAScript-262, section 11.2 for definition of memberExpression.
* By default {@link #hasFeature(int)} returns false.
*/
public static final int FEATURE_MEMBER_EXPR_AS_FUNCTION_NAME = 2;
/**
* Control if reserved keywords are treated as identifiers.
* 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.
*
* By default {@link #hasFeature(int)} returns false.
*/
public static final int FEATURE_RESERVED_KEYWORD_AS_IDENTIFIER = 3;
/**
* Control if toString() should returns the same result
* as toSource() when applied to objects and arrays.
* If hasFeature(FEATURE_TO_STRING_AS_SOURCE) returns true,
* calling toString() on JS objects gives the same result as
* calling toSource(). That is it returns JS source with code
* to create an object with all enumeratable fields of the original object.
*
* 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;
/**
* Control if properties __proto__ and __parent__
* are treated specially.
* If hasFeature(FEATURE_PARENT_PROTO_PROPRTIES) returns true,
* treat __parent__ and __proto__ as special properties.
*
* The properties allow to query and set scope and prototype chains for the
* objects. The special meaning of the properties is available
* only when they are used as the right hand side of the dot operator.
* For example, while x.__proto__ = y changes the prototype
* chain of the object x to point to y,
* x["__proto__"] = y simply assigns a new value to the property
* __proto__ in x even when the feature is on.
*
* By default {@link #hasFeature(int)} returns true.
*/
public static final int FEATURE_PARENT_PROTO_PROPRTIES = 5;
/**
* Control if support for E4X(ECMAScript for XML) extension is available.
* If hasFeature(FEATURE_E4X) returns true, the XML syntax is available.
*
* By default {@link #hasFeature(int)} returns true if
* the current JS version is set to {@link #VERSION_DEFAULT}
* or is greater then {@link #VERSION_1_6}.
* @since 1.6 Release 1
*/
public static final int FEATURE_E4X = 6;
/**
* Control if dynamic scope should be used for name access.
* If hasFeature(FEATURE_DYNAMIC_SCOPE) returns true, then the name lookup
* during name resolution will use the top scope of the script or function
* which is at the top of JS execution stack instead of the top scope of the
* script or function from the current stack frame if the top scope of
* the top stack frame contains the top scope of the current stack frame
* on its prototype chain.
*
* This is useful to define shared scope containing functions that can
* be called from scripts and functions using private scopes.
*
* By default {@link #hasFeature(int)} returns false.
* @since 1.6 Release 1
*/
public static final int FEATURE_DYNAMIC_SCOPE = 7;
/**
* Control if strict variable mode is enabled.
* When the feature is on Rhino reports runtime errors if assignment
* to a global variable that does not exist is executed. When the feature
* is off such assignments creates new variable in the global scope as
* required by ECMA 262.
*
* By default {@link #hasFeature(int)} returns false.
* @since 1.6 Release 1
*/
public static final int FEATURE_STRICT_VARS = 8;
/**
* Control if strict eval mode is enabled.
* When the feature is on Rhino reports runtime errors if non-string
* argument is passed to the eval function. When the feature is off
* eval simply return non-string argument as is without performing any
* evaluation as required by ECMA 262.
*
* By default {@link #hasFeature(int)} returns false.
* @since 1.6 Release 1
*/
public static final int FEATURE_STRICT_EVAL = 9;
/**
* When the feature is on Rhino will add a "fileName" and "lineNumber"
* properties to Error objects automatically. When the feature is off, you
* have to explicitly pass them as the second and third argument to the
* Error constructor. Note that neither behaviour is fully ECMA 262
* compliant (as 262 doesn't specify a three-arg constructor), but keeping
* the feature off results in Error objects that don't have
* additional non-ECMA properties when constructed using the ECMA-defined
* single-arg constructor and is thus desirable if a stricter ECMA
* compliance is desired, specifically adherence to the point 15.11.5. of
* the standard.
*
* By default {@link #hasFeature(int)} returns false.
* @since 1.6 Release 6
*/
public static final int FEATURE_LOCATION_INFORMATION_IN_ERROR = 10;
/**
* Controls whether JS 1.5 'strict mode' is enabled.
* When the feature is on, Rhino reports more than a dozen different
* warnings. When the feature is off, these warnings are not generated.
* FEATURE_STRICT_MODE implies FEATURE_STRICT_VARS and FEATURE_STRICT_EVAL.
*
* By default {@link #hasFeature(int)} returns false.
* @since 1.6 Release 6
*/
public static final int FEATURE_STRICT_MODE = 11;
/**
* Controls whether a warning should be treated as an error.
* @since 1.6 Release 6
*/
public static final int FEATURE_WARNING_AS_ERROR = 12;
public static final String languageVersionProperty = "language version";
public static final String errorReporterProperty = "error reporter";
/**
* Convinient value to use as zero-length array of objects.
*/
public static final Object[] emptyArgs = ScriptRuntime.emptyArgs;
/**
* Create a new Context.
*
* Note that the Context must be associated with a thread before
* it can be used to execute a script.
*
* @see #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.
*
* Calling enter()
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 enter()
* must have a matching call to exit()
. For example,
*
* Context cx = Context.enter();
* try {
* ...
* cx.evaluateString(...);
* }
* finally { Context.exit(); }
*
* @return a Context associated with the current thread
* @see #getCurrentContext
* @see #exit
*/
public static Context enter() {
return enter(null);
}
/**
* Get a Context associated with the current thread, using
* the given Context if need be.
*
* The same as enter()
except that cx
* is associated with the current thread and returned if
* the current thread has no associated context and cx
* 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 exit()
will remove the association between
* the current thread and a Context if the prior call to
* enter()
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 #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 (cx.enterCount < 1) Kit.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.
*
* @return the Context associated with the current thread, or
* null if no context is associated with the current
* thread.
*/
public static Context getCurrentContext() {
return threadContexts.get();
}
private static void setThreadContext(Context cx) {
threadContexts.set(cx);
}
private static ThreadLocal threadContexts
= new ThreadLocal();
/**
* Checks if this is a sealed Context. A sealed Context instance does not
* allow to modify any of its properties and will throw an exception
* on any such attempt.
* @see #seal(Object sealKey)
*/
public final boolean isSealed()
{
return sealed;
}
/**
* Seal this Context object so any attempt to modify any of its properties
* including calling {@link #enter()} and {@link #exit()} methods will
* throw an exception.
*
* If sealKey is not null, calling
* {@link #unseal(Object sealKey)} with the same key unseals
* the object. If sealKey is null, unsealing is no longer possible.
*
* @see #isSealed()
* @see #unseal(Object)
*/
public final void seal(Object sealKey)
{
if (sealed) onSealedMutation();
sealed = true;
this.sealKey = sealKey;
}
/**
* Unseal previously sealed Context object.
* The sealKey argument should not be null and should match
* sealKey suplied with the last call to
* {@link #seal(Object)} or an exception will be thrown.
*
* @see #isSealed()
* @see #seal(Object sealKey)
*/
public final void unseal(Object sealKey)
{
if (sealKey == null) throw new IllegalArgumentException();
if (this.sealKey != sealKey) throw new IllegalArgumentException();
if (!sealed) throw new IllegalStateException();
sealed = false;
this.sealKey = null;
}
static void onSealedMutation()
{
throw new IllegalStateException();
}
/**
* Get the current language version.
*
* 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 final int getLanguageVersion()
{
return version;
}
/**
* Set the language version.
*
*
* 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)
{
if (sealed) onSealedMutation();
checkLanguageVersion(version);
Object listeners = propertyListeners;
if (listeners != null && version != this.version) {
firePropertyChangeImpl(listeners, languageVersionProperty,
new Integer(this.version),
new Integer(version));
}
this.version = version;
}
public static boolean isValidLanguageVersion(int version)
{
switch (version) {
case VERSION_DEFAULT:
case VERSION_1_0:
case VERSION_1_1:
case VERSION_1_2:
case VERSION_1_3:
case VERSION_1_4:
case VERSION_1_5:
case VERSION_1_6:
return true;
}
return false;
}
public static void checkLanguageVersion(int version)
{
if (isValidLanguageVersion(version)) {
return;
}
throw new IllegalArgumentException("Bad language version: "+version);
}
/**
* Get the implementation version.
*
*
* The implementation version is of the form
*
* "name langVer release
relNum date"
*
* where name is the name of the product, langVer is
* the language version, relNum is the release number, and
* date 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 final String getImplementationVersion()
{
// XXX Probably it would be better to embed this directly into source
// with special build preprocessing but that would require some ant
// tweaking and then replacing token in resource files was simpler
if (implementationVersion == null) {
implementationVersion
= ScriptRuntime.getMessage0("implementation.version");
}
return implementationVersion;
}
/**
* Get the current error reporter.
*
* @see com.google.javascript.rhino.ErrorReporter
*/
public final ErrorReporter getErrorReporter()
{
return errorReporter;
}
/**
* Change the current error reporter.
*
* @return the previous error reporter
* @see com.google.javascript.rhino.ErrorReporter
*/
public final ErrorReporter setErrorReporter(ErrorReporter reporter)
{
if (sealed) onSealedMutation();
if (reporter == null) throw new IllegalArgumentException();
ErrorReporter old = getErrorReporter();
if (reporter == old) {
return old;
}
Object listeners = propertyListeners;
if (listeners != null) {
firePropertyChangeImpl(listeners, errorReporterProperty,
old, reporter);
}
this.errorReporter = reporter;
return old;
}
/**
* Get the current locale. Returns the default locale if none has
* been set.
*
* @see java.util.Locale
*/
public final Locale getLocale()
{
if (locale == null)
locale = Locale.getDefault();
return locale;
}
/**
* Set the current locale.
*
* @see java.util.Locale
*/
public final Locale setLocale(Locale loc)
{
if (sealed) onSealedMutation();
Locale result = locale;
locale = loc;
return result;
}
/**
* Register an object to receive notifications when a bound property
* has changed
* @see java.beans.PropertyChangeEvent
* @see #removePropertyChangeListener(java.beans.PropertyChangeListener)
* @param l the listener
*/
public final void addPropertyChangeListener(PropertyChangeListener l)
{
if (sealed) onSealedMutation();
propertyListeners = Kit.addListener(propertyListeners, l);
}
/**
* Remove an object from the list of objects registered to receive
* notification of changes to a bounded property
* @see java.beans.PropertyChangeEvent
* @see #addPropertyChangeListener(java.beans.PropertyChangeListener)
* @param l the listener
*/
public final void removePropertyChangeListener(PropertyChangeListener l)
{
if (sealed) onSealedMutation();
propertyListeners = Kit.removeListener(propertyListeners, l);
}
/**
* 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 newValue the new value
*/
final void firePropertyChange(String property, Object oldValue,
Object newValue)
{
Object listeners = propertyListeners;
if (listeners != null) {
firePropertyChangeImpl(listeners, property, oldValue, newValue);
}
}
private void firePropertyChangeImpl(Object listeners, String property,
Object oldValue, Object newValue)
{
for (int i = 0; ; ++i) {
Object l = Kit.getListener(listeners, i);
if (l == null)
break;
if (l instanceof PropertyChangeListener) {
PropertyChangeListener pcl = (PropertyChangeListener)l;
pcl.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 com.google.javascript.rhino.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 com.google.javascript.rhino.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 com.google.javascript.rhino.ErrorReporter
*/
public static void reportError(String message, String sourceName,
int lineno, String lineSource,
int lineOffset)
{
Context cx = getCurrentContext();
if (cx != null) {
cx.getErrorReporter().error(message, sourceName, lineno,
lineSource, lineOffset);
} else {
throw new EvaluatorException(message, sourceName, lineno,
lineSource, lineOffset);
}
}
/**
* Report an error using the error reporter for the current thread.
*
* @param message the error message to report
* @see com.google.javascript.rhino.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 com.google.javascript.rhino.ErrorReporter
*/
public static EvaluatorException reportRuntimeError(String message,
String sourceName,
int lineno,
String lineSource,
int lineOffset)
{
Context cx = getCurrentContext();
if (cx != null) {
return cx.getErrorReporter().
runtimeError(message, sourceName, lineno,
lineSource, lineOffset);
} else {
throw new EvaluatorException(message, sourceName, lineno,
lineSource, lineOffset);
}
}
static EvaluatorException reportRuntimeError0(String messageId)
{
String msg = ScriptRuntime.getMessage0(messageId);
return reportRuntimeError(msg);
}
static EvaluatorException reportRuntimeError1(String messageId,
Object arg1)
{
String msg = ScriptRuntime.getMessage1(messageId, arg1);
return reportRuntimeError(msg);
}
static EvaluatorException reportRuntimeError2(String messageId,
Object arg1, Object arg2)
{
String msg = ScriptRuntime.getMessage2(messageId, arg1, arg2);
return reportRuntimeError(msg);
}
static EvaluatorException reportRuntimeError3(String messageId,
Object arg1, Object arg2,
Object arg3)
{
String msg = ScriptRuntime.getMessage3(messageId, arg1, arg2, arg3);
return reportRuntimeError(msg);
}
static EvaluatorException reportRuntimeError4(String messageId,
Object arg1, Object arg2,
Object arg3, Object arg4)
{
String msg
= ScriptRuntime.getMessage4(messageId, arg1, arg2, arg3, arg4);
return reportRuntimeError(msg);
}
/**
* Report a runtime error using the error reporter for the current thread.
*
* @param message the error message to report
* @see com.google.javascript.rhino.ErrorReporter
*/
public static EvaluatorException reportRuntimeError(String message)
{
int[] linep = { 0 };
String filename = getSourcePositionFromStack(linep);
return Context.reportRuntimeError(message, filename, linep[0], null, 0);
}
/**
* Tell whether debug information is being generated.
* @since 1.3
*/
public final boolean isGeneratingDebug()
{
return generatingDebug;
}
/**
* Tell whether source information is being generated.
* @since 1.3
*/
public final boolean isGeneratingSource()
{
return generatingSource;
}
/**
* Specify whether or not source information should be generated.
*
* Without source information, evaluating the "toString" method
* on JavaScript functions produces only "[native code]" for
* the body of the function.
* Note that code generated without source is not fully ECMA
* conformant.
* @since 1.3
*/
public final void setGeneratingSource(boolean generatingSource)
{
if (sealed) onSealedMutation();
this.generatingSource = generatingSource;
}
/**
* Get the current optimization level.
*
* The optimization level is expressed as an integer between -1 and
* 9.
* @since 1.3
*
*/
public final int getOptimizationLevel()
{
return optimizationLevel;
}
public static boolean isValidOptimizationLevel(int optimizationLevel)
{
return -1 <= optimizationLevel && optimizationLevel <= 9;
}
public static void checkOptimizationLevel(int optimizationLevel)
{
if (isValidOptimizationLevel(optimizationLevel)) {
return;
}
throw new IllegalArgumentException(
"Optimization level outside [-1..9]: "+optimizationLevel);
}
/**
* Get a value corresponding to a key.
*
* Since the Context is associated with a thread it can be
* used to maintain values that can be later retrieved using
* the current thread.
*
* 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.
*
* @param key the key used to index the value
* @param value the value to save
*/
public final void putThreadLocal(Object key, Object value)
{
if (sealed) onSealedMutation();
if (hashtable == null)
hashtable = new Hashtable