1 /* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2  *
3  * This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 import org.mozilla.javascript.*;
8 
9 /**
10  * Example of controlling the JavaScript with multiple scopes and threads.
11  */
12 public class DynamicScopes {
13 
14     static boolean useDynamicScope;
15 
16     static class MyFactory extends ContextFactory
17     {
18         @Override
hasFeature(Context cx, int featureIndex)19         protected boolean hasFeature(Context cx, int featureIndex)
20         {
21             if (featureIndex == Context.FEATURE_DYNAMIC_SCOPE) {
22                 return useDynamicScope;
23             }
24             return super.hasFeature(cx, featureIndex);
25         }
26     }
27 
28     static {
ContextFactory.initGlobal(new MyFactory())29         ContextFactory.initGlobal(new MyFactory());
30     }
31 
32 
33     /**
34      * Main entry point.
35      *
36      * Set up the shared scope and then spawn new threads that execute
37      * relative to that shared scope. Try to run functions with and
38      * without dynamic scope to see the effect.
39      *
40      * The expected output is
41      * <pre>
42      * sharedScope
43      * nested:sharedScope
44      * sharedScope
45      * nested:sharedScope
46      * sharedScope
47      * nested:sharedScope
48      * thread0
49      * nested:thread0
50      * thread1
51      * nested:thread1
52      * thread2
53      * nested:thread2
54      * </pre>
55      * The final three lines may be permuted in any order depending on
56      * thread scheduling.
57      */
main(String[] args)58     public static void main(String[] args)
59     {
60         Context cx = Context.enter();
61         try {
62             // Precompile source only once
63             String source = ""
64                             +"var x = 'sharedScope';\n"
65                             +"function f() { return x; }\n"
66                             // Dynamic scope works with nested function too
67                             +"function initClosure(prefix) {\n"
68                             +"    return function test() { return prefix+x; }\n"
69                             +"}\n"
70                             +"var closure = initClosure('nested:');\n"
71                             +"";
72             Script script = cx.compileString(source, "sharedScript", 1, null);
73 
74             useDynamicScope = false;
75             runScripts(cx, script);
76             useDynamicScope = true;
77             runScripts(cx, script);
78         } finally {
79             Context.exit();
80         }
81     }
82 
runScripts(Context cx, Script script)83     static void runScripts(Context cx, Script script)
84     {
85         // Initialize the standard objects (Object, Function, etc.)
86         // This must be done before scripts can be executed. The call
87         // returns a new scope that we will share.
88         ScriptableObject sharedScope = cx.initStandardObjects(null, true);
89 
90         // Now we can execute the precompiled script against the scope
91         // to define x variable and f function in the shared scope.
92         script.exec(cx, sharedScope);
93 
94         // Now we spawn some threads that execute a script that calls the
95         // function 'f'. The scope chain looks like this:
96         // <pre>
97         //            ------------------                ------------------
98         //           | per-thread scope | -prototype-> |   shared scope   |
99         //            ------------------                ------------------
100         //                    ^
101         //                    |
102         //               parentScope
103         //                    |
104         //            ------------------
105         //           | f's activation   |
106         //            ------------------
107         // </pre>
108         // Both the shared scope and the per-thread scope have variables 'x'
109         // defined in them. If 'f' is compiled with dynamic scope enabled,
110         // the 'x' from the per-thread scope will be used. Otherwise, the 'x'
111         // from the shared scope will be used. The 'x' defined in 'g' (which
112         // calls 'f') should not be seen by 'f'.
113         final int threadCount = 3;
114         Thread[] t = new Thread[threadCount];
115         for (int i=0; i < threadCount; i++) {
116             String source2 = ""
117                 +"function g() { var x = 'local'; return f(); }\n"
118                 +"java.lang.System.out.println(g());\n"
119                 +"function g2() { var x = 'local'; return closure(); }\n"
120                 +"java.lang.System.out.println(g2());\n"
121                 +"";
122             t[i] = new Thread(new PerThread(sharedScope, source2,
123                                             "thread" + i));
124         }
125         for (int i=0; i < threadCount; i++)
126             t[i].start();
127         // Don't return in this thread until all the spawned threads have
128         // completed.
129         for (int i=0; i < threadCount; i++) {
130             try {
131                 t[i].join();
132             } catch (InterruptedException e) {
133             }
134         }
135     }
136 
137     static class PerThread implements Runnable {
138 
PerThread(Scriptable sharedScope, String source, String x)139         PerThread(Scriptable sharedScope, String source, String x) {
140             this.sharedScope = sharedScope;
141             this.source = source;
142             this.x = x;
143         }
144 
run()145         public void run() {
146             // We need a new Context for this thread.
147             Context cx = Context.enter();
148             try {
149                 // We can share the scope.
150                 Scriptable threadScope = cx.newObject(sharedScope);
151                 threadScope.setPrototype(sharedScope);
152 
153                 // We want "threadScope" to be a new top-level
154                 // scope, so set its parent scope to null. This
155                 // means that any variables created by assignments
156                 // will be properties of "threadScope".
157                 threadScope.setParentScope(null);
158 
159                 // Create a JavaScript property of the thread scope named
160                 // 'x' and save a value for it.
161                 threadScope.put("x", threadScope, x);
162                 cx.evaluateString(threadScope, source, "threadScript", 1, null);
163             } finally {
164                 Context.exit();
165             }
166         }
167         private Scriptable sharedScope;
168         private String source;
169         private String x;
170     }
171 
172 }
173 
174