/* * Copyright (c) 1998-2011 Caucho Technology -- all rights reserved * * This file is part of Resin(R) Open Source * * Each copy or derived work must preserve the copyright notice and this * notice unmodified. * * Resin Open Source is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * Resin Open Source is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty * of NON-INFRINGEMENT. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License * along with Resin Open Source; if not, write to the * Free SoftwareFoundation, Inc. * 59 Temple Place, Suite 330 * Boston, MA 02111-1307 USA * * @author Scott Ferguson */ package com.caucho.es; import com.caucho.java.LineMap; import com.caucho.util.Exit; import com.caucho.vfs.Path; import java.util.HashMap; /** * The Script object represents a compiled JavaScript. Executing it * is thread safe. To create a Script, use the Parser class to parse * a file. * * <p>Java programs set JavaScript Global properties by adding objects to * a HashMap. Typically you will at least assign the 'File' and * the 'out' objects. The running script will * see these objects as properties of the Global object. If you set the * 'out' object, the script can use the bare bones 'writeln("foo")' to * write to 'out'. * * <pre><code> * HashMap map = new HashMap(); * map.put("File", Vfs.lookup()); * map.put("out", System.out); * map.put("myObject", myObject); * * script.execute(map, null); * </code></pre> * * <p>You can also make any Java object be the global prototype. * Essentially, the effect is similar to the HashMap technique, but * it's a little simpler. * * <p>Scripts are thread-safe. Multiple script instances can * safely execute in separate threads. Script.execute creates the * entire JavaScript global object hierarchy fresh for each execution. * So one Script execution cannot interfere with another, even by doing * evil things like modifying the Object prototype. * * <p>Of course, wrapped Java objects shared by script invocations * must still be synchronized. */ abstract public class Script { protected Path scriptPath; protected Path classDir; /** * Internal method to check if the source files have changed. */ public boolean isModified() { return true; } /** * Internal method to set the script search path for imported * scripts. */ public void setScriptPath(Path scriptPath) { this.scriptPath = scriptPath; } /** * Internal method to set the work directory for generated *.java * and *.class. */ public void setClassDir(Path classDir) { this.classDir = classDir; } /** * Returns the map from source file line numbers to the generated * java line numbers. */ public LineMap getLineMap() { return null; } /** * Execute the script; the main useful entry. * * <p>Calling programs can make Java objects available as properties * of the global object by creating a property hash map or assigning * a global prototype. * * <pre><code> * HashMap map = new HashMap(); * map.put("File", Vfs.lookup()); * map.put("out", System.out); * map.put("myObject", myObject); * script.execute(map, null); * </code></pre> * * Then the JavaScript can use the defined objects: * <pre><code> * out.println(myObject.myMethod("foo")); * </code></pre> * * @param properties A hash map of global properties. * @param proto Global prototype. Gives the script direct access to * the java methods of the object. * * @return String value of the last expression, like the JavaScript eval. * This is useful only for testing. */ public String execute(HashMap properties, Object proto) throws Throwable { Global oldGlobal = Global.getGlobalProto(); boolean doExit = Exit.addExit(); try { Global resin = new Global(properties, proto, classDir, scriptPath, getClass().getClassLoader()); resin.begin(); ESGlobal global = initClass(resin); ESBase value = global.execute(); if (value == null) return null; else return value.toStr().toString(); } finally { Global.end(oldGlobal); if (doExit) Exit.exit(); } } /** * Execute the program, returning a closure of the global state. * <code>executeClosure</code> will execute the global script. * * <p>Later routines can then call into the closure. The closure * is not thread-safe. So only a single thread may execute the * closure. * * @param properties A hash map of global properties. * @param proto Global prototype. Gives the script direct access to * the java methods of the object. * * @return the closure */ public ScriptClosure executeClosure(HashMap properties, Object proto) throws Throwable { Global resin = new Global(properties, proto, classDir, scriptPath, getClass().getClassLoader()); boolean doExit = Exit.addExit(); Global oldGlobal = resin.begin(); try { ESGlobal global = initClass(resin); global.execute(); return new ScriptClosure(resin, global, this); } finally { resin.end(oldGlobal); if (doExit) Exit.exit(); } } /** * Internal method to initialize the script after loading it. */ public ESGlobal initClass(Global resin, ESObject global) throws Throwable { return initClass(resin); } /** * Internal method implemented by the generated script for initialization. */ public abstract ESGlobal initClass(Global resin) throws Throwable; /** * Internal method to export objects. */ public void export(ESObject dest, ESObject src) throws Throwable { } }