helma/src/helma/scripting/rhino/RhinoCore.java
hns cb92ec8469 * Add boolean resolve argument to ScriptingEngine.invoke() to tell the engine if
functionName argument should be resolved as member expression. Use this feature
  to allow calling nested/deep functions in internal invocations (e.g. scheduler calls).
  Fixes bug 290.
2006-01-11 15:28:52 +00:00

1127 lines
39 KiB
Java

/*
* Helma License Notice
*
* The contents of this file are subject to the Helma License
* Version 2.0 (the "License"). You may not use this file except in
* compliance with the License. A copy of the License is available at
* http://adele.helma.org/download/helma/license.txt
*
* Copyright 1998-2003 Helma Software. All Rights Reserved.
*
* $RCSfile$
* $Author$
* $Revision$
* $Date$
*/
package helma.scripting.rhino;
import helma.scripting.rhino.extensions.*;
import helma.scripting.rhino.debug.HelmaDebugger;
import helma.scripting.rhino.debug.ScopeProvider;
import helma.framework.core.*;
import helma.framework.repository.Resource;
import helma.objectmodel.*;
import helma.objectmodel.db.DbMapping;
import helma.objectmodel.db.DbKey;
import helma.scripting.*;
import helma.util.CacheMap;
import helma.util.SystemMap;
import helma.util.WrappedMap;
import org.mozilla.javascript.*;
import java.io.*;
import java.text.*;
import java.util.*;
/**
* This is the implementation of ScriptingEnvironment for the Mozilla Rhino EcmaScript interpreter.
*/
public final class RhinoCore implements ScopeProvider {
// the application we're running in
public final Application app;
// the global object
DynamicGlobalObject global;
// caching table for JavaScript object wrappers
CacheMap wrappercache;
// table containing JavaScript prototypes
Hashtable prototypes;
// timestamp of last type update
volatile long lastUpdate = 0;
// the wrap factory
WrapFactory wrapper;
// the prototype for HopObject
ScriptableObject hopObjectProto;
// the prototype for path objects
PathWrapper pathProto;
// Any error that may have been found in global code
String globalError;
HelmaDebugger debugger = null;
// dynamic portion of the type check sleep that grows
// as the app remains unchanged
long updateSnooze = 500;
/**
* Create a Rhino evaluator for the given application and request evaluator.
*/
public RhinoCore(Application app) {
this.app = app;
// wrappercache = new CacheMap(500, .75f);
wrappercache = new CacheMap(500);
prototypes = new Hashtable();
Context context = Context.enter();
context.setCompileFunctionsWithDynamicScope(true);
context.setApplicationClassLoader(app.getClassLoader());
wrapper = new WrapMaker();
wrapper.setJavaPrimitiveWrap(false);
context.setWrapFactory(wrapper);
// Set up visual debugger if rhino.debug = true
if ("true".equals(app.getProperty("rhino.debug"))) {
initDebugger(context);
}
// Set default optimization level according to whether debugger is on
int optLevel = debugger == null ? 0 : -1;
try {
optLevel = Integer.parseInt(app.getProperty("rhino.optlevel"));
} catch (Exception ignore) {
}
context.setOptimizationLevel(optLevel);
try {
// create global object
global = new DynamicGlobalObject(this, app);
// call the initStandardsObject in ImporterTopLevel so that
// importClass() and importPackage() are set up.
global.initStandardObjects(context, false);
global.init();
pathProto = new PathWrapper(this);
hopObjectProto = HopObject.init(global);
// use lazy loaded constructors for all extension objects that
// adhere to the ScriptableObject.defineClass() protocol
new LazilyLoadedCtor(global, "File",
"helma.scripting.rhino.extensions.FileObject", false);
new LazilyLoadedCtor(global, "Ftp",
"helma.scripting.rhino.extensions.FtpObject", false);
new LazilyLoadedCtor(global, "Image",
"helma.scripting.rhino.extensions.ImageObject", false);
new LazilyLoadedCtor(global, "Remote",
"helma.scripting.rhino.extensions.XmlRpcObject", false);
MailObject.init(global, app.getProperties());
// add some convenience functions to string, date and number prototypes
Scriptable stringProto = ScriptableObject.getClassPrototype(global, "String");
stringProto.put("trim", stringProto, new StringTrim());
Scriptable dateProto = ScriptableObject.getClassPrototype(global, "Date");
dateProto.put("format", dateProto, new DateFormat());
Scriptable numberProto = ScriptableObject.getClassPrototype(global, "Number");
numberProto.put("format", numberProto, new NumberFormat());
initialize();
} catch (Exception e) {
System.err.println("Cannot initialize interpreter");
System.err.println("Error: " + e);
e.printStackTrace();
throw new RuntimeException(e.getMessage());
} finally {
Context.exit();
if (debugger != null) {
debugger.contextExited(context);
debugger.contextReleased(context);
}
}
}
void initDebugger(Context context) {
if (debugger == null) {
debugger = new HelmaDebugger(app.getName());
debugger.setScopeProvider(this);
debugger.pack();
debugger.setLocation(60, 60);
}
if (!debugger.isVisible())
debugger.setVisible(true);
debugger.contextCreated(context);
debugger.contextEntered(context);
}
/**
* Initialize the evaluator, making sure the minimum type information
* necessary to bootstrap the rest is parsed.
*/
private synchronized void initialize() {
Collection protos = app.getPrototypes();
for (Iterator i = protos.iterator(); i.hasNext();) {
Prototype proto = (Prototype) i.next();
initPrototype(proto);
}
// always fully initialize global prototype, because
// we always need it and there's no chance to trigger
// creation on demand.
getPrototype("global");
}
/**
* Initialize a prototype info without compiling its script files.
*
* @param prototype the prototype to be created
*/
private synchronized void initPrototype(Prototype prototype) {
String name = prototype.getName();
String lowerCaseName = prototype.getLowerCaseName();
TypeInfo type = (TypeInfo) prototypes.get(lowerCaseName);
// check if the prototype info exists already
ScriptableObject op = (type == null) ? null : type.objProto;
// if prototype info doesn't exist (i.e. is a standard prototype
// built by HopExtension), create it.
if (op == null) {
if ("global".equals(lowerCaseName)) {
op = global;
} else if ("hopobject".equals(lowerCaseName)) {
op = hopObjectProto;
} else {
op = new HopObject(name);
op.setParentScope(global);
}
type = registerPrototype(prototype, op);
}
// Register a constructor for all types except global.
// This will first create a new prototyped HopObject and then calls
// the actual (scripted) constructor on it.
if (!"global".equals(lowerCaseName)) {
try {
FunctionObject fo = new FunctionObject(name, HopObject.hopObjCtor, global);
fo.addAsConstructor(global, op);
// add static getById() function
fo.defineProperty("getById", new GetById(name), GetById.ATTRIBUTES);
} catch (Exception ignore) {
System.err.println("Error adding ctor for " + name + ": " + ignore);
ignore.printStackTrace();
}
}
}
/**
* Set up a prototype, parsing and compiling all its script files.
*
* @param type the info, containing the object proto, last update time and
* the set of compiled functions properties
*/
private synchronized void evaluatePrototype(TypeInfo type) {
type.prepareCompilation();
Prototype prototype = type.frameworkProto;
// set the parent prototype in case it hasn't been done before
// or it has changed...
setParentPrototype(prototype, type);
type.error = null;
if ("global".equals(prototype.getLowerCaseName())) {
globalError = null;
}
// loop through the prototype's code elements and evaluate them
Iterator code = prototype.getCodeResources();
while (code.hasNext()) {
evaluate(type, (Resource) code.next());
}
type.commitCompilation();
}
/**
* Set the parent prototype on the ObjectPrototype.
*
* @param prototype the prototype spec
* @param type the prototype object info
*/
private void setParentPrototype(Prototype prototype, TypeInfo type) {
String name = prototype.getName();
String lowerCaseName = prototype.getLowerCaseName();
if (!"global".equals(lowerCaseName) && !"hopobject".equals(lowerCaseName)) {
// get the prototype's prototype if possible and necessary
TypeInfo parentType = null;
Prototype parent = prototype.getParentPrototype();
if (parent != null) {
// see if parent prototype is already registered. if not, register it
parentType = getPrototypeInfo(parent.getName());
}
if (parentType == null && !app.isJavaPrototype(name)) {
// FIXME: does this ever occur?
parentType = getPrototypeInfo("hopobject");
}
type.setParentType(parentType);
}
}
/**
* This method is called before an execution context is entered to let the
* engine know it should update its prototype information. The update policy
* here is to check for update those prototypes which already have been compiled
* before. Others will be updated/compiled on demand.
*/
public void updatePrototypes() throws IOException {
if ((System.currentTimeMillis() - lastUpdate) < 1000L + updateSnooze) {
return;
}
synchronized(this) {
if ((System.currentTimeMillis() - lastUpdate) < 1000L + updateSnooze) {
return;
}
// init prototypes and/or update prototype checksums
app.typemgr.checkPrototypes();
// get a collection of all prototypes (code directories)
Collection protos = app.getPrototypes();
// in order to respect inter-prototype dependencies, we try to update
// the global prototype before all other prototypes, and parent
// prototypes before their descendants.
HashSet checked = new HashSet(protos.size() * 2);
TypeInfo type = (TypeInfo) prototypes.get("global");
if (type != null) {
updatePrototype(type, checked);
}
for (Iterator i = protos.iterator(); i.hasNext();) {
Prototype proto = (Prototype) i.next();
if (checked.contains(proto)) {
continue;
}
type = (TypeInfo) prototypes.get(proto.getLowerCaseName());
if (type == null) {
// a prototype we don't know anything about yet. Init local update info.
initPrototype(proto);
} else if (type.lastUpdate > -1) {
// only need to update prototype if it has already been initialized.
// otherwise, this will be done on demand.
updatePrototype(type, checked);
}
}
lastUpdate = System.currentTimeMillis();
// max updateSnooze is 4 seconds, reached after 66.6 idle minutes
long newSnooze = (lastUpdate - app.typemgr.getLastCodeUpdate()) / 1000;
updateSnooze = Math.min(4000, Math.max(0, newSnooze));
}
}
/**
* Check one prototype for updates. Used by <code>upatePrototypes()</code>.
*
* @param type the type info to check
* @param checked a set of prototypes that have already been checked
*/
private void updatePrototype(TypeInfo type, HashSet checked) {
// first, remember prototype as updated
checked.add(type.frameworkProto);
if (type.parentType != null &&
!checked.contains(type.parentType.frameworkProto)) {
updatePrototype(type.getParentType(), checked);
}
// let the prototype check if its resources have changed
type.frameworkProto.checkForUpdates();
// and re-evaluate if necessary
if (type.needsUpdate()) {
evaluatePrototype(type);
}
}
/**
* A version of getPrototype() that retrieves a prototype and checks
* if it is valid, i.e. there were no errors when compiling it. If
* invalid, a ScriptingException is thrown.
*/
public Scriptable getValidPrototype(String protoName) {
if (globalError != null) {
throw new EvaluatorException(globalError);
}
TypeInfo type = getPrototypeInfo(protoName);
if (type != null && type.hasError()) {
throw new EvaluatorException(type.getError());
}
return type == null ? null : type.objProto;
}
/**
* Get the object prototype for a prototype name and initialize/update it
* if necessary. The policy here is to update the prototype only if it
* hasn't been updated before, otherwise we assume it already was updated
* by updatePrototypes(), which is called for each request.
*/
public Scriptable getPrototype(String protoName) {
TypeInfo type = getPrototypeInfo(protoName);
return type == null ? null : type.objProto;
}
/**
* Get an array containing the property ids of all properties that were
* compiled from scripts for the given prototype.
*
* @param protoName the name of the prototype
* @return an array containing all compiled properties of the given prototype
*/
public Map getPrototypeProperties(String protoName) {
TypeInfo type = getPrototypeInfo(protoName);
SystemMap map = new SystemMap();
Iterator it = type.compiledProperties.iterator();
while(it.hasNext()) {
Object key = it.next();
if (key instanceof String)
map.put(key, type.objProto.get((String) key, type.objProto));
}
return map;
}
/**
* Private helper function that retrieves a prototype's TypeInfo
* and creates it if not yet created. This is used by getPrototype() and
* getValidPrototype().
*/
private TypeInfo getPrototypeInfo(String protoName) {
if (protoName == null) {
return null;
}
TypeInfo type = (TypeInfo) prototypes.get(protoName.toLowerCase());
// if type exists and hasn't been evaluated (used) yet, evaluate it now.
// otherwise, it has already been evaluated for this request by updatePrototypes(),
// which is called before a request is handled.
if ((type != null) && (type.lastUpdate == -1)) {
type.frameworkProto.checkForUpdates();
if (type.needsUpdate()) {
evaluatePrototype(type);
}
}
return type;
}
/**
* Register an object prototype for a prototype name.
*/
private TypeInfo registerPrototype(Prototype proto, ScriptableObject op) {
TypeInfo type = new TypeInfo(proto, op);
prototypes.put(proto.getLowerCaseName(), type);
return type;
}
/**
* Check if an object has a function property (public method if it
* is a java object) with that name.
*/
public boolean hasFunction(String protoname, String fname) {
// System.err.println ("HAS_FUNC: "+fname);
// throws EvaluatorException if type has a syntax error
Scriptable op = getValidPrototype(protoname);
try {
// if this is an untyped object return false
if (op == null) {
return false;
}
Object func = ScriptableObject.getProperty(op, fname);
if ((func != null) && func instanceof Function) {
return true;
}
} catch (Exception esx) {
// System.err.println ("Error in hasFunction: "+esx);
return false;
}
return false;
}
/**
* Convert an input argument from Java to the scripting runtime
* representation.
*/
public Object processXmlRpcArgument (Object what) throws Exception {
if (what == null)
return null;
if (what instanceof Vector) {
Vector v = (Vector) what;
Object[] a = v.toArray();
for (int i=0; i<a.length; i++) {
a[i] = processXmlRpcArgument(a[i]);
}
return Context.getCurrentContext().newArray(global, a);
}
if (what instanceof Hashtable) {
Hashtable t = (Hashtable) what;
for (Enumeration e=t.keys(); e.hasMoreElements(); ) {
Object key = e.nextElement();
t.put(key, processXmlRpcArgument(t.get(key)));
}
return Context.toObject(new SystemMap(t), global);
}
if (what instanceof String)
return what;
if (what instanceof Number)
return what;
if (what instanceof Boolean)
return what;
if (what instanceof Date) {
Date d = (Date) what;
Object[] args = { new Long(d.getTime()) };
return Context.getCurrentContext().newObject(global, "Date", args);
}
return Context.toObject(what, global);
}
/**
* convert a JavaScript Object object to a generic Java object stucture.
*/
public Object processXmlRpcResponse (Object what) throws Exception {
// unwrap if argument is a Wrapper
if (what instanceof Wrapper) {
what = ((Wrapper) what).unwrap();
}
if (what instanceof NativeObject) {
NativeObject no = (NativeObject) what;
Object[] ids = no.getIds();
Hashtable ht = new Hashtable(ids.length*2);
for (int i=0; i<ids.length; i++) {
if (ids[i] instanceof String) {
String key = (String) ids[i];
Object o = no.get(key, no);
if (o != null) {
ht.put(key, processXmlRpcResponse(o));
}
}
}
what = ht;
} else if (what instanceof NativeArray) {
NativeArray na = (NativeArray) what;
Number n = (Number) na.get("length", na);
int l = n.intValue();
Vector retval = new Vector(l);
for (int i=0; i<l; i++) {
retval.add(i, processXmlRpcResponse(na.get(i, na)));
}
what = retval;
} else if (what instanceof Map) {
Map map = (Map) what;
Hashtable ht = new Hashtable(map.size()*2);
for (Iterator it=map.entrySet().iterator(); it.hasNext();) {
Map.Entry entry = (Map.Entry) it.next();
ht.put(entry.getKey().toString(),
processXmlRpcResponse(entry.getValue()));
}
what = ht;
} else if (what instanceof Number) {
Number n = (Number) what;
if (what instanceof Float || what instanceof Long) {
what = new Double(n.doubleValue());
} else if (!(what instanceof Double)) {
what = new Integer(n.intValue());
}
} else if (what instanceof Scriptable) {
Scriptable s = (Scriptable) what;
if ("Date".equals(s.getClassName())) {
what = new Date((long) ScriptRuntime.toNumber(s));
}
}
return what;
}
/**
* Return the application we're running in
*/
public Application getApplication() {
return app;
}
/**
* Get a Script wrapper for any given object. If the object implements the IPathElement
* interface, the getPrototype method will be used to retrieve the name of the prototype
* to use. Otherwise, a Java-Class-to-Script-Prototype mapping is consulted.
*/
public Scriptable getElementWrapper(Object e) {
Scriptable w = (Scriptable) wrappercache.get(e);
if (w == null) {
// Gotta find out the prototype name to use for this object...
String prototypeName = app.getPrototypeName(e);
Scriptable op = getPrototype(prototypeName);
if (op == null) {
// no prototype found, return an unscripted wrapper
w = new NativeJavaObject(global, e, e.getClass());
} else {
w = new JavaObject(global, e, prototypeName, op, this);
}
wrappercache.put(e, w);
}
return w;
}
/**
* Get a script wrapper for an instance of helma.objectmodel.INode
*/
public Scriptable getNodeWrapper(INode n) {
if (n == null) {
return null;
}
HopObject esn = (HopObject) wrappercache.get(n);
if (esn == null) {
String protoname = n.getPrototype();
Scriptable op = null;
op = getValidPrototype(protoname);
// no prototype found for this node
if (op == null) {
// maybe this object has a prototype name that has been
// deleted, but the storage layer was able to set a
// DbMapping matching the relational table the object
// was fetched from.
DbMapping dbmap = n.getDbMapping();
if (dbmap != null && (protoname = dbmap.getTypeName()) != null) {
op = getValidPrototype(protoname);
}
// if not found, fall back to HopObject prototype
if (op == null) {
protoname = "HopObject";
op = getValidPrototype("HopObject");
}
}
esn = new HopObject(protoname, op);
esn.init(this, n);
wrappercache.put(n, esn);
}
return esn;
}
protected String postProcessHref(Object obj, String protoName, String basicHref)
throws UnsupportedEncodingException, IOException {
// check if the app.properties specify a href-function to post-process the
// basic href.
String hrefFunction = app.getProperty("hrefFunction", null);
if (hrefFunction != null) {
Object handler = obj;
String proto = protoName;
while (handler != null) {
if (hasFunction(proto, hrefFunction)) {
// get the currently active rhino engine and invoke the function
Context cx = Context.getCurrentContext();
RhinoEngine engine = (RhinoEngine) cx.getThreadLocal("engine");
Object result = null;
try {
result = engine.invoke(handler, hrefFunction,
new Object[] { basicHref },
ScriptingEngine.ARGS_WRAP_DEFAULT,
false);
} catch (ScriptingException x) {
throw new EvaluatorException("Error in hrefFunction: " + x);
}
if (result == null) {
throw new EvaluatorException("hrefFunction " + hrefFunction +
" returned null");
}
basicHref = result.toString();
break;
}
handler = app.getParentElement(handler);
proto = app.getPrototypeName(handler);
}
}
// check if the app.properties specify a href-skin to post-process the
// basic href.
String hrefSkin = app.getProperty("hrefSkin", null);
if (hrefSkin != null) {
// we need to post-process the href with a skin for this application
// first, look in the object href was called on.
Skin skin = null;
Object handler = obj;
// get the currently active rhino engine and render the skin
Context cx = Context.getCurrentContext();
RhinoEngine engine = (RhinoEngine) cx.getThreadLocal("engine");
while (handler != null) {
Prototype proto = app.getPrototype(handler);
if (proto != null) {
skin = engine.getSkin(proto.getName(), hrefSkin);
}
if (skin != null) {
break;
}
handler = app.getParentElement(handler);
}
if (skin != null) {
engine.getResponse().pushStringBuffer();
HashMap param = new HashMap();
param.put("path", basicHref);
skin.render(engine.getRequestEvaluator(), handler, param);
basicHref = engine.getResponse().popStringBuffer().trim();
}
}
return basicHref;
}
/////////////////////////////////////////////
// skin related methods
/////////////////////////////////////////////
protected static Object[] unwrapSkinpath(Object[] skinpath) {
if (skinpath != null) {
for (int i=0; i<skinpath.length; i++) {
if (skinpath[i] instanceof HopObject) {
skinpath[i] = ((HopObject) skinpath[i]).getNode();
} else if (skinpath[i] instanceof Wrapper) {
skinpath[i] = ((Wrapper) skinpath[i]).unwrap();
}
}
}
return skinpath;
}
protected static Map getSkinParam(Object paramobj) {
Map param = null;
if ((paramobj != null) && (paramobj != Undefined.instance)) {
param = new HashMap();
if (paramobj instanceof Scriptable) {
Scriptable sp = (Scriptable) paramobj;
Object[] ids = sp.getIds();
for (int i = 0; i < ids.length; i++) {
Object obj = sp.get(ids[i].toString(), sp);
if (obj instanceof Wrapper) {
param.put(ids[i], ((Wrapper) obj).unwrap());
} else if (obj != Undefined.instance) {
param.put(ids[i], obj);
}
}
}
}
return param;
}
////////////////////////////////////////////////
// private evaluation/compilation methods
////////////////////////////////////////////////
private synchronized void evaluate (TypeInfo type, Resource code) {
// get the current context
Context cx = Context.getCurrentContext();
// unregister the per-thread scope while evaluating
Object threadScope = cx.getThreadLocal("threadscope");
cx.removeThreadLocal("threadscope");
String sourceName = code.getName();
Reader reader = null;
try {
Scriptable op = type.objProto;
// do the update, evaluating the file
if (sourceName.endsWith(".js")) {
reader = new InputStreamReader(code.getInputStream());
cx.evaluateReader(op, reader, sourceName, 1, null);
} else if (sourceName.endsWith(".hac")) {
reader = new StringReader(HacHspConverter.convertHac(code));
cx.evaluateReader(op, reader, sourceName, 0, null);
} else if (sourceName.endsWith(".hsp")) {
reader = new StringReader(HacHspConverter.convertHsp(code));
cx.evaluateReader(op, reader, sourceName, 0, null);
reader = new StringReader(HacHspConverter.convertHspAsString(code));
cx.evaluateReader(op, reader, sourceName, 0, null);
}
} catch (Exception e) {
app.logError("Error parsing file " + sourceName, e);
// mark prototype as broken
if (type.error == null) {
type.error = e.getMessage();
if (type.error == null || e instanceof EcmaError) {
type.error = e.toString();
}
if ("global".equals(type.frameworkProto.getLowerCaseName())) {
globalError = type.error;
}
wrappercache.clear();
}
// e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException ignore) {
// shouldn't happen
}
}
if (threadScope != null) {
cx.putThreadLocal("threadscope", threadScope);
}
}
}
/**
* Return the global scope of this RhinoCore.
*/
public Scriptable getScope() {
return global;
}
/**
* TypeInfo helper class
*/
class TypeInfo {
// the framework prototype object
Prototype frameworkProto;
// the JavaScript prototype for this type
ScriptableObject objProto;
// timestamp of last update. This is -1 so even an empty prototype directory
// (with lastUpdate == 0) gets evaluated at least once, which is necessary
// to get the prototype chain set.
long lastUpdate = -1;
// the parent prototype info
TypeInfo parentType;
// a set of property keys that were in script compilation.
// Used to decide which properties should be removed if not renewed.
Set compiledProperties;
// a set of property keys that were present before first script compilation
final Set predefinedProperties;
String error;
public TypeInfo(Prototype proto, ScriptableObject op) {
frameworkProto = proto;
objProto = op;
// remember properties already defined on this object prototype
compiledProperties = new HashSet();
predefinedProperties = new HashSet();
Object[] keys = op.getAllIds();
for (int i = 0; i < keys.length; i++) {
predefinedProperties.add(keys[i].toString());
}
}
/**
* If prototype implements PropertyRecorder tell it to start
* registering property puts.
*/
public void prepareCompilation() {
if (objProto instanceof PropertyRecorder) {
((PropertyRecorder) objProto).startRecording();
}
}
/**
* Compilation has been completed successfully - switch over to code
* from temporary prototype, removing properties that haven't been
* renewed.
*/
public void commitCompilation() {
// loop through properties defined on the prototype object
// and remove thos properties which haven't been renewed during
// this compilation/evaluation pass.
if (objProto instanceof PropertyRecorder) {
PropertyRecorder recorder = (PropertyRecorder) objProto;
recorder.stopRecording();
Set changedProperties = recorder.getChangeSet();
recorder.clearChangeSet();
// ignore all properties that were defined before we started
// compilation. We won't manage these properties, even
// if they were set during compilation.
changedProperties.removeAll(predefinedProperties);
// remove all renewed properties from the previously compiled
// property names so we can remove those properties that were not
// renewed in this compilation
compiledProperties.removeAll(changedProperties);
Iterator it = compiledProperties.iterator();
while (it.hasNext()) {
String key = (String) it.next();
try {
objProto.setAttributes(key, 0);
objProto.delete(key);
} catch (Exception px) {
System.err.println("Error unsetting property "+key+" on "+
frameworkProto.getName());
}
}
// update compiled properties
compiledProperties = changedProperties;
}
// mark this type as updated
lastUpdate = frameworkProto.lastCodeUpdate();
// If this prototype defines a postCompile() function, call it
Context cx = Context.getCurrentContext();
try {
Object fObj = ScriptableObject.getProperty(objProto,
"onCodeUpdate");
if (fObj instanceof Function) {
Object[] args = {frameworkProto.getName()};
((Function) fObj).call(cx, global, objProto, args);
}
} catch (Exception x) {
app.logError("Exception in "+frameworkProto.getName()+
".onCodeUpdate(): " + x, x);
}
}
public boolean needsUpdate() {
return frameworkProto.lastCodeUpdate() > lastUpdate;
}
public void setParentType(TypeInfo type) {
parentType = type;
if (type == null) {
objProto.setPrototype(null);
} else {
objProto.setPrototype(type.objProto);
}
}
public TypeInfo getParentType() {
return parentType;
}
public boolean hasError() {
TypeInfo p = this;
while (p != null) {
if (p.error != null)
return true;
p = p.parentType;
}
return false;
}
public String getError() {
TypeInfo p = this;
while (p != null) {
if (p.error != null)
return p.error;
p = p.parentType;
}
return null;
}
public String toString() {
return ("TypeInfo[" + frameworkProto + "," + new Date(lastUpdate) + "]");
}
}
/**
* Object wrapper class
*/
class WrapMaker extends WrapFactory {
public Object wrap(Context cx, Scriptable scope, Object obj, Class staticType) {
// Wrap Nodes
if (obj instanceof INode) {
return getNodeWrapper((INode) obj);
}
// Masquerade SystemMap and WrappedMap as native JavaScript objects
if (obj instanceof SystemMap || obj instanceof WrappedMap) {
return new MapWrapper((Map) obj, RhinoCore.this);
}
// Convert java.util.Date objects to JavaScript Dates
if (obj instanceof Date) {
Object[] args = { new Long(((Date) obj).getTime()) };
try {
return cx.newObject(global, "Date", args);
} catch (JavaScriptException nafx) {
return obj;
}
}
// Wrap scripted Java objects
if (obj != null && app.getPrototypeName(obj) != null) {
return getElementWrapper(obj);
}
return super.wrap(cx, scope, obj, staticType);
}
public Scriptable wrapNewObject(Context cx, Scriptable scope, Object obj) {
// System.err.println ("N-Wrapping: "+obj);
if (obj instanceof INode) {
return getNodeWrapper((INode) obj);
}
if (obj != null && app.getPrototypeName(obj) != null) {
return getElementWrapper(obj);
}
return super.wrapNewObject(cx, scope, obj);
}
}
class StringTrim extends BaseFunction {
public Object call(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
String str = thisObj.toString();
return str.trim();
}
}
class DateFormat extends BaseFunction {
public Object call(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
Date date = new Date((long) ScriptRuntime.toNumber(thisObj));
SimpleDateFormat df = null;
if (args.length > 0 && args[0] != Undefined.instance && args[0] != null) {
if (args.length > 1 && args[1] instanceof NativeJavaObject) {
Object locale = ((NativeJavaObject) args[1]).unwrap();
if (locale instanceof Locale) {
df = new SimpleDateFormat(args[0].toString(), (Locale) locale);
} else {
throw new IllegalArgumentException("Second argument to Date.format() not a java.util.Locale: " +
locale.getClass());
}
} else {
df = new SimpleDateFormat(args[0].toString());
}
} else {
df = new SimpleDateFormat();
}
return df.format(date);
}
}
class NumberFormat extends BaseFunction {
public Object call(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
DecimalFormat df = null;
if (args.length > 0 && args[0] != Undefined.instance) {
df = new DecimalFormat(args[0].toString());
} else {
df = new DecimalFormat("#,##0.00");
}
return df.format(ScriptRuntime.toNumber(thisObj)).toString();
}
}
class GetById extends BaseFunction {
static final int ATTRIBUTES = DONTENUM | PERMANENT | READONLY;
String typeName;
public GetById(String typeName) {
this.typeName = typeName;
}
/**
* Retrieve any persistent HopObject by type name and id.
*
* @return the HopObject or null if it doesn't exist
*/
public Object call(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
if (args.length < 1 || args.length > 2)
throw new IllegalArgumentException("Wrong number of arguments in getById()");
// If second argument is provided, use it as type name.
// Otherwise, use our own type name.
String type = args.length == 1 ? typeName: Context.toString(args[1]);
DbMapping dbmap = app.getDbMapping(type);
if (dbmap == null)
return null;
Object node = null;
try {
DbKey key = new DbKey(dbmap, Context.toString(args[0]));
node = app.getNodeManager().getNode(key);
} catch (Exception x) {
return null;
}
return node == null ? null : Context.toObject(node, this);
}
}
}