Make type handling case-insensitive, change default prototypes to first-letter-upper-case,

do some heavily needed refactoring around helma.scripting.rhino.HopObject and
helma.framework.core.TypeManager.
This commit is contained in:
hns 2003-12-18 17:34:13 +00:00
parent 3f284fa899
commit e57a939fa0
10 changed files with 220 additions and 155 deletions

View file

@ -283,7 +283,7 @@ public final class Application implements IPathElement, Runnable {
// set the context classloader. Note that this must be done before
// using the logging framework so that a new LogFactory gets created
// for this app.
Thread.currentThread().setContextClassLoader(typemgr.loader);
Thread.currentThread().setContextClassLoader(typemgr.getClassLoader());
if (Server.getServer() != null) {
Vector extensions = Server.getServer().getExtensions();
@ -356,7 +356,7 @@ public final class Application implements IPathElement, Runnable {
nmgr = new NodeManager(this, dbDir.getAbsolutePath(), props);
// reset the classloader to the parent/system/server classloader.
Thread.currentThread().setContextClassLoader(typemgr.loader.getParent());
Thread.currentThread().setContextClassLoader(typemgr.getClassLoader().getParent());
}
@ -695,13 +695,15 @@ public final class Application implements IPathElement, Runnable {
try {
if (classMapping.containsKey("root.factory.class") &&
classMapping.containsKey("root.factory.method")) {
Class c = typemgr.loader.loadClass(classMapping.getProperty("root.factory.class"));
String rootFactory = classMapping.getProperty("root.factory.class");
Class c = typemgr.getClassLoader().loadClass(rootFactory);
Method m = c.getMethod(classMapping.getProperty("root.factory.method"),
null);
rootObject = m.invoke(c, null);
} else {
Class c = typemgr.loader.loadClass(classMapping.getProperty("root"));
String rootClass = classMapping.getProperty("root");
Class c = typemgr.getClassLoader().loadClass(rootClass);
rootObject = c.newInstance();
}
@ -795,14 +797,14 @@ public final class Application implements IPathElement, Runnable {
* Return the prototype with the given name, if it exists
*/
public Prototype getPrototypeByName(String name) {
return (Prototype) typemgr.prototypes.get(name);
return typemgr.getPrototype(name);
}
/**
* Return a collection containing all prototypes defined for this application
*/
public Collection getPrototypes() {
return typemgr.prototypes.values();
return typemgr.getPrototypes();
}
/**
@ -1193,7 +1195,7 @@ public final class Application implements IPathElement, Runnable {
* Return the application's classloader
*/
public ClassLoader getClassLoader() {
return typemgr.loader;
return typemgr.getClassLoader();
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////

View file

@ -19,6 +19,7 @@ package helma.framework.core;
import helma.objectmodel.db.DbMapping;
import helma.scripting.*;
import helma.util.SystemMap;
import helma.util.SystemProperties;
import java.io.*;
import java.util.*;
@ -30,6 +31,7 @@ import java.util.*;
*/
public final class Prototype {
String name;
String lowerCaseName;
Application app;
File directory;
File[] files;
@ -61,15 +63,37 @@ public final class Prototype {
/**
* Creates a new Prototype object.
*
* @param name ...
* @param dir ...
* @param app ...
* @param name the prototype's name
* @param dir the prototype directory, if known
* @param app the application this prototype is a part of
*/
public Prototype(String name, File dir, Application app) {
// app.logEvent ("Constructing Prototype "+app.getName()+"/"+name);
this.app = app;
this.name = name;
this.directory = dir;
lowerCaseName = name.toLowerCase();
if (dir != null) {
directory = dir;
} else {
directory = new File(app.appDir, name);
// a little bit of overkill to maintain backwards compatibility
// with lower case type names...
if (!directory.isDirectory()) {
File lowerDir = new File(app.appDir, lowerCaseName);
if (lowerDir.isDirectory()) {
directory = lowerDir;
}
}
}
// Create and register type properties file
File propfile = new File(directory, "type.properties");
SystemProperties props = new SystemProperties(propfile.getAbsolutePath());
dbmap = new DbMapping(app, name, props);
// we don't need to put the DbMapping into proto.updatables, because
// dbmappings are checked separately in TypeManager.checkFiles() for
// each request
code = new HashMap();
zippedCode = new HashMap();
@ -145,7 +169,7 @@ public final class Prototype {
*/
public void setParentPrototype(Prototype parent) {
// this is not allowed for the hopobject and global prototypes
if ("hopobject".equalsIgnoreCase(name) || "global".equalsIgnoreCase(name)) {
if ("HopObject".equals(name) || "global".equals(name)) {
return;
}
@ -164,11 +188,11 @@ public final class Prototype {
* Check if the given prototype is within this prototype's parent chain.
*/
public final boolean isInstanceOf(String pname) {
if (name.equals(pname)) {
if (name.equals(pname) || lowerCaseName.equals(pname)) {
return true;
}
if ((parent != null) && !"hopobject".equalsIgnoreCase(parent.getName())) {
if ((parent != null) && !"HopObject".equals(parent.getName())) {
return parent.isInstanceOf(pname);
}
@ -185,12 +209,17 @@ public final class Prototype {
Prototype p = parent;
while ((p != null) && !"hopobject".equalsIgnoreCase(p.getName())) {
while ((p != null) && !"hopobject".equals(p.getLowerCaseName())) {
Object old = handlers.put(p.name, obj);
// if an object was already registered by this name, put it back in again.
if (old != null) {
handlers.put(p.name, old);
}
// same with lower case name
old = handlers.put(p.lowerCaseName, obj);
if (old != null) {
handlers.put(p.lowerCaseName, old);
}
p = p.parent;
}
@ -201,7 +230,7 @@ public final class Prototype {
*
* @param dbmap ...
*/
public void setDbMapping(DbMapping dbmap) {
protected void setDbMapping(DbMapping dbmap) {
this.dbmap = dbmap;
}
@ -245,7 +274,7 @@ public final class Prototype {
}
/**
*
* Return this prototype's name
*
* @return ...
*/
@ -253,6 +282,15 @@ public final class Prototype {
return name;
}
/**
* Return this prototype's name in lower case letters
*
* @return ...
*/
public String getLowerCaseName() {
return lowerCaseName;
}
/**
* Get the last time any script has been re-read for this prototype.
*/

View file

@ -294,6 +294,7 @@ public final class RequestEvaluator implements Runnable {
// immediately register objects with their direct prototype name
if (protos[i] != null) {
macroHandlers.put(protos[i].getName(), obj);
macroHandlers.put(protos[i].getLowerCaseName(), obj);
}
}

View file

@ -61,6 +61,7 @@ public class RequestPath {
if (proto != null) {
primaryProtos.put(proto.getName(), obj);
primaryProtos.put(proto.getLowerCaseName(), obj);
proto.registerParents(secondaryProtos, obj);
}
}

View file

@ -412,7 +412,7 @@ public final class Skin {
// not a global macro - need to find handler object
// was called with this object - check it or its parents for matching prototype
if (!handler.equals("this") &&
!handler.equals(app.getPrototypeName(thisObject))) {
!handler.equalsIgnoreCase(app.getPrototypeName(thisObject))) {
// the handler object is not what we want
Object n = thisObject;

View file

@ -30,30 +30,34 @@ import java.util.*;
* applications and updates the evaluators if anything has changed.
*/
public final class TypeManager {
final static String[] standardTypes = { "user", "global", "root", "hopobject" };
final static String[] standardTypes = { "User", "Global", "Root", "HopObject" };
final static String templateExtension = ".hsp";
final static String scriptExtension = ".js";
final static String actionExtension = ".hac";
final static String skinExtension = ".skin";
Application app;
File appDir;
HashMap prototypes; // map of prototypes
HashMap zipfiles; // map of zipped script files
HashSet jarfiles; // set of Java archives
long lastCheck = 0;
long appDirMod = 0;
private Application app;
private File appDir;
// map of prototypes
private HashMap prototypes;
// map of zipped script files
private HashMap zipfiles;
// set of Java archives
private HashSet jarfiles;
private long lastCheck = 0;
private long appDirMod = 0;
// a checksum that changes whenever something in the application files changes.
long checksum;
private long checksum;
// the hopobject prototype
Prototype hopobjectProto;
// private Prototype hopobjectProto;
// the global prototype
Prototype globalProto;
// private Prototype globalProto;
// app specific class loader, includes jar files in the app directory
AppClassLoader loader;
private AppClassLoader loader;
/**
* Creates a new TypeManager object.
@ -114,13 +118,9 @@ public final class TypeManager {
*/
public void createPrototypes() {
// create standard prototypes.
createPrototype("root");
createPrototype("user");
// get references to hopobject and global protos,
// since we need it regularly when setting parent prototypes.
hopobjectProto = createPrototype("hopobject");
globalProto = createPrototype("global");
for (int i = 0; i < standardTypes.length; i++) {
createPrototype(standardTypes[i], null);
}
// loop through directories and create prototypes
checkFiles();
@ -236,19 +236,12 @@ public final class TypeManager {
DbMapping dbmap = proto.getDbMapping();
if ((dbmap != null) && dbmap.needsUpdate()) {
// call dbmap.update(). This also checks the
// parent prototype for prototypes other than
// global and HopObject, which is a bit awkward...
// I mean we're the type manager, so this should
// be part of our job, right?
dbmap.update();
// this is now done in dbmap.update()!!!
/*if ((proto != hopobjectProto) && (proto != globalProto)) {
// set parent prototype, in case it has changed.
String parentName = dbmap.getExtends();
if (parentName != null) {
proto.setParentPrototype(getPrototype(parentName));
} else if (!app.isJavaPrototype(proto.getName())) {
proto.setParentPrototype(hopobjectProto);
}
} */
}
}
@ -292,40 +285,48 @@ public final class TypeManager {
return checksum;
}
/**
* Return the class loader used by this application.
*
* @return the ClassLoader
*/
public ClassLoader getClassLoader() {
return loader;
}
/**
* Return a collection containing the prototypes defined for this type
* manager.
*
* @return a collection containing the prototypes
*/
public Collection getPrototypes() {
return prototypes.values();
}
/**
* Get a prototype defined for this application
*/
public Prototype getPrototype(String typename) {
return (Prototype) prototypes.get(typename);
if (typename == null) {
return null;
}
return (Prototype) prototypes.get(typename.toLowerCase());
}
/**
* Get a prototype, creating it if it doesn't already exist. Note
* that it doesn't create a DbMapping - this is left to the
* caller (e.g. ZippedAppFile).
*/
public Prototype createPrototype(String typename) {
return createPrototype(typename, new File(appDir, typename));
}
/**
* Create a prototype from a directory containing scripts and other stuff
* Create and register a new Prototype.
*
* @param typename the name of the prototype
* @param dir the prototype directory if it is know, or null if we
* ought to find out by ourselves
* @return the newly created prototype
*/
public Prototype createPrototype(String typename, File dir) {
Prototype proto = new Prototype(typename, dir, app);
// Create and register type properties file
File propfile = new File(dir, "type.properties");
SystemProperties props = new SystemProperties(propfile.getAbsolutePath());
DbMapping dbmap = new DbMapping(app, typename, props);
// we don't need to put the DbMapping into proto.updatables, because
// dbmappings are checked separately in checkFiles for each request
// proto.updatables.put ("type.properties", dbmap);
proto.setDbMapping(dbmap);
// put the prototype into our map
prototypes.put(typename, proto);
prototypes.put(proto.getLowerCaseName(), proto);
return proto;
}

View file

@ -97,7 +97,7 @@ public class ZippedAppFile implements Updatable {
Prototype proto = app.typemgr.getPrototype(dir);
if (proto == null) {
proto = app.typemgr.createPrototype(dir);
proto = app.typemgr.createPrototype(dir, null);
newPrototypes.add(proto);
}

View file

@ -505,7 +505,7 @@ public final class Node implements INode, Serializable {
public String getPrototype() {
// if prototype is null, it's a vanilla HopObject.
if (prototype == null) {
return "hopobject";
return "HopObject";
}
return prototype;

View file

@ -78,6 +78,35 @@ public class HopObject extends ScriptableObject implements Wrapper {
setPrototype(proto);
}
public static HopObject init(Scriptable scope)
throws PropertyException {
int attributes = READONLY | DONTENUM | PERMANENT;
// create prototype object
HopObject proto = new HopObject();
proto.setPrototype(getObjectPrototype(scope));
// install JavaScript methods and properties
Method[] methods = HopObject.class.getDeclaredMethods();
for (int i=0; i<methods.length; i++) {
String methodName = methods[i].getName();
if (methodName.startsWith("jsFunction_")) {
methodName = methodName.substring(11);
FunctionObject func = new FunctionObject(methodName,
methods[i], proto);
proto.defineProperty(methodName, func, attributes);
} else if (methodName.startsWith("jsGet_")) {
methodName = methodName.substring(6);
proto.defineProperty(methodName, null, methods[i],
null, attributes);
}
}
return proto;
}
/**
* This method is used as HopObject constructor from JavaScript.
*/
@ -408,14 +437,18 @@ public class HopObject extends ScriptableObject implements Wrapper {
/**
* Prefetch child objects from (relational) database.
*/
public void jsFunction_prefetchChildren() {
jsFunction_prefetchChildren(0, 1000);
public void jsFunction_prefetchChildren(Object startArg, Object lengthArg) {
// check if we were called with no arguments
if (startArg == Undefined.instance && lengthArg == Undefined.instance) {
prefetchChildren(0, 1000);
} else {
int start = (int) ScriptRuntime.toNumber(startArg);
int length = (int) ScriptRuntime.toNumber(lengthArg);
prefetchChildren(start, length);
}
}
/**
* Prefetch child objects from (relational) database.
*/
public void jsFunction_prefetchChildren(int start, int length) {
private void prefetchChildren(int start, int length) {
if (!(node instanceof helma.objectmodel.db.Node)) {
return;
}
@ -424,7 +457,8 @@ public class HopObject extends ScriptableObject implements Wrapper {
try {
((helma.objectmodel.db.Node) node).prefetchChildren(start, length);
} catch (Exception x) {
} catch (Exception ignore) {
System.err.println("Error in HopObject.prefetchChildren(): "+ignore);
}
}
@ -446,6 +480,7 @@ public class HopObject extends ScriptableObject implements Wrapper {
private Scriptable list() {
checkNode();
prefetchChildren(0, 1000);
Enumeration e = node.getSubnodes();
ArrayList a = new ArrayList();
@ -475,7 +510,7 @@ public class HopObject extends ScriptableObject implements Wrapper {
checkNode();
jsFunction_prefetchChildren(start, length);
prefetchChildren(start, length);
ArrayList a = new ArrayList();
for (int i=start; i<start+length; i++) {
@ -706,7 +741,7 @@ public class HopObject extends ScriptableObject implements Wrapper {
* @return ...
*/
public Object get(String name, Scriptable start) {
// System.err.println ("GET from "+node+": "+name);
// System.err.println("GET from "+this+": "+name+" ->"+super.get(name, start));
Object retval = null;
if (node == null) {

View file

@ -51,6 +51,9 @@ public final class RhinoCore {
// the wrap factory
WrapFactory wrapper;
// the prototype for HopObject
ScriptableObject hopObjectProto;
// the prototype for path objects
PathWrapper pathProto;
@ -90,21 +93,15 @@ public final class RhinoCore {
global = (GlobalObject) context.initStandardObjects(g);
ScriptableObject.defineClass(global, HopObject.class);
ScriptableObject.defineClass(global, FileObject.class);
ScriptableObject.defineClass(global, FtpObject.class);
pathProto = new PathWrapper(this);
hopObjectProto = HopObject.init(global);
FileObject.init(global);
FtpObject.init(global);
ImageObject.init(global);
XmlRpcObject.init(global);
MailObject.init(global, app.getProperties());
registerPrototype("hopobject",
(ScriptableObject) ScriptableObject
.getClassPrototype(global, "HopObject"));
registerPrototype("global", global);
// add some convenience functions to string, date and number prototypes
Scriptable stringProto = ScriptableObject.getClassPrototype(global, "String");
stringProto.put("trim", stringProto, new StringTrim());
@ -150,33 +147,34 @@ public final class RhinoCore {
*
* @param prototype the prototype to be created
*/
synchronized void initPrototype(Prototype prototype) {
synchronized TypeInfo 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 = getRawPrototype(name);
ScriptableObject op = (type == null) ? null : type.objectPrototype;
// if prototype info doesn't exist (i.e. is a standard prototype
// built by HopExtension), create it.
if (op == null) {
try {
if ("global".equals(lowerCaseName)) {
op = global;
} else if ("hopobject".equals(lowerCaseName)) {
op = hopObjectProto;
} else {
op = new HopObject(name);
op.setParentScope(global);
// op.put("prototypename", op, name);
} catch (Exception ignore) {
System.err.println("Error creating prototype: " + ignore);
ignore.printStackTrace();
}
registerPrototype(name, op);
type = registerPrototype(prototype, op);
}
// Register a constructor for all types except global.
// This will first create a new prototyped hopobject and then calls
// This will first create a new prototyped HopObject and then calls
// the actual (scripted) constructor on it.
if (!"global".equalsIgnoreCase(name) &&
!"root".equalsIgnoreCase(name) &&
!"hopobject".equalsIgnoreCase(name)) {
if (!"global".equals(lowerCaseName)) {
try {
installConstructor(name, op);
} catch (Exception ignore) {
@ -184,18 +182,20 @@ public final class RhinoCore {
ignore.printStackTrace();
}
}
return type;
}
/**
* Set up a prototype, parsing and compiling all its script files.
*
* @param prototype the prototype to update/evaluate/compile
* @param type the info, containing the object proto, last update time and
* the set of compiled functions properties
*/
synchronized void evaluatePrototype(Prototype prototype, TypeInfo type) {
// System.err.println("EVALUATING PROTO: "+prototype);
synchronized void evaluatePrototype(TypeInfo type) {
Scriptable op = type.objectPrototype;
Prototype prototype = type.frameworkPrototype;
// set the parent prototype in case it hasn't been done before
// or it has changed...
@ -258,8 +258,9 @@ public final class RhinoCore {
*/
private void setParentPrototype(Prototype prototype, Scriptable op) {
String name = prototype.getName();
String lowerCaseName = prototype.getLowerCaseName();
if (!"global".equalsIgnoreCase(name) && !"hopobject".equalsIgnoreCase(name)) {
if (!"global".equals(lowerCaseName) && !"hopobject".equals(lowerCaseName)) {
// get the prototype's prototype if possible and necessary
Scriptable opp = null;
@ -282,12 +283,12 @@ public final class RhinoCore {
/**
* This is a version of org.mozilla.javascript.FunctionObject.addAsConstructor()
* that does not set the constructor property in the prototype. This is because
* we want our own scripted constructor function to prevail, if it is defined.
* we want our own scripted constructor function to be visible, if it is defined.
*
* @param name the name of the constructor
* @param op the object prototype
*/
private void installConstructor(String name, Scriptable op) {
void installConstructor(String name, Scriptable op) {
FunctionObject fo = new FunctionObject(name, HopObject.hopObjCtor, global);
ScriptRuntime.setFunctionProtoAndParent(global, fo);
@ -315,27 +316,23 @@ public final class RhinoCore {
for (Iterator i = protos.iterator(); i.hasNext();) {
Prototype proto = (Prototype) i.next();
TypeInfo type = (TypeInfo) prototypes.get(proto.getName());
TypeInfo type = (TypeInfo) prototypes.get(proto.getLowerCaseName());
if (type == null) {
// a prototype we don't know anything about yet. Init local update info.
initPrototype(proto);
type = (TypeInfo) prototypes.get(proto.getName());
type = initPrototype(proto);
}
// only update prototype if it has already been initialized.
// otherwise, this will be done on demand
// System.err.println ("CHECKING PROTO "+proto+": "+type);
if (type.lastUpdate > -1) {
Prototype p = app.typemgr.getPrototype(type.protoName);
// let the type manager scan the prototype's directory
app.typemgr.updatePrototype(proto);
if (p != null) {
// System.err.println ("UPDATING PROTO: "+p);
app.typemgr.updatePrototype(p);
if (p.getLastUpdate() > type.lastUpdate) {
evaluatePrototype(p, type);
}
// and re-evaluate if necessary
if (type.needsUpdate()) {
evaluatePrototype(type);
}
}
}
@ -343,19 +340,6 @@ public final class RhinoCore {
lastUpdate = System.currentTimeMillis();
}
/**
* Get a raw prototype, i.e. in potentially unfinished state
* without checking if it needs to be updated.
*/
private ScriptableObject getRawPrototype(String protoName) {
if (protoName == null) {
return null;
}
TypeInfo type = (TypeInfo) prototypes.get(protoName);
return (type == null) ? null : type.objectPrototype;
}
/**
* A version of getPrototype() that retrieves a prototype and checks
@ -394,20 +378,16 @@ public final class RhinoCore {
return null;
}
TypeInfo type = (TypeInfo) prototypes.get(protoName);
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)) {
Prototype p = app.typemgr.getPrototype(protoName);
app.typemgr.updatePrototype(type.frameworkPrototype);
if (p != null) {
app.typemgr.updatePrototype(p);
if (p.getLastUpdate() > type.lastUpdate) {
evaluatePrototype(p, type);
}
if (type.needsUpdate()) {
evaluatePrototype(type);
}
}
@ -417,10 +397,10 @@ public final class RhinoCore {
/**
* Register an object prototype for a prototype name.
*/
private void registerPrototype(String protoName, ScriptableObject op) {
if ((protoName != null) && (op != null)) {
prototypes.put(protoName, new TypeInfo(op, protoName));
}
private TypeInfo registerPrototype(Prototype proto, ScriptableObject op) {
TypeInfo type = new TypeInfo(proto, op);
prototypes.put(proto.getLowerCaseName(), type);
return type;
}
/**
@ -561,8 +541,8 @@ public final class RhinoCore {
Scriptable op = getPrototype(prototypeName);
if (op == null) {
prototypeName = "hopobject";
op = getPrototype("hopobject");
prototypeName = "HopObject";
op = getPrototype("HopObject");
}
w = new JavaObject(global, e, prototypeName, op, this);
@ -602,8 +582,8 @@ public final class RhinoCore {
// no prototype found for this node?
if (op == null) {
op = getValidPrototype("hopobject");
protoname = "hopobject";
op = getValidPrototype("HopObject");
protoname = "HopObject";
}
esn = new HopObject(protoname, op);
@ -797,7 +777,7 @@ public final class RhinoCore {
}
// mark prototype as broken
if (type.error == null && e instanceof EcmaError) {
if ("global".equals(type.protoName)) {
if ("global".equals(type.frameworkPrototype.getLowerCaseName())) {
globalError = (EcmaError) e;
} else {
type.error = (EcmaError) e;
@ -828,7 +808,10 @@ public final class RhinoCore {
*/
class TypeInfo {
// the object prototype for this type
// the framework prototype object
Prototype frameworkPrototype;
// the JavaScript prototype for this type
ScriptableObject objectPrototype;
// timestamp of last update. This is -1 so even an empty prototype directory
@ -837,7 +820,7 @@ public final class RhinoCore {
long lastUpdate = -1;
// the prototype name
String protoName;
// String protoName;
// a set of property values that were defined in last script compliation
Set compiledFunctions;
@ -847,9 +830,9 @@ public final class RhinoCore {
EcmaError error;
public TypeInfo(ScriptableObject op, String name) {
public TypeInfo(Prototype proto, ScriptableObject op) {
frameworkPrototype = proto;
objectPrototype = op;
protoName = name;
compiledFunctions = new HashSet(0);
// remember properties already defined on this object prototype
predefinedProperties = new HashSet();
@ -859,8 +842,12 @@ public final class RhinoCore {
}
}
public boolean needsUpdate() {
return frameworkPrototype.getLastUpdate() > lastUpdate;
}
public String toString() {
return ("TypeInfo[" + protoName + "," + new Date(lastUpdate) + "]");
return ("TypeInfo[" + frameworkPrototype + "," + new Date(lastUpdate) + "]");
}
}