001: /* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
002: *
003: * ***** BEGIN LICENSE BLOCK *****
004: * Version: MPL 1.1/GPL 2.0
005: *
006: * The contents of this file are subject to the Mozilla Public License Version
007: * 1.1 (the "License"); you may not use this file except in compliance with
008: * the License. You may obtain a copy of the License at
009: * http://www.mozilla.org/MPL/
010: *
011: * Software distributed under the License is distributed on an "AS IS" basis,
012: * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
013: * for the specific language governing rights and limitations under the
014: * License.
015: *
016: * The Original Code is Rhino code, released
017: * May 6, 1999.
018: *
019: * The Initial Developer of the Original Code is
020: * Netscape Communications Corporation.
021: * Portions created by the Initial Developer are Copyright (C) 1997-1999
022: * the Initial Developer. All Rights Reserved.
023: *
024: * Contributor(s):
025: * Igor Bukanov, igor@fastmail.fm
026: *
027: * Alternatively, the contents of this file may be used under the terms of
028: * the GNU General Public License Version 2 or later (the "GPL"), in which
029: * case the provisions of the GPL are applicable instead of those above. If
030: * you wish to allow use of your version of this file only under the terms of
031: * the GPL and not to allow others to use your version of this file under the
032: * MPL, indicate your decision by deleting the provisions above and replacing
033: * them with the notice and other provisions required by the GPL. If you do
034: * not delete the provisions above, a recipient may use your version of this
035: * file under either the MPL or the GPL.
036: *
037: * ***** END LICENSE BLOCK ***** */
038:
039: // API class
040: package org.mozilla.javascript;
041:
042: /**
043: * Factory class that Rhino runtime uses to create new {@link Context}
044: * instances. A <code>ContextFactory</code> can also notify listeners
045: * about context creation and release.
046: * <p>
047: * When the Rhino runtime needs to create new {@link Context} instance during
048: * execution of {@link Context#enter()} or {@link Context}, it will call
049: * {@link #makeContext()} of the current global ContextFactory.
050: * See {@link #getGlobal()} and {@link #initGlobal(ContextFactory)}.
051: * <p>
052: * It is also possible to use explicit ContextFactory instances for Context
053: * creation. This is useful to have a set of independent Rhino runtime
054: * instances under single JVM. See {@link #call(ContextAction)}.
055: * <p>
056: * The following example demonstrates Context customization to terminate
057: * scripts running more then 10 seconds and to provide better compatibility
058: * with JavaScript code using MSIE-specific features.
059: * <pre>
060: * import org.mozilla.javascript.*;
061: *
062: * class MyFactory extends ContextFactory
063: * {
064: *
065: * // Custom {@link Context} to store execution time.
066: * private static class MyContext extends Context
067: * {
068: * long startTime;
069: * }
070: *
071: * static {
072: * // Initialize GlobalFactory with custom factory
073: * ContextFactory.initGlobal(new MyFactory());
074: * }
075: *
076: * // Override {@link #makeContext()}
077: * protected Context makeContext()
078: * {
079: * MyContext cx = new MyContext();
080: * // Use pure interpreter mode to allow for
081: * // {@link #observeInstructionCount(Context, int)} to work
082: * cx.setOptimizationLevel(-1);
083: * // Make Rhino runtime to call observeInstructionCount
084: * // each 10000 bytecode instructions
085: * cx.setInstructionObserverThreshold(10000);
086: * return cx;
087: * }
088: *
089: * // Override {@link #hasFeature(Context, int)}
090: * public boolean hasFeature(Context cx, int featureIndex)
091: * {
092: * // Turn on maximum compatibility with MSIE scripts
093: * switch (featureIndex) {
094: * case {@link Context#FEATURE_NON_ECMA_GET_YEAR}:
095: * return true;
096: *
097: * case {@link Context#FEATURE_MEMBER_EXPR_AS_FUNCTION_NAME}:
098: * return true;
099: *
100: * case {@link Context#FEATURE_RESERVED_KEYWORD_AS_IDENTIFIER}:
101: * return true;
102: *
103: * case {@link Context#FEATURE_PARENT_PROTO_PROPERTIES}:
104: * return false;
105: * }
106: * return super.hasFeature(cx, featureIndex);
107: * }
108: *
109: * // Override {@link #observeInstructionCount(Context, int)}
110: * protected void observeInstructionCount(Context cx, int instructionCount)
111: * {
112: * MyContext mcx = (MyContext)cx;
113: * long currentTime = System.currentTimeMillis();
114: * if (currentTime - mcx.startTime > 10*1000) {
115: * // More then 10 seconds from Context creation time:
116: * // it is time to stop the script.
117: * // Throw Error instance to ensure that script will never
118: * // get control back through catch or finally.
119: * throw new Error();
120: * }
121: * }
122: *
123: * // Override {@link #doTopCall(Callable,
124: Context, Scriptable,
125: Scriptable, Object[])}
126: * protected Object doTopCall(Callable callable,
127: * Context cx, Scriptable scope,
128: * Scriptable thisObj, Object[] args)
129: * {
130: * MyContext mcx = (MyContext)cx;
131: * mcx.startTime = System.currentTimeMillis();
132: *
133: * return super.doTopCall(callable, cx, scope, thisObj, args);
134: * }
135: *
136: * }
137: *
138: * </pre>
139: */
140:
141: public class ContextFactory {
142: private static volatile boolean hasCustomGlobal;
143: private static ContextFactory global = new ContextFactory();
144:
145: private volatile boolean sealed;
146:
147: private final Object listenersLock = new Object();
148: private volatile Object listeners;
149: private boolean disabledListening;
150: private ClassLoader applicationClassLoader;
151:
152: /**
153: * Listener of {@link Context} creation and release events.
154: */
155: public interface Listener {
156: /**
157: * Notify about newly created {@link Context} object.
158: */
159: public void contextCreated(Context cx);
160:
161: /**
162: * Notify that the specified {@link Context} instance is no longer
163: * associated with the current thread.
164: */
165: public void contextReleased(Context cx);
166: }
167:
168: /**
169: * Get global ContextFactory.
170: *
171: * @see #hasExplicitGlobal()
172: * @see #initGlobal(ContextFactory)
173: */
174: public static ContextFactory getGlobal() {
175: return global;
176: }
177:
178: /**
179: * Check if global factory was set.
180: * Return true to indicate that {@link #initGlobal(ContextFactory)} was
181: * already called and false to indicate that the global factory was not
182: * explicitly set.
183: *
184: * @see #getGlobal()
185: * @see #initGlobal(ContextFactory)
186: */
187: public static boolean hasExplicitGlobal() {
188: return hasCustomGlobal;
189: }
190:
191: /**
192: * Set global ContextFactory.
193: * The method can only be called once.
194: *
195: * @see #getGlobal()
196: * @see #hasExplicitGlobal()
197: */
198: public synchronized static void initGlobal(ContextFactory factory) {
199: if (factory == null) {
200: throw new IllegalArgumentException();
201: }
202: if (hasCustomGlobal) {
203: throw new IllegalStateException();
204: }
205: hasCustomGlobal = true;
206: global = factory;
207: }
208:
209: /**
210: * Create new {@link Context} instance to be associated with the current
211: * thread.
212: * This is a callback method used by Rhino to create {@link Context}
213: * instance when it is necessary to associate one with the current
214: * execution thread. <tt>makeContext()</tt> is allowed to call
215: * {@link Context#seal(Object)} on the result to prevent
216: * {@link Context} changes by hostile scripts or applets.
217: */
218: protected Context makeContext() {
219: return new Context(this );
220: }
221:
222: /**
223: * Implementation of {@link Context#hasFeature(int featureIndex)}.
224: * This can be used to customize {@link Context} without introducing
225: * additional subclasses.
226: */
227: protected boolean hasFeature(Context cx, int featureIndex) {
228: int version;
229: switch (featureIndex) {
230: case Context.FEATURE_NON_ECMA_GET_YEAR:
231: /*
232: * During the great date rewrite of 1.3, we tried to track the
233: * evolving ECMA standard, which then had a definition of
234: * getYear which always subtracted 1900. Which we
235: * implemented, not realizing that it was incompatible with
236: * the old behavior... now, rather than thrash the behavior
237: * yet again, we've decided to leave it with the - 1900
238: * behavior and point people to the getFullYear method. But
239: * we try to protect existing scripts that have specified a
240: * version...
241: */
242: version = cx.getLanguageVersion();
243: return (version == Context.VERSION_1_0
244: || version == Context.VERSION_1_1 || version == Context.VERSION_1_2);
245:
246: case Context.FEATURE_MEMBER_EXPR_AS_FUNCTION_NAME:
247: return false;
248:
249: case Context.FEATURE_RESERVED_KEYWORD_AS_IDENTIFIER:
250: return false;
251:
252: case Context.FEATURE_TO_STRING_AS_SOURCE:
253: version = cx.getLanguageVersion();
254: return version == Context.VERSION_1_2;
255:
256: case Context.FEATURE_PARENT_PROTO_PROPERTIES:
257: return true;
258:
259: case Context.FEATURE_E4X:
260: version = cx.getLanguageVersion();
261: return (version == Context.VERSION_DEFAULT || version >= Context.VERSION_1_6);
262:
263: case Context.FEATURE_DYNAMIC_SCOPE:
264: return false;
265:
266: case Context.FEATURE_STRICT_VARS:
267: return false;
268:
269: case Context.FEATURE_STRICT_EVAL:
270: return false;
271:
272: case Context.FEATURE_LOCATION_INFORMATION_IN_ERROR:
273: return false;
274:
275: case Context.FEATURE_STRICT_MODE:
276: return false;
277:
278: case Context.FEATURE_WARNING_AS_ERROR:
279: return false;
280:
281: case Context.FEATURE_ENHANCED_JAVA_ACCESS:
282: return false;
283: }
284: // It is a bug to call the method with unknown featureIndex
285: throw new IllegalArgumentException(String.valueOf(featureIndex));
286: }
287:
288: private boolean isDom3Present() {
289: Class nodeClass = Kit.classOrNull("org.w3c.dom.Node");
290: if (nodeClass == null)
291: return false;
292: // Check to see whether DOM3 is present; use a new method defined in
293: // DOM3 that is vital to our implementation
294: try {
295: nodeClass.getMethod("getUserData",
296: new Class[] { String.class });
297: return true;
298: } catch (NoSuchMethodException e) {
299: return false;
300: }
301: }
302:
303: /**
304: * Provides a default
305: * {@link org.mozilla.javascript.xml.XMLLib.Factory XMLLib.Factory}
306: * to be used by the <code>Context</code> instances produced by this
307: * factory. See {@link Context#getE4xImplementationFactory} for details.
308: *
309: * May return null, in which case E4X functionality is not supported in
310: * Rhino.
311: *
312: * The default implementation now prefers the DOM3 E4X implementation.
313: */
314: protected org.mozilla.javascript.xml.XMLLib.Factory getE4xImplementationFactory() {
315: // Must provide default implementation, rather than abstract method,
316: // so that past implementors of ContextFactory do not fail at runtime
317: // upon invocation of this method.
318: // Note that the default implementation returns null if we
319: // neither have XMLBeans nor a DOM3 implementation present.
320:
321: if (isDom3Present()) {
322: return org.mozilla.javascript.xml.XMLLib.Factory
323: .create("org.mozilla.javascript.xmlimpl.XMLLibImpl");
324: } else if (Kit.classOrNull("org.apache.xmlbeans.XmlCursor") != null) {
325: return org.mozilla.javascript.xml.XMLLib.Factory
326: .create("org.mozilla.javascript.xml.impl.xmlbeans.XMLLibImpl");
327: } else {
328: return null;
329: }
330: }
331:
332: /**
333: * Create class loader for generated classes.
334: * This method creates an instance of the default implementation
335: * of {@link GeneratedClassLoader}. Rhino uses this interface to load
336: * generated JVM classes when no {@link SecurityController}
337: * is installed.
338: * Application can override the method to provide custom class loading.
339: */
340: protected GeneratedClassLoader createClassLoader(ClassLoader parent) {
341: return new DefiningClassLoader(parent);
342: }
343:
344: /**
345: * Get ClassLoader to use when searching for Java classes.
346: * Unless it was explicitly initialized with
347: * {@link #initApplicationClassLoader(ClassLoader)} the method returns
348: * null to indicate that Thread.getContextClassLoader() should be used.
349: */
350: public final ClassLoader getApplicationClassLoader() {
351: return applicationClassLoader;
352: }
353:
354: /**
355: * Set explicit class loader to use when searching for Java classes.
356: *
357: * @see #getApplicationClassLoader()
358: */
359: public final void initApplicationClassLoader(ClassLoader loader) {
360: if (loader == null)
361: throw new IllegalArgumentException("loader is null");
362: if (!Kit.testIfCanLoadRhinoClasses(loader))
363: throw new IllegalArgumentException(
364: "Loader can not resolve Rhino classes");
365:
366: if (this .applicationClassLoader != null)
367: throw new IllegalStateException(
368: "applicationClassLoader can only be set once");
369: checkNotSealed();
370:
371: this .applicationClassLoader = loader;
372: }
373:
374: /**
375: * Execute top call to script or function.
376: * When the runtime is about to execute a script or function that will
377: * create the first stack frame with scriptable code, it calls this method
378: * to perform the real call. In this way execution of any script
379: * happens inside this function.
380: */
381: protected Object doTopCall(Callable callable, Context cx,
382: Scriptable scope, Scriptable this Obj, Object[] args) {
383: return callable.call(cx, scope, this Obj, args);
384: }
385:
386: /**
387: * Implementation of
388: * {@link Context#observeInstructionCount(int instructionCount)}.
389: * This can be used to customize {@link Context} without introducing
390: * additional subclasses.
391: */
392: protected void observeInstructionCount(Context cx,
393: int instructionCount) {
394: }
395:
396: protected void onContextCreated(Context cx) {
397: Object listeners = this .listeners;
398: for (int i = 0;; ++i) {
399: Listener l = (Listener) Kit.getListener(listeners, i);
400: if (l == null)
401: break;
402: l.contextCreated(cx);
403: }
404: }
405:
406: protected void onContextReleased(Context cx) {
407: Object listeners = this .listeners;
408: for (int i = 0;; ++i) {
409: Listener l = (Listener) Kit.getListener(listeners, i);
410: if (l == null)
411: break;
412: l.contextReleased(cx);
413: }
414: }
415:
416: public final void addListener(Listener listener) {
417: checkNotSealed();
418: synchronized (listenersLock) {
419: if (disabledListening) {
420: throw new IllegalStateException();
421: }
422: listeners = Kit.addListener(listeners, listener);
423: }
424: }
425:
426: public final void removeListener(Listener listener) {
427: checkNotSealed();
428: synchronized (listenersLock) {
429: if (disabledListening) {
430: throw new IllegalStateException();
431: }
432: listeners = Kit.removeListener(listeners, listener);
433: }
434: }
435:
436: /**
437: * The method is used only to implement
438: * Context.disableStaticContextListening()
439: */
440: final void disableContextListening() {
441: checkNotSealed();
442: synchronized (listenersLock) {
443: disabledListening = true;
444: listeners = null;
445: }
446: }
447:
448: /**
449: * Checks if this is a sealed ContextFactory.
450: * @see #seal()
451: */
452: public final boolean isSealed() {
453: return sealed;
454: }
455:
456: /**
457: * Seal this ContextFactory so any attempt to modify it like to add or
458: * remove its listeners will throw an exception.
459: * @see #isSealed()
460: */
461: public final void seal() {
462: checkNotSealed();
463: sealed = true;
464: }
465:
466: protected final void checkNotSealed() {
467: if (sealed)
468: throw new IllegalStateException();
469: }
470:
471: /**
472: * Call {@link ContextAction#run(Context cx)}
473: * using the {@link Context} instance associated with the current thread.
474: * If no Context is associated with the thread, then
475: * {@link #makeContext()} will be called to construct
476: * new Context instance. The instance will be temporary associated
477: * with the thread during call to {@link ContextAction#run(Context)}.
478: *
479: * @see ContextFactory#call(ContextAction)
480: * @see Context#call(ContextFactory factory, Callable callable,
481: * Scriptable scope, Scriptable thisObj,
482: * Object[] args)
483: */
484: public final Object call(ContextAction action) {
485: return Context.call(this , action);
486: }
487:
488: /**
489: * Get a context associated with the current thread, creating one if need
490: * be. The Context stores the execution state of the JavaScript engine, so
491: * it is required that the context be entered before execution may begin.
492: * Once a thread has entered a Context, then getCurrentContext() may be
493: * called to find the context that is associated with the current thread.
494: * <p>
495: * Calling <code>enterContext()</code> will return either the Context
496: * currently associated with the thread, or will create a new context and
497: * associate it with the current thread. Each call to
498: * <code>enterContext()</code> must have a matching call to
499: * {@link Context#exit()}.
500: * <pre>
501: * Context cx = contextFactory.enterContext();
502: * try {
503: * ...
504: * cx.evaluateString(...);
505: * } finally {
506: * Context.exit();
507: * }
508: * </pre>
509: * Instead of using <tt>enterContext()</tt>, <tt>exit()</tt> pair consider
510: * using {@link #call(ContextAction)} which guarantees proper association
511: * of Context instances with the current thread.
512: * With this method the above example becomes:
513: * <pre>
514: * ContextFactory.call(new ContextAction() {
515: * public Object run(Context cx) {
516: * ...
517: * cx.evaluateString(...);
518: * return null;
519: * }
520: * });
521: * </pre>
522: * @return a Context associated with the current thread
523: * @see Context#getCurrentContext()
524: * @see Context#exit()
525: * @see #call(ContextAction)
526: */
527: public Context enterContext() {
528: return enterContext(null);
529: }
530:
531: /**
532: * @deprecated use {@link #enterContext()} instead
533: * @return a Context associated with the current thread
534: */
535: public final Context enter() {
536: return enterContext(null);
537: }
538:
539: /**
540: * @deprecated Use {@link Context#exit()} instead.
541: */
542: public final void exit() {
543: Context.exit();
544: }
545:
546: /**
547: * Get a Context associated with the current thread, using the given
548: * Context if need be.
549: * <p>
550: * The same as <code>enterContext()</code> except that <code>cx</code>
551: * is associated with the current thread and returned if the current thread
552: * has no associated context and <code>cx</code> is not associated with any
553: * other thread.
554: * @param cx a Context to associate with the thread if possible
555: * @return a Context associated with the current thread
556: * @see #enterContext()
557: * @see #call(ContextAction)
558: * @throws IllegalStateException if <code>cx</code> is already associated
559: * with a different thread
560: */
561: public final Context enterContext(Context cx) {
562: return Context.enter(cx, this);
563: }
564: }
|