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    }