/*
* 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.es.parser.Parser;
import com.caucho.es.wrapper.Wrapper;
import com.caucho.java.LineMap;
import com.caucho.loader.DynamicClassLoader;
import com.caucho.loader.SimpleLoader;
import com.caucho.util.FreeList;
import com.caucho.util.IntMap;
import com.caucho.util.LruCache;
import com.caucho.util.WeakLruCache;
import com.caucho.vfs.Path;
import java.lang.ref.SoftReference;
import java.util.Date;
import java.util.HashMap;
import java.util.logging.Logger;
/**
* Implementation class for the global prototype
*/
public class Global extends ESBase {
private static Integer LOCK = new Integer(0);
private static final Logger log = Logger.getLogger(Global.class.getName());
private final static int OBJECT = 0;
private final static int FUNCTION = OBJECT + 1;
private final static int ARRAY = FUNCTION + 1;
private final static int STRING = ARRAY + 1;
private final static int BOOL = STRING + 1;
private final static int NUM = BOOL + 1;
private final static int DATE = NUM + 1;
private final static int MATH = DATE + 1;
private final static int REGEXP = MATH + 1;
private final static int PACKAGES = REGEXP + 1;
private final static int CAUCHO = PACKAGES + 1;
private final static int JAVA = CAUCHO + 1;
private final static int JAVAX = JAVA + 1;
private static Global goldGlobal;
private static IntMap propertyMap;
private ESGlobal global;
private Global root;
ESObject objProto;
ESObject object;
ESObject funProto;
ESObject fun;
ESObject arrayProto;
ESObject array;
ESObject stringProto;
ESObject string;
ESObject boolProto;
ESObject bool;
ESObject numProto;
ESObject num;
ESObject dateProto;
ESObject date;
ESObject math;
ESRegexp regexpProto;
ESRegexpWrapper regExp;
ESPackage pkg;
HashMap properties;
HashMap globalProperties;
// static lookup of current Global
private static final ThreadLocal<Global> _globals
= new ThreadLocal<Global>();
private static Thread lastThread;
private static Global lastGlobal;
// bean wrapping
private final static WeakLruCache<Class,ESBase> _staticWraps
= new WeakLruCache<Class,ESBase>(256);
private final static WeakLruCache<Class,ESBase> _staticClassWraps
= new WeakLruCache<Class,ESBase>(256);
// cache
private static FreeList<Call> _freeCalls = new FreeList<Call>(2);
private static HashMap<String,SoftReference<Script>> _runtimeScripts;
/* = new HashMap<String,SoftReference<Script>>(); */
private ClassLoader parentLoader;
private ClassLoader loader;
private Path scriptPath;
private Path classDir;
private HashMap importScripts;
private HashMap importGlobals;
int markCount;
/**
* Null constructor
*/
private Global(boolean init)
{
ESBase.init(null);
/*
if (_globals == null) {
_globals = new ThreadLocal();
_staticWraps = new LruCache<Class,ESBase>(256);
_staticClassWraps = new LruCache<Class,ESBase>(256);
ESId.intern("foo"); // XXX: bogus to fix stupid kaffe static init.
}
*/
propertyMap = new IntMap();
propertyMap.put(ESId.intern("Object"), OBJECT);
propertyMap.put(ESId.intern("Function"), FUNCTION);
propertyMap.put(ESId.intern("Array"), ARRAY);
propertyMap.put(ESId.intern("String"), STRING);
propertyMap.put(ESId.intern("Boolean"), BOOL);
propertyMap.put(ESId.intern("Number"), NUM);
propertyMap.put(ESId.intern("Date"), DATE);
propertyMap.put(ESId.intern("Math"), MATH);
propertyMap.put(ESId.intern("RegExp"), REGEXP);
propertyMap.put(ESId.intern("Packages"), PACKAGES);
propertyMap.put(ESId.intern("caucho"), CAUCHO);
propertyMap.put(ESId.intern("java"), JAVA);
propertyMap.put(ESId.intern("javax"), JAVAX);
globalProperties = new HashMap();
object = NativeObject.create(this);
fun = NativeFunction.create(this);
object.prototype = funProto;
int flags = ESBase.DONT_ENUM;
int allflags = (ESBase.DONT_ENUM|ESBase.DONT_DELETE|ESBase.READ_ONLY);
array = NativeArray.create(this);
string = NativeString.create(this);
bool = NativeBoolean.create(this);
num = NativeNumber.create(this);
math = NativeMath.create(this);
date = NativeDate.create(this);
regExp = NativeRegexp.create(this);
pkg = ESPackage.create();
NativeGlobal.create(this);
NativeFile.create(this);
globalProperties.put(ESId.intern("NaN"), ESNumber.create(0.0/0.0));
globalProperties.put(ESId.intern("Infinity"), ESNumber.create(1.0/0.0));
}
/**
* Creates a new global object for a script thread.
*
* @param properties any global properties for the script
* @param proto a Java prototype object underlying the global object
* @param classDir work directory where generated classes will go
* @param scriptPath a path for searching scripts
* @param parentLoader the parent class loader.
*/
Global(HashMap properties, Object proto,
Path classDir, Path scriptPath, ClassLoader parentLoader)
throws Throwable
{
synchronized (LOCK) {
if (goldGlobal == null)
goldGlobal = new Global(true);
}
root = this;
this.parentLoader = parentLoader;
this.loader = SimpleLoader.create(parentLoader, classDir, null);
this.classDir = classDir;
this.scriptPath = scriptPath;
// Object
objProto = (ESObject) goldGlobal.objProto.resinCopy();
object = (ESObject) goldGlobal.object.resinCopy();
// Function
funProto = (ESObject) goldGlobal.funProto.resinCopy();
funProto.prototype = objProto;
object.prototype = funProto;
fun = (ESObject) goldGlobal.fun.resinCopy();
fun.prototype = funProto;
// Array
arrayProto = (ESObject) goldGlobal.arrayProto.resinCopy();
arrayProto.prototype = objProto;
array = (ESObject) goldGlobal.array.resinCopy();
array.prototype = funProto;
// String
stringProto = (ESObject) goldGlobal.stringProto.resinCopy();
stringProto.prototype = objProto;
string = (ESObject) goldGlobal.string.resinCopy();
string.prototype = funProto;
// Boolean
boolProto = (ESObject) goldGlobal.boolProto.resinCopy();
boolProto.prototype = objProto;
bool = (ESObject) goldGlobal.bool.resinCopy();
bool.prototype = funProto;
// Number
numProto = (ESObject) goldGlobal.numProto.resinCopy();
numProto.prototype = objProto;
num = (ESObject) goldGlobal.num.resinCopy();
num.prototype = funProto;
// Math
math = (ESObject) goldGlobal.math.resinCopy();
math.prototype = objProto;
// Date
dateProto = (ESObject) goldGlobal.dateProto.resinCopy();
dateProto.prototype = objProto;
date = (ESObject) goldGlobal.date.resinCopy();
date.prototype = funProto;
// RegExp
//regexpProto = (ESRegexp) goldGlobal.regexpProto.resinCopy();
//regexpProto.prototype = objProto;
//regExp = (ESRegexpWrapper) goldGlobal.regExp.resinCopy();
//regExp.prototype = funProto;
//regExp.regexp = regexpProto;
pkg = ESPackage.create();
if (proto != null) {
prototype = objectWrap(proto);
prototype.prototype = objProto;
}
else
prototype = objProto;
if (properties != null)
this.properties = properties;
globalProperties = goldGlobal.globalProperties;
}
Global(Global root)
{
this.root = root;
objProto = root.objProto;
object = root.object;
funProto = root.funProto;
fun = root.fun;
arrayProto = root.arrayProto;
array = root.array;
stringProto = root.stringProto;
string = root.string;
boolProto = root.boolProto;
bool = root.bool;
numProto = root.numProto;
num = root.num;
math = root.math;
dateProto = root.dateProto;
date = root.date;
regexpProto = root.regexpProto;
regExp = root.regExp;
pkg = root.pkg;
properties = root.properties;
prototype = root.prototype;
globalProperties = root.globalProperties;
_runtimeScripts = root._runtimeScripts;
}
void addProperty(ESId id, ESBase value)
{
globalProperties.put(id, value);
}
public ESBase getProperty(ESString id)
throws Throwable
{
int index = propertyMap.get(id);
switch (index) {
case OBJECT:
return snap(id, object);
case FUNCTION:
return snap(id, fun);
case ARRAY:
return snap(id, array);
case STRING:
return snap(id, string);
case BOOL:
return snap(id, bool);
case NUM:
return snap(id, num);
case DATE:
return snap(id, date);
case MATH:
return snap(id, math);
case REGEXP:
return snap(id, getRegexp());
case PACKAGES:
return snap(id, pkg);
case CAUCHO:
return snap(id, pkg.getProperty("com").getProperty("caucho"));
case JAVA:
return snap(id, pkg.getProperty("java"));
case JAVAX:
return snap(id, pkg.getProperty("javax"));
default:
ESBase value = prototype == null ? null : prototype.getProperty(id);
Object obj;
if (value != null && value != esEmpty)
return snap(id, value);
else if (properties != null &&
(obj = properties.get(id.toString())) != null)
return snap(id, objectWrap(obj));
else if ((value = (ESBase) globalProperties.get(id)) != null)
return snap(id, value);
else {
return esEmpty;
}
}
}
private ESBase snap(ESString id, ESBase value)
{
if (value == null)
throw new RuntimeException();
global.put(id, value, DONT_ENUM);
return value;
}
ESRegexpWrapper getRegexp()
{
if (regExp != null)
return regExp;
else if (root.regExp != null) {
regExp = root.regExp;
return regExp;
}
initRegexp();
return regExp;
}
ESRegexp getRegexpProto()
{
if (regexpProto != null)
return regexpProto;
else if (root.regexpProto != null) {
regexpProto = root.regexpProto;
return regexpProto;
}
initRegexp();
return regexpProto;
}
private void initRegexp()
{
root.regexpProto = (ESRegexp) goldGlobal.regexpProto.resinCopy();
root.regexpProto.prototype = root.objProto;
root.regExp = (ESRegexpWrapper) goldGlobal.regExp.resinCopy();
root.regExp.prototype = root.funProto;
root.regExp.regexp = root.regexpProto;
regexpProto = root.regexpProto;
regExp = root.regExp;
}
/**
* Sets a running script.
*
* @param name classname of the script.
* @param script the script itself.
*/
public void addScript(String name, Script script)
{
if (_runtimeScripts != null)
_runtimeScripts.put(name, new SoftReference<Script>(script));
}
/**
* Returns the line map for the named class to translate Java line
* numbers to javascript line numbers.
*
* @param className class throwing the error.
* @return the line map.
*/
LineMap getLineMap(String className)
{
try {
int p = className.indexOf('$');
if (p > 0)
className = className.substring(0, p);
Script script = null;
if (_runtimeScripts != null) {
SoftReference<Script> ref = _runtimeScripts.get(className);
if (ref != null)
script = ref.get();
}
if (script != null)
return script.getLineMap();
else
return null;
} catch (Exception e) {
return null;
}
}
/**
* Returns the named script. If the script has already been loaded,
* return the old script.
*/
Script findScript(String className)
throws Throwable
{
Script script = (Script) importScripts.get(className);
if (script != null)
return script;
Parser parser = new Parser();
parser.setScriptPath(getScriptPath());
parser.setClassLoader(getClassLoader());
parser.setWorkDir(getClassDir());
return parser.parse(className);
}
/**
* Returns the global prototype for the current thread.
*/
public static Global getGlobalProto()
{
return (Global) _globals.get();
}
/**
* Starts execution of a JavaScript thread.
*
* @return the old global context for the thread.
*/
Global begin()
{
Global oldGlobal = (Global) _globals.get();
_globals.set(this);
return oldGlobal;
}
/**
* Completes execution of a JavaScript thread, restoring the global context.
*
* @param oldGlobal the old global context for the thread.
*/
static void end(Global oldGlobal)
{
_globals.set(oldGlobal);
}
Call getCall()
{
Call call = _freeCalls.allocate();
if (call == null)
return new Call();
else {
call.clear();
return call;
}
}
void freeCall(Call call)
{
call.free();
_freeCalls.free(call);
}
ESBase objectWrap(Object object)
throws Throwable
{
if (object == null)
return ESBase.esNull;
Class cl = object.getClass();
String clName = cl.getName();
if (object instanceof ESBase)
return (ESBase) object;
if (clName.equals("java.lang.String"))
return new ESString(object.toString());
if (clName.equals("java.lang.Double"))
return ESNumber.create(((Double) object).doubleValue());
if (clName.equals("java.util.Date"))
return convertDate(object);
ESBase wrapper;
synchronized (_staticWraps) {
wrapper = _staticWraps.get(cl);
}
if (wrapper == null || ((DynamicClassLoader) wrapper.getClass().getClassLoader()).isDestroyed()) {
ESBase []values = Wrapper.bean(this, cl);
if (values == null)
return ESBase.esNull;
ESBase clWrapper = values[0];
wrapper = values[1];
if (wrapper.getClass().getClassLoader().getParent().equals(getClass().getClassLoader())) {
synchronized (_staticWraps) {
_staticClassWraps.put(cl, clWrapper);
_staticWraps.put(cl, wrapper);
}
}
}
if (wrapper instanceof ESJavaWrapper)
return ((ESJavaWrapper) wrapper).wrap(object);
else {
return ((ESBeanWrapper) wrapper).wrap(object);
}
}
private ESBase convertDate(Object object)
{
return ESDate.create(((Date) object).getTime());
}
// XXX: backwards -- s/b wrap and staticWrap
public static ESBase wrap(Object object)
throws Throwable
{
return getGlobalProto().objectWrap(object);
}
ESBase classWrap(Class cl)
throws Throwable
{
if (cl == null)
throw new RuntimeException();
ESBase clWrapper;
synchronized (_staticWraps) {
clWrapper = _staticClassWraps.get(cl);
}
if (clWrapper == null || ((DynamicClassLoader) clWrapper.getClass().getClassLoader()).isDestroyed()) {
ESBase []values = Wrapper.bean(this, cl);
clWrapper = values[0];
ESBase wrapper = values[1];
synchronized (_staticWraps) {
_staticWraps.put(cl, wrapper);
_staticClassWraps.put(cl, clWrapper);
}
}
return clWrapper;
}
public ClassLoader getClassLoader()
{
return loader != null ? loader : root.loader;
}
public ClassLoader getParentLoader()
{
return parentLoader != null ? parentLoader : root.parentLoader;
}
public Path getClassDir()
{
return classDir != null ? classDir : root.classDir;
}
public Path getScriptPath()
{
return scriptPath != null ? scriptPath : root.scriptPath;
}
public void importScript(ESObject global, String name)
throws Throwable
{
if (importScripts == null) {
importScripts = new HashMap();
importGlobals = new HashMap();
}
ESGlobal scriptGlobal = (ESGlobal) importGlobals.get(name);
if (scriptGlobal == null) {
if (importScripts.get(name) != null)
return;
Parser parser = new Parser();
parser.setScriptPath(getScriptPath());
parser.setClassLoader(getClassLoader());
parser.setWorkDir(getClassDir());
Script script = parser.parse(name);
importScripts.put(name, script);
scriptGlobal = script.initClass(this);
importGlobals.put(name, scriptGlobal);
scriptGlobal.execute();
}
scriptGlobal.export(global);
}
ESGlobal getGlobal()
{
return global;
}
public void setGlobal(ESGlobal global)
{
this.global = global;
}
public Object toJavaObject()
throws ESException
{
Object o = prototype.toJavaObject();
return (o == null) ? this : o;
}
/*
* Somewhat bogus for creating based on globals.
* "this" is the global
*/
public ESObject createObject()
{
return new ESObject("Object", objProto);
}
/**
* Somewhat bogus for creating based on globals.
* "this" is the global
*/
ESArray createArray()
{
ESArray array = new ESArray();
array.prototype = arrayProto;
return array;
}
void clearMark()
{
markCount = 0;
}
int addMark()
{
return ++markCount;
}
}